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

Stripe API问题:免费订阅升级付费时如何避免重复扣费

订阅升级流程优化与重复扣费问题解决

当前流程说明

1. 生成支付会话

用户点击网站上的upgrade按钮后,调用以下代码创建Stripe Checkout会话,引导用户完成支付:

await stripe.checkout.sessions.create({
    customer: customerId,
    payment_method_types: ['card', 'cashapp'],
    mode: 'subscription',
    line_items: [
        {
            price_data: {
                currency: 'usd',
                unit_amount: priceCents,
                product: stripeProductId,
                recurring: {
                    interval: 'month',
                },
                tax_behavior: 'exclusive',
            },
            quantity: 1,
        },
    ],
})

2. 支付完成后的处理

用户完成支付后,系统收到checkout.session.completed Webhook事件,先执行本地操作(如更新SQL数据库),再调用以下API更新订阅:

const subscription = await stripe.subscriptions.retrieve(
    subscriptionId,
)
await stripe.subscriptions.update(subscriptionId, {
    proration_behavior: 'none',
    items: [
        {
            id: subscription.items.data[0].id,
            deleted: true,
        },
        {
            price_data: {
                currency: 'usd',
                unit_amount: Math.round(priceCentsForFutureCycles),
                product: targetPlanStripeProductId,
                recurring: {
                    interval: 'month',
                },
                tax_behavior: 'exclusive',
            },
            quantity: 1,
        },
    ],
})

问题分析

从免费订阅升级到付费订阅时,会出现重复扣费:Checkout会话已完成一次扣费,后续调用stripe.subscriptions.update时,Stripe会再次生成账单扣费。而付费订阅间升级时无此问题,因为原有付费订阅的计费周期已存在,加上设置了proration_behavior: 'none',不会触发立即计费。

核心原因:免费订阅无有效计费周期,更新订阅时添加付费项目会触发Stripe的立即计费逻辑,导致二次扣费;而付费订阅升级时,系统默认沿用原有计费周期,不会立即生成新账单。

解决方案

方案1:区分订阅类型处理

在Webhook处理逻辑中,先判断用户原有订阅是否为免费订阅:

  • 如果是免费订阅升级:Checkout会话已创建新的付费订阅,只需取消原有免费订阅即可,无需调用stripe.subscriptions.update更新旧订阅。
  • 如果是付费订阅升级:保留现有更新逻辑,利用proration_behavior: 'none'避免立即计费。

关键代码调整示例:

// 从checkout.session.completed事件中获取新创建的订阅ID
const newSubscriptionId = event.data.object.subscription;
const oldSubscriptionId = getCustomerCurrentSubscriptionId(customerId); // 自定义方法获取用户当前订阅ID
const oldSubscription = await stripe.subscriptions.retrieve(oldSubscriptionId);

// 判断原有订阅是否为免费(通过price的unit_amount是否为0判断)
const isOldSubscriptionFree = oldSubscription.items.data[0].price.unit_amount === 0;

if (isOldSubscriptionFree) {
    // 免费转付费:直接取消旧订阅,无需更新
    await stripe.subscriptions.cancel(oldSubscriptionId);
} else {
    // 付费转付费:执行原有更新逻辑
    await stripe.subscriptions.update(oldSubscriptionId, {
        proration_behavior: 'none',
        items: [
            {
                id: oldSubscription.items.data[0].id,
                deleted: true,
            },
            {
                price_data: {
                    currency: 'usd',
                    unit_amount: Math.round(priceCentsForFutureCycles),
                    product: targetPlanStripeProductId,
                    recurring: {
                        interval: 'month',
                    },
                    tax_behavior: 'exclusive',
                },
                quantity: 1,
            },
        ],
    });
    // 取消Checkout创建的新订阅(因为原有更新逻辑是升级旧订阅)
    await stripe.subscriptions.cancel(newSubscriptionId);
}

方案2:统一使用订阅升级模式(推荐)

不再通过Checkout创建新订阅,而是直接基于现有订阅创建升级会话,避免重复创建订阅导致的计费问题:

  1. 创建Checkout会话时,指定subscription参数为用户现有订阅ID,同时设置payment_method_types和line_items为目标付费方案:
await stripe.checkout.sessions.create({
    customer: customerId,
    payment_method_types: ['card', 'cashapp'],
    mode: 'subscription',
    subscription: oldSubscriptionId, // 指定现有订阅ID
    line_items: [
        {
            price_data: {
                currency: 'usd',
                unit_amount: priceCents,
                product: targetPlanStripeProductId,
                recurring: {
                    interval: 'month',
                },
                tax_behavior: 'exclusive',
            },
            quantity: 1,
        },
    ],
    proration_behavior: 'none', // 升级时不生成 prorations
})
  1. 用户完成支付后,Stripe会自动更新现有订阅,无需再手动调用stripe.subscriptions.update,彻底避免重复扣费。

这个方案更符合Stripe的官方升级流程,同时减少冗余操作,降低出错概率。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 05:40:05