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

Stripe创建Subscription时重复扣费问题求助

问题分析

用户订阅产品时出现重复扣费,核心原因有两个:

  1. 每次订阅都重复创建Stripe的Product和Price:这会生成大量冗余资源,还可能触发重复计费逻辑。
  2. 订阅支付行为未明确管控:默认情况下,Stripe创建订阅会自动生成首张发票并扣款,如果存在接口重复调用、前置支付操作,就会导致两次扣费。
修复方案

1. 复用Product和Price,避免重复创建

Product和Price是Stripe里的静态套餐配置,无需每次订阅都重新创建。可以提前在Stripe后台建好对应套餐的配置,也可以在代码中先查询是否已有现成的资源,仅在不存在时才创建:

// 查询同名Product,存在则复用,不存在则创建
const existingProducts = await stripe.products.list({ name: req.body.package_name });
let product = existingProducts.data.length > 0 ? existingProducts.data[0] : await stripe.products.create({ name: req.body.package_name });

// 查询对应规格的Price,存在则复用,不存在则创建
const existingPrices = await stripe.prices.list({
  product: product.id,
  unit_amount: req.body.amount * 100,
  currency: 'gbp',
  recurring: { interval: 'month' }
});
let price = existingPrices.data.length > 0 ? existingPrices.data[0] : await stripe.prices.create({
  unit_amount: req.body.amount * 100,
  currency: 'gbp',
  recurring: { interval: 'month' },
  product: product.id,
});

2. 明确控制订阅的支付逻辑

创建订阅时,通过payment_behavior参数精准控制支付行为,避免自动重复扣款:

  • 正常订阅场景(需立即扣首月费用):使用payment_behavior: 'default_incomplete'(Stripe当前默认值),支付失败时订阅会进入待完成状态,不会自动重试扣款,避免重复扣费。
  • 若用户已通过其他渠道完成支付:设置payment_behavior: 'skip',跳过首张发票的自动支付。

修改后的订阅创建代码:

const subscription = await stripe.subscriptions.create({
  customer: user.stripe_customer_id,
  items: [{ price: price.id }],
  expand: ['latest_invoice.payment_intent'],
  payment_behavior: 'default_incomplete', // 明确支付行为
});

3. 添加幂等性处理,防止重复提交

前端用户可能重复点击订阅按钮导致接口多次调用,给Stripe接口添加幂等键,确保同一请求只会执行一次:

// 前端每次请求生成唯一幂等键,放在请求头或参数中传递
const idempotencyKey = req.headers['idempotency-key'] || req.body.idempotency_key;

// 调用Stripe接口时传入幂等键
const subscription = await stripe.subscriptions.create({
  customer: user.stripe_customer_id,
  items: [{ price: price.id }],
  expand: ['latest_invoice.payment_intent'],
  payment_behavior: 'default_incomplete'
}, { idempotencyKey });

4. 重构代码,提升可读性与可维护性

原代码嵌套过深,换成async/await替代链式then,更易维护且方便统一处理错误:

exports.createSubscription = async function(req, res, next) {
  try {
    const { payment_method_id, package_name, amount } = req.body;

    // 参数校验
    if (!payment_method_id) return Response.failure(req, res, '', 'payment_method_id is required');
    if (!package_name) return Response.failure(req, res, '', 'package_name is required');
    if (!amount) return Response.failure(req, res, '', 'amount is required');

    // 查询用户
    const user = await User.findById(req.userId);
    if (!user?.stripe_customer_id) return Response.failure(req, res, '', '用户不存在或未绑定Stripe客户ID');

    // 绑定并设置默认支付方式
    await stripe.paymentMethods.attach(payment_method_id, { customer: user.stripe_customer_id });
    await stripe.customers.update(user.stripe_customer_id, {
      invoice_settings: { default_payment_method: payment_method_id }
    });

    // 查询/创建Product和Price
    const existingProducts = await stripe.products.list({ name: package_name });
    let product = existingProducts.data.length > 0 ? existingProducts.data[0] : await stripe.products.create({ name: package_name });

    const existingPrices = await stripe.prices.list({
      product: product.id,
      unit_amount: amount * 100,
      currency: 'gbp',
      recurring: { interval: 'month' }
    });
    let price = existingPrices.data.length > 0 ? existingPrices.data[0] : await stripe.prices.create({
      unit_amount: amount * 100,
      currency: 'gbp',
      recurring: { interval: 'month' },
      product: product.id,
    });

    // 创建订阅
    const subscription = await stripe.subscriptions.create({
      customer: user.stripe_customer_id,
      items: [{ price: price.id }],
      expand: ['latest_invoice.payment_intent'],
      payment_behavior: 'default_incomplete',
    });

    Response.success(req, res, subscription, '订阅创建成功');
  } catch (error) {
    console.error('订阅创建失败:', error);
    Response.failure(req, res, {}, error.raw?.message || error.message);
  }
};
额外排查点
  • 登录Stripe后台查看订阅和发票记录,确认是否生成了重复订阅或多次支付的发票。
  • 前端添加按钮防抖逻辑,比如点击后禁用按钮,防止用户重复触发订阅请求。

内容的提问来源于stack exchange,提问作者Expert Dev Designer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 08:38:11