siteConfig.payment.provider 里选一个,
配上对应的环境变量,业务代码继续用同一份 paymentManager API 即可。
所有 Provider 的 webhook 都会被归一化成同一个 NormalizedEvent 结构,
所以发放积分、签发 license、记录分销佣金这类业务代码只用写一次,
跟收钱的是哪家无关。
PaymentManager 的工作方式
门面在 src/payment/index.ts。它在启动时根据 siteConfig.payment.provider
选定当前 Provider:
src/payment/types.ts 里同一份双方法契约:
createCheckout(opts) 和 createPortalLink(opts)。调用方(server action、
设置页等)永远只 import paymentManager,根本不知道当前是哪家。
怎么选
| Provider | 最适合 | 费率 | 是否 MoR | 备注 |
|---|---|---|---|---|
| Stripe | 北美/欧洲/全球信用卡 | 2.9% + 30¢ | 否 | 增值税/销售税自己处理。开发体验最好。 |
| Paddle | 全球、合规要求高 | ~5% + 50¢ | 是 | 替你处理欧盟 VAT 和美国销售税。 |
| Lemon Squeezy | 个人开发者想要 MoR | 5% + 50¢ | 是 | 注册门槛最低。已被 Stripe 收购。 |
| Creem | 中国大陆用户 | 浮动 | 是(境内) | 支持支付宝 + 微信支付。需要中国公司主体。 |
| NOWPayments | crypto 原生买家 | 0.5% | 否 | BTC/ETH/USDC 等 300+ 币种,无 KYC。仅支持一次性付款,不支持订阅。 |
Webhook 路由
每个 Provider 在app/api/webhooks/ 下都有独立路由:
| Provider | URL |
|---|---|
| Stripe | /api/webhooks/stripe |
| Paddle | /api/webhooks/paddle |
| Lemon Squeezy | /api/webhooks/lemonsqueezy |
| Creem | /api/webhooks/creem |
| NOWPayments | /api/webhooks/nowpayments |
统一事件结构
每个 Provider 的parseWebhook 都会输出 src/payment/types.ts 里的
NormalizedEvent:
src/payment/handlers/core.ts 里的 processNormalizedEvent 只读归一化字段。
要新增一个 Provider,只需要实现 createCheckout、createPortalLink、一个
normalize*Event 函数即可,业务逻辑零重复。
幂等
Stripe(其他几家也一样)只要响应不是 2xx 就会重投 webhook,所以核心 handler 对payment.invoiceId(src/db/app.schema.ts 里的唯一索引)做幂等,没有
invoice 的回退到 payment.sessionId:
payment
行并发放积分。
验证
- 把
siteConfig.payment.provider设成想用的 Provider。 - 配好对应的环境变量(见各 Provider 的子文档)。
pnpm dev,打开/pricing,点结账 —— 应该跳到 Provider 的托管收银台。- 用测试卡完成支付。
- Provider 的 webhook 会打到你的路由;查一下 Postgres:
- 重发 webhook,应该看到同一行数据,不会有重复。
常见坑
- 半路换 Provider 不清理在途订单 —— 已发出的 checkout 在新 Provider 下 会 404。先手工 cancel 一遍再切。
- 忘记按 Provider 配
priceIdEnv—— 当前 Provider 下的 promo 和 standard env 都得能解析出来,哪怕值是空字符串。 - 本地开发没有公网隧道 —— webhook 到不了
localhost。用 Stripe CLI、 ngrok 或 cloudflared。 - 测试与正式的 key 混用 —— 每家 Provider 的测试 / 正式 key 是分开的, webhook 签名密钥也是分开的,对不上就过不了校验。
- 解析报错就直接 return —— Provider 会重投,等你修好 bug 后会重复发 积分。任何路径都要做幂等写入。
官方文档
- Stripe:stripe.com/docs
- Paddle:developer.paddle.com
- Lemon Squeezy:docs.lemonsqueezy.com
- Creem:creem.io/docs
- NOWPayments:nowpayments.io · API 参考