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

Stripe订阅变更未支付场景下的业务逻辑处理疑问

Stripe订阅变更后遗留未支付发票的问题解决

你遇到的核心问题是每次订阅变更都会生成独立发票,且未支付的旧发票不会自动关联或失效,这是当前参数设置的必然结果:

  • payment_behavior: 'default_incomplete' 会让订阅变更时立即生成待支付发票,订阅状态变为past_due但不阻止后续变更
  • proration_behavior: 'always_invoice' 会把变更产生的 prorations 单独开票,进一步增加独立发票数量

针对你提到的两个场景,以下是具体解决方案:

一、调整订阅更新逻辑,从根源避免遗留发票

方案1:限制未结清欠款时的订阅变更

将payment_behavior从default_incomplete改为pending_if_incomplete,这样当用户存在未支付发票时,后续的订阅变更请求会被暂停,直到旧发票被支付或取消,从根源上防止多次变更产生多张未付发票。

修改updateCustomerSubscription方法:

async updateCustomerSubscription(subscriptionId: string, subscriptionInfo: SubscriptionInfoT) {
    const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
    return this.stripe.subscriptions.update(subscriptionId, {
      payment_behavior: 'pending_if_incomplete', // 替换原参数
      proration_behavior: 'always_invoice',
      description: Object.values(subscriptionInfo.numbers)
        .filter(n => n)
        .join(', '),
      items: [
        {
          id: subscription.items.data[0].id,
          price: subscriptionInfo.priceId,
          quantity: subscriptionInfo.quantity,
        },
      ],
      metadata: subscriptionInfo.numbers,
      expand: ['latest_invoice.payment_intent'],
    });
  }

方案2:更新订阅时自动作废未支付旧发票

如果需要允许用户在未支付时继续调整订阅,可以在执行更新前,先查询该订阅关联的所有未支付发票并标记为作废,旧发票作废后不会再显示给用户,也不会影响订阅状态。

修改后的updateCustomerSubscription方法:

async updateCustomerSubscription(subscriptionId: string, subscriptionInfo: SubscriptionInfoT) {
    // 先处理未支付的旧发票
    const openInvoices = await this.stripe.invoices.list({
      subscription: subscriptionId,
      status: 'open',
      limit: 10
    });

    for (const invoice of openInvoices.data) {
      await this.stripe.invoices.voidInvoice(invoice.id);
    }

    // 再执行订阅更新
    const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
    return this.stripe.subscriptions.update(subscriptionId, {
      payment_behavior: 'default_incomplete',
      proration_behavior: 'always_invoice',
      description: Object.values(subscriptionInfo.numbers)
        .filter(n => n)
        .join(', '),
      items: [
        {
          id: subscription.items.data[0].id,
          price: subscriptionInfo.priceId,
          quantity: subscriptionInfo.quantity,
        },
      ],
      metadata: subscriptionInfo.numbers,
      expand: ['latest_invoice.payment_intent'],
    });
  }

二、处理已存在的遗留未支付发票

对于已经产生的未支付发票,可以通过以下代码批量作废指定客户的未结清订阅发票:

async voidUnpaidSubscriptionInvoices(customerId: string) {
  const invoices = await this.stripe.invoices.list({
    customer: customerId,
    status: 'open',
    limit: 100
  });
  
  for (const invoice of invoices.data) {
    if (invoice.subscription) {
      await this.stripe.invoices.voidInvoice(invoice.id);
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 13:25:15