三种支付方式自动扣费流程总结
三种支付方式自动扣费流程总结
本文档详细说明 Stripe、Airwallex 和 Antom 三种支付方式的自动扣费(自动续费)完整流程。
📋 目录
1. Stripe 自动扣费流程
1.1 首次支付(开启自动续费)
API 路由: /api/stripe/pay/order
流程步骤:
-
创建/获取 Stripe Customer
// 如果用户没有 stripe_customer_id,创建新的 Customer const customer = await stripe.customers.create({ email: userInfo.email, metadata: { user_id: userId } }); // 保存到 web_user.stripe_customer_id -
创建 Checkout Session
// 调用 createStripePaymentSession { mode: 'payment', payment_method_types: ['card'], // 自动续费仅支持 card customer: customerId, // 关联 Customer payment_intent_data: { setup_future_usage: 'off_session', // 关键:保存支付方式用于后续扣费 metadata: { order_no: orderNum, auto_renewal: 'true' } } } -
用户完成支付
-
用户在 Stripe Checkout 页面完成支付
-
支付成功后,Stripe 会发送 webhook 事件
-
1.2 Webhook 处理
Webhook 事件: payment_intent.succeeded
处理逻辑:
// 从 PaymentIntent 中获取 payment_method
const paymentMethodId = paymentIntent.payment_method;
// 保存到订单的 payment_token 字段
await collection('web_order').updateOne(
{ orderNum },
{
$set: {
payment_token: paymentMethodId,
auto_renewal: true
}
}
);
关键点:
-
payment_method是 Stripe 保存的支付方式 ID(如pm_xxx) -
该支付方式已关联到 Customer,可用于后续自动扣费
1.3 自动扣费(定时任务)
函数: chargeStripeAutoRenewal
流程:
// 1. 从订单获取 payment_token (payment_method_id) 和用户获取 customer_id
const paymentMethodId = order.payment_token;
const customerId = user.stripe_customer_id;
// 2. 创建并确认 PaymentIntent
const paymentIntent = await stripe.paymentIntents.create({
amount: order.price_total,
currency: 'hkd',
customer: customerId,
payment_method: paymentMethodId,
off_session: true, // 用户不在场
confirm: true, // 立即确认
metadata: {
order_no: orderNum,
auto_renewal: 'true'
}
});
关键参数:
-
off_session: true: 表示用户不在场,无需用户交互 -
confirm: true: 立即确认支付,无需额外步骤 -
payment_method: 使用首次支付时保存的payment_method_id
2. Airwallex 自动扣费流程
2.1 首次支付(开启自动续费)
API 路由: /api/airwallex/pay/order
流程步骤:
-
创建/获取 Airwallex Customer
// 调用 createOrGetAirwallexCustomer const customerId = await createOrGetAirwallexCustomer({ userId, userInfo: { email, username }, existingCustomerId: user.airwallex_customer_id }); // 保存到 web_user.airwallex_customer_id -
创建 PaymentIntent
// 调用 createAirwallexPaymentSession { request_id: generateOrderNumber(), amount: amount / 100, // 转换为元 currency: 'HKD', customer_id: customerId, // 关联 Customer next_triggered_by: 'merchant', // 关键:触发 Payment Consent 创建 merchant_trigger_reason: 'scheduled', payment_method_options: { card: { auto_capture: true, three_ds_action: 'FORCE_3DS' } } } -
用户完成支付
-
前端使用 Airwallex SDK 打开托管收银台
-
用户完成 3DS 验证和支付
-
支付成功后,Airwallex 会发送 webhook 事件
-
2.2 Webhook 处理
Webhook 事件: payment_consent.verified
处理逻辑:
// 从 PaymentConsent 中获取 consent ID
const paymentConsentId = paymentConsent.id; // 如 cst_xxx
// 保存到订单的 payment_token 字段
await collection('web_order').updateOne(
{ orderNum },
{
$set: {
payment_token: paymentConsentId,
auto_renewal: true
}
}
);
关键点:
-
payment_consent_id是已验证的支付授权 ID(如cst_xxx) -
该 PaymentConsent 已关联到 Customer,可用于后续自动扣费
-
如果订单中没有保存
payment_token,自动扣费时会从 Customer 查询 PaymentConsents
2.3 自动扣费(定时任务)
函数: chargeAirwallexAutoRenewal
流程:
// 1. 从订单获取 payment_token (payment_consent_id) 和用户获取 customer_id
let paymentConsentId = order.payment_token;
// 2. 如果 payment_token 为空,从 Customer 查询 PaymentConsents
if (!paymentConsentId) {
const consents = await fetchPaymentConsents(customerId);
paymentConsentId = consents.find(c => c.status === 'verified').id;
}
// 3. 创建 PaymentIntent
const intent = await fetch('/api/v1/pa/payment_intents/create', {
body: {
request_id: generateOrderNumber(),
amount: amount / 100,
currency: 'HKD',
customer_id: customerId,
merchant_order_id: `${orderNum}_RENEWAL`
}
});
// 4. 确认支付(关键:在 confirm 时传入 payment_consent_id)
const result = await fetch(`/api/v1/pa/payment_intents/${intent.id}/confirm`, {
body: {
request_id: generateOrderNumber(),
payment_consent_id: paymentConsentId // 关键:在这里传入
}
});
关键参数:
-
payment_consent_id必须在confirm步骤传入,而不是create步骤 -
customer_id在create步骤传入
3. Antom 自动扣费流程
3.1 首次支付(开启自动续费)
API 路由: /api/antom/pay/order
流程步骤:
-
调用 consult 接口获取授权链接
// 调用 createAntomAuthorization { authorizationRequestId: generateOrderNumber(), authState: generateOrderNumber(), customerBelongsTo: 'ALIPAY_HK', scopes: ['AGREEMENT_PAY'], // 自动扣款授权范围 authRedirectUrl: `${BASE_URL}/antom/authorize/callback?type=${type}&orderNum=${orderNum}`, authNotifyUrl: `${BASE_URL}/antom/callback?type=${type}&orderNum=${orderNum}`, terminalType: 'WEB' } -
用户跳转授权
-
前端跳转到返回的
authorizationUrl -
用户在 Antom 页面完成授权(同意自动扣款协议)
-
-
授权回调处理
API 路由:
/antom/authorize/callback(需要创建)Antom 会重定向到
authRedirectUrl,带上参数:-
authCode: 授权码(用于首次支付) -
authState: 授权状态(用于验证)
处理逻辑:
// 1. 接收 authCode 和 authState const authCode = searchParams.get('authCode'); const authState = searchParams.get('authState'); // 2. 使用 authCode 调用 pay 接口完成首次支付(同步扣款) const requestBody = { paymentRequestId: generateOrderNumber(), productCode: 'AGREEMENT_PAYMENT', // 代扣场景 paymentAmount: { currency: 'HKD', value: amountInCents }, order: { referenceOrderId: orderNum, orderDescription: order.title, orderAmount: { currency: 'HKD', value: amountInCents } }, paymentMethod: { paymentMethodId: authCode, // 使用 authCode paymentMethodType: 'ALIPAY_HK' }, settlementStrategy: { settlementCurrency: 'HKD' }, paymentNotifyUrl: buildNotifyUrl(type, orderNum) }; // 3. 调用 pay 接口 const response = await fetch(ANTOM_CONFIG.gateway.pay, { method: 'POST', headers: { /* 签名等 */ }, body: JSON.stringify(requestBody) }); // 4. 支付成功后,保存 agreementId const data = await response.json(); const agreementId = data.agreementId || authCode; // 从响应获取或使用 authCode // 5. 更新订单 await collection('web_order').updateOne( { _id: order._id }, { $set: { status: 2, // 待确认 pay_type: 9, // Antom pay_time: Date.now(), antom_payment_id: data.paymentId, payment_token: agreementId // 保存用于后续自动扣款 } } ); -
3.2 Webhook 处理
Webhook 事件: notifyPayment(支付通知)、notifyAuthorization(授权通知)
处理逻辑:
-
支付通知:更新订单状态
-
授权通知:可获取
agreementId(但通常在授权回调中已处理)
3.3 自动扣费(定时任务)
函数: chargeAntomAutoRenewal
流程:
// 1. 从订单获取 payment_token (agreementId)
const agreementId = order.payment_token;
// 2. 调用 pay 接口进行自动扣款
const requestBody = {
paymentRequestId: generateOrderNumber(),
productCode: 'AGREEMENT_PAYMENT', // 代扣场景
paymentAmount: { currency: 'HKD', value: amountInCents },
order: {
referenceOrderId: `${orderNum}_RENEWAL`,
orderDescription: 'Auto Renewal',
orderAmount: { currency: 'HKD', value: amountInCents }
},
paymentMethod: {
paymentMethodId: agreementId, // 使用保存的 agreementId
paymentMethodType: 'ALIPAY_HK'
},
settlementStrategy: {
settlementCurrency: 'HKD'
},
paymentNotifyUrl: buildNotifyUrl('goods', orderNum)
};
// 3. 调用 pay 接口
const response = await fetch(ANTOM_CONFIG.gateway.pay, {
method: 'POST',
headers: { /* 签名等 */ },
body: JSON.stringify(requestBody)
});
关键参数:
-
productCode: 'AGREEMENT_PAYMENT': 表示代扣场景 -
paymentMethod.paymentMethodId: 使用保存的agreementId
4. 自动续费定时任务
API 路由: /api/cron/auto-renewal
触发条件:
-
订单
auto_renewal: true -
订单
status: 4(已完成)或6(已下载) -
订单
expire_time <= 今天结束时间 -
订单
payment_token存在(Airwallex 可为空,会从 Customer 查询)
处理流程:
// 1. 查询符合条件的订单
const ordersToProcess = await collection('web_order').aggregate([
{ $match: { auto_renewal: true, status: { $in: [4, 6] }, expire_time: { $lte: todayEnd } } },
// ... 分组逻辑,获取每个订单的最新记录
]).toArray();
// 2. 并行处理所有订单
await Promise.allSettled(
ordersToProcess.map(order => processAutoRenewal(order))
);
// 3. 根据 pay_type 调用对应的扣费函数
switch (order.pay_type) {
case 7: // Stripe
chargeResult = await chargeWithStripe(order, paymentToken);
break;
case 8: // Airwallex
chargeResult = await chargeWithAirwallex(order, paymentToken ?? null);
break;
case 9: // Antom
chargeResult = await chargeWithAntom(order, paymentToken);
break;
}
// 4. 扣费成功后,创建续费订单
const renewalOrder = await createRenewalOrder(order, chargeResult);
await giveBonusPoints(renewalOrder);
5. 数据存储说明
5.1 订单表(web_order)
| 字段 | 说明 | Stripe | Airwallex | Antom |
|------|------|--------|-----------|-------|
| auto_renewal | 是否开启自动续费 | true | true | true |
| payment_token | 支付凭证ID | pm_xxx (PaymentMethod ID) | cst_xxx (PaymentConsent ID) | agreementId |
| pay_type | 支付方式 | 7 | 8 | 9 |
| stripe_payment_intent_id | Stripe 支付ID | pi_xxx | - | - |
| airwallex_payment_intent_id | Airwallex 支付ID | - | int_xxx | - |
| antom_payment_id | Antom 支付ID | - | - | 2025xxx |
| status | 订单状态 | 2(待确认) → 4(已完成) | 2(待确认) → 4(已完成) | 2(待确认) → 4(已完成) |
| expire_time | 过期时间 | 需要设置 | 需要设置 | 需要设置 |
5.2 用户表(web_user)
| 字段 | 说明 | Stripe | Airwallex | Antom |
|------|------|--------|-----------|-------|
| stripe_customer_id | Stripe Customer ID | cus_xxx | - | - |
| airwallex_customer_id | Airwallex Customer ID | - | cus_xxx | - |
5.3 续费订单
续费订单会创建新的订单记录:
-
type: 2(续费订单) -
pid: 指向原订单的orderNum -
pay_type: 继承原订单的支付方式 -
payment_token: 继承原订单的payment_token -
auto_renewal: 继承原订单的auto_renewal
📝 总结对比
| 特性 | Stripe | Airwallex | Antom |
|------|--------|-----------|-------|
| 首次支付方式 | Checkout Session | PaymentIntent + 托管收银台 | consult 授权 + pay 支付 |
| 保存的凭证 | PaymentMethod ID (pm_xxx) | PaymentConsent ID (cst_xxx) | Agreement ID |
| 自动扣费接口 | PaymentIntent.create + confirm | PaymentIntent.create + confirm (传入 consent_id) | pay 接口 |
| Customer 管理 | 需要创建 Customer | 需要创建 Customer | 不需要 Customer |
| Webhook 事件 | payment_intent.succeeded | payment_consent.verified | notifyPayment, notifyAuthorization |
| 支付方式限制 | 仅支持 card | 支持多种(card, alipay 等) | 支持多种(ALIPAY_HK 等) |
| 授权方式 | 通过 setup_future_usage | 通过 PaymentConsent | 通过 consult 接口授权 |
🔧 测试建议
Stripe 测试
-
创建订单,开启自动续费
-
完成支付,检查 webhook 是否保存了
payment_token -
设置订单
status: 4,expire_time为过去时间 -
调用
/api/cron/auto-renewal测试自动扣费
Airwallex 测试
-
创建订单,开启自动续费
-
完成支付和 3DS 验证,检查 webhook 是否保存了
payment_token -
设置订单
status: 4,expire_time为过去时间 -
调用
/api/cron/auto-renewal测试自动扣费
Antom 测试
-
创建订单,开启自动续费
-
完成授权和首次支付,检查是否保存了
agreementId到payment_token -
设置订单
status: 4,expire_time为过去时间 -
调用
/api/cron/auto-renewal测试自动扣费
最后更新: 2025-11-11
本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。
萧兮的博客:https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/87499a92-b2cf-42e7-8fb3-44a3d15e0e09