Stripe 集成与配置
使用 Stripe Checkout 完成支付,并通过 Webhook 最终写入积分。讲解环境变量配置、会话创建、回调处理以及签名 Webhook 的验证与处理。
总览
本模板使用 Stripe Checkout 完成支付,并在成功后将积分写入账本。
- 页面:
/[locale]/pricing
从 TypeScript 配置加载套餐。 - 创建会话:客户端
POST /api/checkout
,随后跳转到 Stripe。 - 返回站点:成功后跳转到
/api/pay/callback/stripe
(GET)。 - 最终入账:Stripe 通过 Webhook 调用
/api/pay/webhook/stripe
(POST),将订单记为已支付并增加积分。
注意:Webhook 才是最终可信来源;回调仅用于页面跳转。
快速开始资源
- Stripe:入门 — https://docs.stripe.com/get-started
支付网关 101
什么是支付网关?
支付网关是用于处理和授权电子支付(银行卡、数字钱包、银行转账等)的在线中介服务,相当于线下读卡器的“在线版本”。它负责在互联网上安全地校验、批准或拒绝交易。
到 2025 年,全球电商销售额预计接近 7.4 万亿美元。要抓住增长红利,商家需要提供顺畅、安全的结账体验,以提升转化并建立客户信任。
工作原理(概览)
- 客户下单:在结账页输入支付信息。
- 加密与传输:站点加密信息并发送至支付网关。
- 转发交易:网关将加密信息转给支付处理方。
- 与发卡行通讯:处理方将交易明细发给发卡行/银行进行授权。
- 结果返回:银行基于资金与风控审核,批准或拒绝。
- 回传链路:结果经由处理方返回到支付网关。
- 完成交易:若批准,用户收到成功确认;若拒绝,提示失败原因。
- 资金结算:通过收单行进行清算后,资金入账到商户银行账户(具体时间取决于合作方设置)。
整个过程自动化完成,通常只需数秒。
如何将支付网关集成到网站
- 选择网关:考虑手续费、支持的支付方式、安全性与平台兼容性。
- 商户账户:部分网关(如 Stripe)已内置商户功能;也有需要单独申请的。
- 获取 API Key:用于让你的网站/应用与网关安全通信。
- 完成集成:可用插件(Shopify/WooCommerce)或自定义代码。Stripe 的 API 对开发者十分友好。
- 沙箱测试:在测试环境全面验证支付流程。
- 上线:切换到正式密钥并开始监控。
接口说明
/api/checkout
(POST):校验src/data/pricing.ts
的计划,创建 Checkout 会话,保存订单created
。/api/pay/callback/stripe
(GET):Stripe 成功页的跳转目标;不再做入账逻辑。/api/pay/webhook/stripe
(POST):校验签名,处理checkout.session.completed
,完成入账与积分增加。
积分流程
- 从 Session 的 metadata 读取
order_no
。 - 更新订单状态为
paid
并记录支付详情。 - 在
credits
表插入正数记录(order_pay
)。
必要环境变量
STRIPE_PRIVATE_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_WEB_URL=http://localhost:3000
NEXT_PUBLIC_PAY_CANCEL_URL=/pricing
NEXT_PUBLIC_PAY_SUCCESS_URL=/pricing
NEXT_PUBLIC_PAY_FAIL_URL=/pricing
本地 Webhook 调试
stripe login
stripe listen --forward-to localhost:3000/api/pay/webhook/stripe
# 复制输出的 whsec 到 .env
stripe trigger checkout_session_completed
提示:触发 payment_intent.succeeded
不会被当前实现处理,建议使用 checkout_session_completed
。
试运行
- 注册并登录。
- 访问
/[locale]/pricing
选择任一套餐。 - 使用测试卡
4242 4242 4242 4242
完成支付。 - Webhook 会完成订单入账并增加积分。
- 在
/[locale]/credits-test
验证余额变化。
Stripe 集成指南(高阶概览)
遵循以下步骤即可快速把 Stripe 集成到你的网站:
- 创建 Stripe 账户:填写企业信息与结算银行账户。
- 获取 API 密钥:在 Dashboard → Developers 查看 publishable/secret key。
- 安装 SDK:使用 Stripe 官方库(JavaScript/TypeScript、Ruby、Python 等)。
- 前端集成:使用 Stripe.js 与内置 UI(Elements/Payment Element)或平台插件,安全采集卡片信息。
- 服务端端点:用 secret key 初始化 Stripe,提供创建 PaymentIntent 或 Checkout Session 的接口。
- 采集支付信息:尽量不在你的服务器保存原始卡数据,交给 Stripe 组件处理。
- 提交到服务端:通过 HTTPS 安全传输支付意图与必要参数。
- 服务端处理:使用 SDK 创建 PaymentIntent/Checkout Session。
- 处理结果:更新数据库并将成功/失败反馈给前端。
- 错误与边界:考虑余额不足、卡片过期、3DS 验证、重试等情况。
- 全面测试:使用测试模式、Webhook 与测试卡模拟各种场景。
- 上线:替换为正式密钥,开启所需支付方式并持续监控。
提示:从官方“入门”文档开始 — https://docs.stripe.com/get-started
实现细节
- 套餐定义:
src/data/pricing.ts
(强类型、前后端共用)。 handleCheckoutSession
同时支持一次性与订阅(订阅场景从最新账单获取 PaymentIntent)。- 可扩展处理
invoice.payment_succeeded
实现订阅续费时自动充值积分。
产品与价格(Stripe)
Stripe 将“售卖的内容”和“定价方式”分离:
- 产品(
prod_...
):名称/描述、目录与会计元数据;一个产品可以对应多条价格。 - 价格(
price_...
):币种、金额、是否按月/按年循环、试用、税务行为、分级等。订阅绑定到价格。
为何在 Checkout 使用价格 ID(Price ID)
- 订阅需要明确的计费条款。绑定到
price_...
才能启用续费、按比例结算(proration)、试用和 Stripe Tax。 - 续费账单行会引用同一
price_...
,Webhook 能据此映射到计划并在每个周期自动发放积分。 - 比“临时内联价格”更安全:把金额与规则放在 Stripe 管理,代码保持稳定。
如何在 Stripe 找到价格 ID
- 仪表盘 → 产品 → 选择你的产品。
- 在“价格(Prices)”表中,点击该行末尾的省略号(三点) → 复制价格 ID。
- 价格 ID 以
price_...
开头;不要与产品 ID(prod_...
)混淆。
环境变量(可选但推荐)
# 把计划配置映射到 Stripe 的价格;留空则回退为内联 price_data
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY=price_...
# 可选人民币价格
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY_CNY=price_...
有/无价格 ID 的行为
- 若订阅计划配置了 Price ID,Checkout 将直接引用该
price_...
。 - 若未配置,Checkout 会使用
src/data/pricing.ts
的金额通过内联price_data
创建会话(安全回退)。
续费与积分
模板已支持订阅自动续费时的积分发放:
- Webhook 处理
invoice.payment_succeeded
,当且仅当billing_reason=subscription_cycle
时:- 创建当期的续费订单(Order)。
- 根据计划发放当期积分,过期时间对齐账单周期(+24h 宽限)。
- 幂等:若同一
(subscription_id, period_start)
已有订单,则跳过。 - 首次购买仍由
checkout.session.completed
处理;为避免重复发放,非续费账单会被忽略。
提醒:本地测试记得转发 Webhook
本地或非生产环境测试时,务必将 Stripe 事件转发到你的后端,否则支付虽然成功,但订单与积分不会最终入账:
stripe listen --forward-to localhost:3000/api/pay/webhook/stripe
如果使用外网域名或隧道,请将 forward-to
换成对应地址。
计费与订阅
客户门户(Customer Portal)
在 Stripe 后台启用 Customer Portal,并配置可用功能(取消/恢复、更新卡片、查看发票等)。
本模板提供:
- API:
GET /api/billing/portal
— 创建门户会话并 302 跳转到 Stripe - UI:
/:locale/account/billing
— “Manage billing” 按钮打开门户
实现位置:
- API 路由:
src/app/api/billing/portal/route.ts
- 通过登录用户的邮箱查找或创建 Stripe Customer
- 使用
return_url
返回到/:locale/account/billing
页面(Server Component):
src/app/[locale]/account/billing/page.tsx
链接到/api/billing/portal?locale=<locale>
催款(支付失败)
Webhook invoice.payment_failed
:通过 Resend 发送“更新付款方式”的提示邮件。
- 模板:
src/services/email/templates/payment-failed.tsx
- 发送函数:
sendPaymentFailedEmail()
(src/services/email/send.ts
) - Webhook:
src/app/api/pay/webhook/stripe/route.ts
本地测试(Stripe CLI):
stripe trigger invoice_payment_failed
说明:邮件中包含 /:locale/account/billing
链接,用户可从你的应用打开门户。
其他支付选项
Stripe 只是一个选择。如果你需要加密货币支付或 PayPal 等替代方案,欢迎联系我——我可以根据你的技术栈与目标市场,协助评估并完成对接集成。