其他
新建 Dodo Payments 支付集成时:完成 SDK…
从零开始接入 Dodo Payments,理解 MoR 架构并正确实现 checkout、webhook 验证与授权。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
新建 Dodo Payments 支付集特定
解决思路
- 采用 Dodo Payments 的 Merchant of Record 模式:不要构建销售税引擎或争议处理系统;由 Dodo 作为每笔交易的法律卖方,处理销售税登记/计算/缴纳和卡组织争议/拒付;你只创建 checkout sessions、监听 webhooks,并在支付成功后授予访问。
- 环境 Base URL 只用 Live `https://live.dodopayments.com` 或 Test `https://test.dodopayments.com`;绝不要使用 `api.dodopayments.com`,它没有 DNS 记录且不可达。
- API Key 格式:Test 为 `dodo_test_...`,Live 为 `dodo_live_...`。
- 设置环境变量:`DODO_PAYMENTS_API_KEY` 用于 API 请求的 Bearer token;`DODO_PAYMENTS_WEBHOOK_KEY` 用于 webhook 签名验证密钥。
- `environment` 参数省略时默认 `live_mode`;开发时始终显式传 `test_mode`。
- TypeScript/Node.js 安装:运行 `npm install dodopayments`。
- TypeScript/Node.js 初始化:`import DodoPayments from 'dodopayments'; const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode' });`。
- TypeScript/Node.js 构造选项:`bearerToken` 默认读环境变量 `DODO_PAYMENTS_API_KEY`,API 调用必需;`environment` 为 `'test_mode' | 'live_mode'` 默认 `'live_mode'`,开发显式设置;`webhookKey` 默认读 `DODO_PAYMENTS_WEBHOOK_KEY`,用于 `webhooks.unwrap()`;`baseURL` 可覆盖,且与 `environment` 互斥。
- Python 安装:运行 `pip install dodopayments`。
- Python 初始化:`import os; from dodopayments import DodoPayments; client = DodoPayments(bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"), environment="test_mode")`。
- Go 安装:运行 `go get -u github.com/dodopayments/dodopayments-go@v1.110.0`。
- Go 初始化:导入 `github.com/dodopayments/dodopayments-go` 和 `github.com/dodopayments/dodopayments-go/option`;使用 `client := dodopayments.NewClient(option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")), option.WithEnvironmentTestMode())`。
- PHP 安装:运行 `composer require "dodopayments/client:6.19.0"`。
- PHP 初始化:`use Dodopayments\Client; $client = new Client(bearerToken: getenv('DODO_PAYMENTS_API_KEY') ?: 'My Bearer Token', environment: 'test_mode');`。
- Ruby 安装:在 Gemfile 加 `gem "dodopayments", "~> 2.22.0"`。
- Ruby 初始化:`dodo_payments = Dodopayments::Client.new(bearer_token: ENV["DODO_PAYMENTS_API_KEY"], environment: "test_mode")`。
- Java 安装:Maven 依赖 `com.dodopayments.api:dodo-payments-java:1.110.0`。
- Java 初始化:`DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();` 会读取 `DODO_PAYMENTS_API_KEY`、`DODO_PAYMENTS_WEBHOOK_KEY`、`DODO_PAYMENTS_BASE_URL`。
- Kotlin 安装:Maven 依赖 `com.dodopayments.api:dodo-payments-kotlin:1.110.0`。
- Kotlin 初始化:`val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()`。
- 框架适配器选择:按栈使用 `@dodopayments/*` 包:Next.js 用 `@dodopayments/nextjs`,Nuxt 用 `@dodopayments/nuxt`,Express 用 `@dodopayments/express`,Fastify 用 `@dodopayments/fastify`,Hono 用 `@dodopayments/hono`,Astro 用 `@dodopayments/astro`,SvelteKit 用 `@dodopayments/sveltekit`,Remix 用 `@dodopayments/remix`,TanStack Start 用 `@dodopayments/tanstack`,Better Auth 用 `@dodopayments/better-auth`,Convex 用 `@dodopayments/convex`,Bun 用 `@dodopayments/bun`;每个适配器提供 checkout、customer portal 和 webhook handlers。
- Products:在 dashboard 或 API 创建要售卖的 item,并选择类型:One-time 单次购买、Subscription 循环计费、Usage-based 按量计费。
- Customers:表示买家;在 checkout 时显式创建或隐式创建,可关联多个 payment methods、subscriptions 和 credit balances。
- Checkout Sessions:服务端创建 session,重定向客户到 hosted checkout URL,并监听 webhooks 确认支付;详细 checkout 配置见 `checkout-integration` 技能。
- Subscriptions:通过 checkout sessions 或 subscriptions API 管理循环扣费;生命周期、试用、计划变更、按需扣费见 `subscription-integration` 技能。
- Webhooks:接收实时事件,如 `payment.succeeded`、`subscription.active`、`refund.succeeded`、`credit.deducted`;签名验证见 `webhook-integration` 技能。
- Credit Entitlements:给产品附加虚拟余额(API calls、tokens、compute hours);按产品配置 rollover、overage、expiration 规则;见 `credit-based-billing` 技能。
- Canonical flow 第 1 步:在服务端创建 checkout session,包含 product ID 和 customer email。
- Canonical flow 第 2 步:将客户重定向到返回的 `checkout_url`。
- Canonical flow 第 3 步:客户在 hosted checkout 上支付。
- Canonical flow 第 4 步:Dodo 向你的端点发送 webhook,例如 `payment.succeeded`。
- Canonical flow 第 5 步:使用 `client.webhooks.unwrap()` 验证 webhook 签名。
- Canonical flow 第 6 步:仅在 webhook 验证成功后授予访问权限。
- 绝不只根据浏览器 `return_url` 重定向授予访问;webhook 才是权威确认。
- 示例:创建 session 使用 `client.checkoutSessions.create({ product_cart: [{ product_id: 'pdt_example', quantity: 1 }], customer: { email: 'customer@example.com' }, return_url: 'https://yoursite.com/success' })`,然后重定向到 `session.checkout_url`。
- 示例 webhook endpoint:用 `express.raw({ type: 'application/json' })` 保留原始请求体;从 headers 取 `webhook-id`、`webhook-signature`、`webhook-timestamp`;调用 `client.webhooks.unwrap(req.body.toString(), { headers: { 'webhook-id': webhookId, 'webhook-signature': ..., 'webhook-timestamp': ... } })`。
- 示例 webhook 处理:用 `processWebhookOnce(webhookId, async () => { ... })` 通过原子唯一插入抑制重复,并在同一数据库事务中执行副作用;`payment.succeeded` 只给一次性购买授予访问(订阅访问从 `subscription.active` 开始);`subscription.active` 授予订阅访问;响应 `{ received: true, duplicate: !handled }`;验证失败返回 401 `{ error: 'Invalid signature' }`。
- API 认证:所有请求使用 Bearer token 认证,header 为 `Authorization: Bearer dodo_live_...`。
- API 分页:list endpoints 使用页码分页,接受 `page_size` 和 `page_number`,响应行在 `items`;例如 `client.payments.list({ page_size: 50, page_number: 0 })` 后遍历 `payments.items`。
- API 分页自动遍历:使用 `for await (const payment of client.payments.list()) { console.log(payment.payment_id); }` 让 SDK 遍历每一页。
- 速率限制:Dodo 强制 rate limits;SDK 会自动重试 `429` 响应。
- SDK 错误:捕获并检查类型化错误,例如 `if (error instanceof DodoPayments.APIError) { console.error(error.status, error.message); }`。
- 重试:SDK 默认重试两次,对连接错误和 `408`、`409`、`429`、`5xx` 响应使用短指数退避;不要添加无条件自定义重试循环。
- 重试覆盖:构造 client 时设置 `maxRetries: 0` 覆盖所有请求,或单请求传 `{ maxRetries: 0 }` 覆盖一次请求。
- Webhook 验证强制:必须验证签名,绝不在未验证时信任 payload。
- Webhook 签名规范:Dodo 实现 Standard Webhooks spec;签名消息是 `webhook-id.webhook-timestamp.raw_body`(用点连接),使用 HMAC-SHA256,base64 编码。
- Webhook 验证做法:使用 SDK helper `client.webhooks.unwrap(req.body.toString(), { headers: { 'webhook-id': ..., 'webhook-signature': ..., 'webhook-timestamp': ... } })`;它会验证签名并解析 payload,失败时抛错。
- 常见错误 1:不要在 `/success` 等 `return_url` 重定向中授予访问;应等待 webhook 并验证后授予。
- 常见错误 2:不要手写 HMAC 验证;使用 `client.webhooks.unwrap()`;旧式只签 payload 是错的,因为 Standard Webhooks 签 `webhook-id.webhook-timestamp.raw_body`,手写 HMAC 永远不匹配。
- 常见错误 3:不要重新序列化请求体;验证需要精确 raw body;不要 `JSON.parse(req.body)` 后 `JSON.stringify(body)`,应使用 `req.body.toString()`。
- 常见错误 4:不要用已弃用 API:`client.payments.create()` 和 `client.subscriptions.create()`;新集成使用 `client.checkoutSessions.create()`。
- 常见错误 5:不要忘记设置 `environment: 'test_mode'`;默认是 `live_mode`,开发时显式传 `test_mode` 避免扣真实卡。
- 常见错误 6:不要把 API key 硬编码在代码中;始终使用环境变量。
- 常见错误 7:不要忽略 `webhook-timestamp` header;它防止重放攻击;`client.webhooks.unwrap()` 自动验证,手写验证需检查时间戳在最近几分钟内。
- 常见错误 8:不要在生产使用 `unsafeUnwrap()`;它跳过签名验证;仅用于 `dodo wh trigger` 的无签名测试 payload。
- 需要进一步细节时查阅资源:Dodo Payments Docs `https://docs.dodopayments.com`、API Reference、SDK Repositories、Checkout Sessions Guide、Webhook Guide、Customer Portal、Subscription Integration、Credit-Based Billing。
适用边界 · 注意事项
- 不适用于非 Dodo Payments 的支付集成或不需要 Merchant of Record 的项目。
- 不适用于需要深入实现订阅生命周期、信用计费或详细 webhook 事件配置的场景,这些应分别参考 subscription-integration、credit-based-billing、webhook-integration 技能。
- 不适用于无法接收 webhook 或无法保留原始请求体的服务端环境,因为支付确认必须依赖 webhook 验证。