三种支付方式自动扣费流程总结

三种支付方式自动扣费流程总结

三种支付方式自动扣费流程总结

本文档详细说明 Stripe、Airwallex 和 Antom 三种支付方式的自动扣费(自动续费)完整流程。


📋 目录

  1. Stripe 自动扣费流程

  2. Airwallex 自动扣费流程

  3. Antom 自动扣费流程

  4. 自动续费定时任务

  5. 数据存储说明


1. Stripe 自动扣费流程

1.1 首次支付(开启自动续费)

API 路由: /api/stripe/pay/order

流程步骤:

  1. 创建/获取 Stripe Customer

    
    // 如果用户没有 stripe_customer_id,创建新的 Customer
    
    const customer = await stripe.customers.create({
    
      email: userInfo.email,
    
      metadata: { user_id: userId }
    
    });
    
    // 保存到 web_user.stripe_customer_id
    
    
  2. 创建 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'
    
        }
    
      }
    
    }
    
    
  3. 用户完成支付

    • 用户在 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

流程步骤:

  1. 创建/获取 Airwallex Customer

    
    // 调用 createOrGetAirwallexCustomer
    
    const customerId = await createOrGetAirwallexCustomer({
    
      userId,
    
      userInfo: { email, username },
    
      existingCustomerId: user.airwallex_customer_id
    
    });
    
    // 保存到 web_user.airwallex_customer_id
    
    
  2. 创建 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'
    
        }
    
      }
    
    }
    
    
  3. 用户完成支付

    • 前端使用 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_idcreate 步骤传入


3. Antom 自动扣费流程

3.1 首次支付(开启自动续费)

API 路由: /api/antom/pay/order

流程步骤:

  1. 调用 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'
    
    }
    
    
  2. 用户跳转授权

    • 前端跳转到返回的 authorizationUrl

    • 用户在 Antom 页面完成授权(同意自动扣款协议)

  3. 授权回调处理

    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 测试

  1. 创建订单,开启自动续费

  2. 完成支付,检查 webhook 是否保存了 payment_token

  3. 设置订单 status: 4expire_time 为过去时间

  4. 调用 /api/cron/auto-renewal 测试自动扣费

Airwallex 测试

  1. 创建订单,开启自动续费

  2. 完成支付和 3DS 验证,检查 webhook 是否保存了 payment_token

  3. 设置订单 status: 4expire_time 为过去时间

  4. 调用 /api/cron/auto-renewal 测试自动扣费

Antom 测试

  1. 创建订单,开启自动续费

  2. 完成授权和首次支付,检查是否保存了 agreementIdpayment_token

  3. 设置订单 status: 4expire_time 为过去时间

  4. 调用 /api/cron/auto-renewal 测试自动扣费


最后更新: 2025-11-11


本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。

萧兮的博客https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/87499a92-b2cf-42e7-8fb3-44a3d15e0e09