Stripe

Nuxt 模板的 Stripe Checkout、订阅、终身与计费门户。

Stripeserver/payment/providers/stripe-provider.ts 中实现 PaymentProvider 接口,并在 NUXT_STRIPE_API_KEY 设置时由工厂注册。它使用 Stripe Checkout 处理周期性方案与一次性(终身)购买, 通过 webhook 同步订阅,并提供 Billing Portal

配置

变量用途
NUXT_PUBLIC_PAYMENT_PROVIDER设为 stripe 以选择该提供商。
NUXT_STRIPE_API_KEY密钥/受限密钥(sk_...rk_...);同时决定是否注册。
NUXT_STRIPE_WEBHOOK_SECRET用于校验 webhook 签名的签名密钥(whsec_...)。
NUXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY / _PRO_YEARLY / _LIFETIME三个方案的公开 Price ID(price_...)。

Stripe 的取值是 Price IDprice_...),不是 Product ID。客户端固定 apiVersion: "2026-05-27.dahlia"

环境变量

结账

createPayment 将方案的 productType 映射为一种 Checkout 模式:

productType模式结果
month / yearsubscription周期性订阅
one_timepayment一次性终身购买

它将 metadataorderNoproductIdproductTypeuserId)附加到会话 —— 对于订阅,还附加到 subscription_data。若用户已有 Stripe customerId,会话会复用它; 否则 Stripe 会基于 customer_email 创建客户。返回 { orderNo, thirdOrderNo, checkoutUrl }

Webhook

  • 路径: POST /api/payment/notify/stripenotify/stripe.post.ts)。
  • 校验: 路由读取原始请求体与 stripe-signature 请求头,调用 constructStripeEvent(使用 NUXT_STRIPE_WEBHOOK_SECRET)。缺少签名返回 401; 无效返回 400。校验通过的事件交给 handleStripeEvent 分发。
  • 已处理事件:
事件效果
checkout.session.completed / checkout.session.async_payment_succeeded若已支付,将 payment 行标记为 successpayment 模式下授予终身权益。
checkout.session.async_payment_failedpayment 行标记为 failed
checkout.session.expiredpayment 行标记为 closed
customer.subscription.created / updated / deletedupsert 该用户的 subscription 行(状态、周期、试用、取消时间戳)。
invoice.paid在匹配的订阅上写入 lastPaidAt
charge.refunded / refund.created将匹配的 payment 行标记为 refunded

订阅同步

syncStripeSubscription 将 Stripe 状态映射到本地枚举 (active / trialing / canceled / expired / unpaid),从价格周期推导 monthyear,并写入周期窗口。

每个用户只有一行权益。一个守卫可防止旧的周期性订阅的迟到事件覆盖 one_time (终身)购买。若你自定义同步逻辑,请保留该守卫。

终身购买

对于 payment 模式的结账,syncLifetimeAccess 写入一行 one_time 订阅, status: activeperiodEnd 设为极远的未来,使用户在没有 Stripe 订阅对象的情况下 仍拥有永久访问权。

计费门户

createStripePortal 打开一个 Stripe Billing Portal 会话。 POST /api/payment/portalportal.post.ts)读取用户的 subscription.customerId 并返回门户 URL —— 但仅当存储的 subscription.providerstripe 时;否则返回 400ManageBillingButton.vue 组件链接到它。

本地测试

使用 Stripe CLI 将事件转发到你的开发服务器 (nuxt dev 运行在端口 3000):

stripe listen --forward-to localhost:3000/api/payment/notify/stripe

CLI 会打印一个 whsec_... 密钥 —— 将其设为 NUXT_STRIPE_WEBHOOK_SECRET。随后用 测试卡(如 4242 4242 4242 4242)完成一次结账, 确认 payment 行翻转为 success

生产注意事项

  • 使用正式密钥与正式 Price ID。
  • /api/payment/notify/stripe 注册 webhook 端点并订阅上述事件;将其签名密钥 复制到 NUXT_STRIPE_WEBHOOK_SECRET
  • 增加幂等 —— Stripe 会重试投递。
  • 优先使用范围限定到 Checkout、Customers、Subscriptions 与 Billing Portal 的 受限密钥rk_...)。

验证

  • NUXT_PUBLIC_PAYMENT_PROVIDER=stripe 时,定价 CTA 打开 Stripe Checkout。
  • 订阅购买会创建一行 activesubscription,且周期正确。
  • 终身(一次性)购买会创建一行 one_timeperiodEnd 为极远未来。
  • “管理计费”可打开 Stripe Billing Portal。
  • 无效的 webhook 签名返回 400

On this page