Stripe推荐方案:为什么应该忽略CHECKOUT_SESSION_ID

在构建SaaS应用时,Stripe支付集成是每个开发者都会遇到的挑战。面对Stripe复杂的webhook系统和容易出错的CHECKOUT_SESSION_ID机制,很多开发者感到头疼不已。本文将分享一个经过实战检验的Stripe推荐方案,帮助你在实现支付功能时保持理智。

【免费下载链接】stripe-recommendations How to implement Stripe without going mad 【免费下载链接】stripe-recommendations 项目地址: https://gitcode.com/gh_mirrors/st/stripe-recommendations

🎯 核心问题:Stripe的"分裂大脑"困境

Stripe最大的问题在于它在你代码库中引入了固有的"分裂大脑"状态。当客户完成结账时,"购买状态"存储在Stripe中,然后你需要通过webhook在自己的数据库中跟踪购买信息。

这种设计导致了:

  • 超过258种事件类型,数据量各不相同
  • 事件接收顺序无法保证
  • 部分更新和竞争条件难以处理
  • 容易出现支付失败但应用显示订阅成功的情况

💡 解决方案:单一同步函数策略

我们的核心思路很简单:创建一个单一的syncStripeDataToKV(customerId: string)函数,将所有给定Stripe客户的数据同步到你的KV存储中

推荐流程概览

  1. 前端:订阅按钮点击时调用"generate-stripe-checkout"端点
  2. 用户:在应用中点击"订阅"按钮
  3. 后端:创建Stripe客户
  4. 后端:存储Stripe的customerId与应用userId之间的绑定关系
  5. 后端:为用户创建"结账会话",返回URL设置为应用的专用/success路由
  6. 用户:完成支付、订阅,重定向回/success
  7. 前端:加载时触发后端的syncAfterSuccess函数
  8. 后端:使用userId从KV获取Stripe的customerId
  9. 后端:使用customerId调用syncStripeDataToKV
  10. 后端:在所有相关事件上调用syncStripeDataToKV

🛠️ 关键实现细节

结账流程优化

关键是确保在开始结账之前始终定义好客户。客户的临时性是一个直截了当的设计缺陷,我们不知道Stripe为什么要这样构建。

// 在创建结账会话前确保客户存在
const checkout = await stripe.checkout.sessions.create({
  customer: stripeCustomerId, // 永远使用stripeCustomerId
  success_url: "https://your-app.com/success",
  // ...其他配置
});

同步函数实现

syncStripeDataToKV函数负责将Stripe客户的所有数据同步到你的KV存储中。这个函数将在你的/success端点和/api/stripewebhook处理程序中使用。

export async function syncStripeDataToKV(customerId: string) {
  const subscriptions = await stripe.subscriptions.list({
    customer: customerId,
    limit: 1,
    status: "all",
    expand: ["data.default_payment_method"],
  });

  // 存储完整的订阅状态
  const subData = {
    subscriptionId: subscription.id,
    status: subscription.status,
    priceId: subscription.items.data[0].price.id,
    currentPeriodEnd: subscription.current_period_end,
    currentPeriodStart: subscription.current_period_start,
    cancelAtPeriodEnd: subscription.cancel_at_period_end,
    // ...其他支付信息
  };

  await kv.set(`stripe:customer:${customerId}`, subData);
  return subData;
}

🚀 为什么应该忽略CHECKOUT_SESSION_ID

你可能注意到我们没有使用任何CHECKOUT_SESSION_ID相关内容?这是因为它设计不佳,并且鼓励你实现12种不同的方式来获取Stripe状态。忽略这些诱惑,坚持使用单一的syncStripeDataToKV函数,这将让你的生活更轻松。

成功端点处理

/success端点是用户在完成结账后被重定向到的页面。虽然不是"必需"的,但你的用户很可能会在webhooks之前回到你的网站。这是一个难以处理的竞争条件。急切地调用syncStripeDataToKV将防止你可能最终陷入的任何奇怪状态。

export async function GET(req: Request) {
  const user = auth(req);
  const stripeCustomerId = await kv.get(`stripe:user:${user.id}`);
  
  if (!stripeCustomerId) {
    return redirect("/");
  }

  await syncStripeDataToKV(stripeCustomerId);
  return redirect("/");
}

📋 需要跟踪的事件

为了确保订阅状态的准确性,我们需要跟踪以下关键事件:

const allowedEvents: Stripe.Event.Type[] = [
  "checkout.session.completed",
  "customer.subscription.created",
  "customer.subscription.updated",
  "customer.subscription.deleted",
  "invoice.paid",
  "invoice.payment_failed",
  "payment_intent.succeeded",
  "payment_intent.payment_failed",
  // ...其他相关事件
];

🎪 专业提示

禁用"Cash App Pay"

我确信这几乎只被不良用户使用。超过90%的取消交易都是通过Cash App Pay进行的。

启用"限制客户只能有一个订阅"

这是一个非常有用的隐藏设置,为我省去了很多麻烦和竞争条件。有趣的事实:这是防止用户在打开两个结账会话时能够结账两次的唯一方法。

💪 实施优势

采用这种Stripe推荐方案的优势:

  • 简化代码:单一同步函数替代复杂的webhook处理
  • 减少竞争条件:避免支付状态不一致的问题
  • 提高可靠性:确保应用状态与Stripe状态同步
  • 易于维护:统一的处理逻辑让调试和扩展更简单

🎯 总结

这个Stripe支付集成方案经过了多次实战检验,是目前最简单的Stripe设置之一。虽然看起来步骤很多,但每一步都是必要的。跳过任何步骤都会让实现变得不必要地困难。

记住:坚持使用单一的syncStripeDataToKV函数,忽略CHECKOUT_SESSION_ID的诱惑,你将能够构建出更可靠、更易维护的支付系统。

无论你选择完全复制这个实现还是从中汲取灵感,我都希望你能在这个文档中找到一些价值。祝你在Stripe集成的道路上保持理智!🚀

【免费下载链接】stripe-recommendations How to implement Stripe without going mad 【免费下载链接】stripe-recommendations 项目地址: https://gitcode.com/gh_mirrors/st/stripe-recommendations

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值