You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何处理Stripe未完成交易:实现未完成时发送用户邮件

解决方案

一、利用Stripe Webhooks覆盖主动取消/会话过期场景

Stripe会在以下场景触发Webhook事件,通过监听这些事件可直接触发提醒邮件:

  • checkout.session.canceled:用户点击Stripe Checkout页面的返回/取消按钮时触发
  • checkout.session.expired:会话超时未完成支付时触发(默认超时1小时,可在创建Session时自定义expires_at调整)

1. 后端修改Session创建代码

在创建Checkout Session时添加metadata字段,存储用户邮箱、姓名等信息,方便Webhook触发时直接获取:

const session = await stripe.checkout.sessions.create({
    customer_email: email,
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: 'xxx',
            description: `xxx`
          },
          unit_amount: xxx,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`, // 用Stripe占位符获取会话ID,避免URL暴露用户信息
    cancel_url: `${baseUrl}/apply-now?cancel=true`,
    metadata: { // 存储用户关键信息
      user_email: email,
      user_name: fullName
    }
  });

2. 配置Stripe Webhook端点

在Express后端添加Webhook处理路由,监听目标事件并发送邮件:

const express = require('express');
const router = express.Router();
const stripe = require('stripe')(secret);
const YOUR_WEBHOOK_SECRET = '你的Stripe Webhook密钥'; // 在Stripe控制台生成获取

// 处理Stripe Webhook事件
router.post('/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, YOUR_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 处理用户主动取消事件
  if (event.type === 'checkout.session.canceled') {
    const session = event.data.object;
    await sendReminderEmail(session.metadata.user_email, session.metadata.user_name);
  }

  // 处理会话过期事件
  if (event.type === 'checkout.session.expired') {
    const session = event.data.object;
    await sendReminderEmail(session.metadata.user_email, session.metadata.user_name);
  }

  res.json({received: true});
});

// 邮件发送函数(根据实际邮件服务修改,如Nodemailer)
async function sendReminderEmail(email, name) {
  // 示例逻辑:
  // const transporter = nodemailer.createTransport({host: '你的邮件服务器', auth: {...}});
  // await transporter.sendMail({
  //   from: '你的业务邮箱',
  //   to: email,
  //   subject: '你的支付尚未完成',
  //   text: `Hi ${name}, 我们注意到你还未完成支付,点击链接即可继续完成流程`
  // });
}

module.exports = router;

注意:需在Stripe控制台的Webhook设置中,添加你的端点URL,并勾选checkout.session.canceled和checkout.session.expired事件,获取Webhook密钥。

二、前端监听页面离开事件,覆盖浏览器返回/关闭标签场景

当用户在你的页面(未跳转至Stripe Checkout前)点击浏览器返回、关闭标签或跳走,通过beforeunload事件结合navigator.sendBeacon发送异步请求,触发邮件提醒。

前端(Angular)修改代码

import {loadStripe} from '@stripe/stripe-js';
import {Component, OnInit, OnDestroy} from '@angular/core';

@Component({
  selector: 'app-payment',
  templateUrl: './payment.component.html',
  styleUrls: ['./payment.component.css']
})
export class PaymentComponent implements OnInit, OnDestroy {
  private sessionId: string | null = null;
  private unloadListener: (() => void) | null = null;

  ngOnInit() {
    // 监听页面离开事件
    this.unloadListener = () => {
      if (this.sessionId) {
        // 用sendBeacon发送请求,避免页面关闭时请求被中断
        navigator.sendBeacon(`${environment.apiURL}/send-payment-reminder`, JSON.stringify({
          sessionId: this.sessionId,
          email: this.myForm.value.email,
          name: this.myForm.value.fullName
        }));
      }
    };
    window.addEventListener('beforeunload', this.unloadListener);
  }

  ngOnDestroy() {
    // 组件销毁时移除监听,避免内存泄漏
    if (this.unloadListener) {
      window.removeEventListener('beforeunload', this.unloadListener);
    }
  }

  initiatePayment() {
    fetch(`${environment.apiURL}/${endpoint}`, {
      method: 'POST',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify(this.myForm.value)
    })
    .then(response => response.json())
    .then(async session => {
      this.sessionId = session.id; // 保存会话ID
      const stripe = await loadStripe(stripe_publish_key);
      return stripe.redirectToCheckout({ sessionId: session.id });
    })
    .then(result => {
      if (result.error) {
        alert(result.error.message);
      }
    });
  }
}

后端添加邮件提醒接口

// 在Express后端添加路由
router.post('/send-payment-reminder', express.json(), async (req, res) => {
  const { sessionId, email, name } = req.body;

  // 先验证会话状态,避免用户已完成支付后重复发送邮件
  const session = await stripe.checkout.sessions.retrieve(sessionId);
  if (session.payment_status === 'paid') {
    return res.json({message: '支付已完成,无需发送提醒'});
  }

  // 发送提醒邮件
  await sendReminderEmail(email, name);
  res.json({message: '提醒邮件已发送'});
});

三、补充说明

  1. Stripe Checkout页面的关闭/跳转:由于该页面属于Stripe域名,前端无法监听其离开事件,这种场景依赖checkout.session.expired事件即可,会话超时后Stripe会自动触发该事件,后端收到后发送邮件。
  2. 避免重复发送:所有邮件发送逻辑中,都先查询Checkout Session的payment_status,仅当状态为unpaid时才执行发送。
  3. 邮件体验优化:可在邮件中添加支付跳转链接,用户点击后直接通过stripe.redirectToCheckout({sessionId: xxx})进入未完成的会话。

内容的提问来源于stack exchange,提问作者Jonathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:55:00