如何处理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: '提醒邮件已发送'}); });
三、补充说明
- Stripe Checkout页面的关闭/跳转:由于该页面属于Stripe域名,前端无法监听其离开事件,这种场景依赖
checkout.session.expired事件即可,会话超时后Stripe会自动触发该事件,后端收到后发送邮件。 - 避免重复发送:所有邮件发送逻辑中,都先查询Checkout Session的
payment_status,仅当状态为unpaid时才执行发送。 - 邮件体验优化:可在邮件中添加支付跳转链接,用户点击后直接通过
stripe.redirectToCheckout({sessionId: xxx})进入未完成的会话。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

