支付
Next.js 模板的提供商无关结账与 Webhook —— Creem 或 Stripe。
支付是一套可在其上构建的端到端计费流程。模板在同一套接口背后内置了两个可互换的 提供商 —— Creem 与 Stripe,通过单个环境变量切换。结账、订单与计费页面、 订阅状态,以及提供商 webhook(含订阅生命周期与退款)都基于真实数据运行。
选择一个提供商
架构
一个小型注册表解析当前提供商;路由与定价 UI 从不直接导入某个提供商。
| 文件 | 作用 |
|---|---|
src/config/payment-config.ts | 提供商选择(getPaymentProvider)与 env 驱动的产品 ID(getPaymentProducts) |
src/lib/payment/index.ts | 提供商注册表 + getProvider()(实例记忆化) |
src/lib/payment/types.ts | PaymentProvider 接口与共享的参数/结果类型 |
src/lib/payment/providers/creem.ts | Creem 实现 |
src/lib/payment/providers/stripe.ts | Stripe 实现 |
src/lib/payment/order-no.ts | 订单号生成(buildOrderNo) |
src/app/api/payment/create/route.ts | 已认证的结账创建 |
src/app/api/payment/notify/[provider]/route.ts | 所有提供商共用的单一 webhook 接收端 |
src/app/api/payment/portal/route.ts | 自助计费门户(取决于提供商) |
src/app/api/payment/query/route.ts | 按 orderId 查询订单状态 |
src/lib/db/schema/payment.ts | payment 与 subscription 表 |
每个提供商都实现同一个接口:
interface PaymentProvider {
readonly type: PaymentProviderType;
createCheckout(params: CreateCheckoutParams): Promise<CreateCheckoutResult>;
handleWebhook(request: WebhookRequest): Promise<void>;
createPortal?(customerId: string, returnUrl: string): Promise<string>;
}切换提供商
设置提供商,并提供该提供商的产品/价格 ID:
# creem(默认)或 stripe
NEXT_PUBLIC_PAYMENT_PROVIDER=stripe当提供商为 stripe 时,getPaymentProducts() 读取 NEXT_PUBLIC_STRIPE_PRICE_*
ID,否则读取 NEXT_PUBLIC_CREEM_PRODUCT_* ID。展示价格位于
src/config/price-config.ts,与提供商实际收费相互独立 —— 需你自行保持一致。见
环境变量。
共享流程
定价 CTA → 创建结账
POST /api/payment/create 要求已认证会话,用 Zod 校验 { productId },解析产品,
生成订单号,插入一条 payment 行(status: paying、provider: <当前>),随后调用
当前提供商的 createCheckout。返回 { orderNo, checkoutUrl }。
跳转到托管结账
浏览器被导向 checkoutUrl。成功 URL 为 {origin}/pay-success?orderId={orderNo};
取消则返回 {origin}/#pricing。
成功页 → 查询订单
/pay-success 可调用 GET /api/payment/query?orderId=...,从 payment 表读取
订单状态。
Webhook → 更新数据表
提供商调用 POST /api/payment/notify/{provider}。路由按路径段查找提供商并调用其
handleWebhook,后者校验签名(失败返回 400 而非 500),并更新
payment / subscription 表。
数据模型
两个提供商写入相同的数据表。subscription 行是用户唯一的权益来源;provider 记录其
归属,以便 /api/payment/portal 将计费门户路由到正确的 SDK。
| 列 | 说明 |
|---|---|
payment.provider / subscription.provider | creem | stripe(枚举还允许 zpay、polar) |
payment.thirdOrderNo | 提供商结账/会话 ID |
subscription.customerId | 提供商客户 ID —— 计费门户必需 |
subscription.subscriptionId | 提供商订阅 ID(一次性/终身为 null) |
subscription.productType | month | year | one_time |
新增一个提供商
- 在
payment-config.ts的paymentProviders中添加其值。 - 在
src/lib/payment/providers/中实现PaymentProvider接口。 - 在
src/lib/payment/index.ts的注册表中注册该类。
无需改动任何路由或调用方 —— [provider] webhook 路由与 getProvider() 会自动识别它。
已接入的部分
- 订单页(
account/order)列出用户真实的payment历史。 - 订阅页(
account/subscription)展示来自subscription表的实时套餐、状态、 周期与取消情况。 - 计费页(
setting/billing)渲染套餐概要与计费历史,并将 Stripe 客户链接到 Billing Portal 以管理支付方式、发票与取消。 GET /api/payment/query与/api/payment/orders均已认证并限定到当前登录用户。
