其他
用 Dodo Payments 实现订阅计费时:处理试用…
在 Dodo Payments 上构建带试用、计划变更、按需扣费和失败恢复的循环订阅计费系统。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
用 Dodo Payments 实现订阅特定
解决思路
- 通过 Checkout Session 创建订阅:调用 client.checkoutSessions.create,传入 product_cart(含 product_id 和 quantity)、customer(email/name 或 customer_id)、return_url,可选 subscription_data.trial_period_days 设置试用天数,然后把用户重定向到 session.checkout_url;成功后 Dodo 在服务端创建订阅并触发 subscription.active webhook。
- 添加附加项(add-ons):在 product_cart 条目的 addons 数组中传入 { addon_id, quantity }。
- 理解订阅生命周期状态:pending(创建中,→active/failed)、active(正常续费,→on_hold/cancelled/expired)、on_hold(续费或改套餐扣款失败,可恢复,→active/failed/cancelled)、cancelled(不再续费,→expired)、failed(首次授权/扣款失败,终态)、expired(订阅期结束,终态);试用中的订阅状态仍为 active,subscription.renewed 是事件而非状态。
- 检索订阅:调用 client.subscriptions.retrieve('sub_xxxxx'),读取 status 和 next_billing_date。
- 更新支付方式:当订阅因扣款失败进入 on_hold 时,调用 client.subscriptions.updatePaymentMethod('sub_xxxxx', { payment_method: { type: 'existing', payment_method_id: 'pm_new_method' } }),会自动扣取欠款,成功后依次触发 payment.succeeded 和 subscription.active。
- 查询计量订阅用量历史:调用 client.subscriptions.retrieveUsageHistory('sub_xxxxx', { page_size: 50, page_number: 0 })。
- 查询信用额度使用:调用 client.subscriptions.retrieveCreditUsage('sub_xxxxx'),遍历 items 读取 credit_entitlement_name 和 balance(balance 是字符串)。
- 变更套餐:调用 client.subscriptions.changePlan('sub_xxxxx', { product_id, quantity, proration_billing_mode, on_payment_failure }),必须指定 proration_billing_mode。
- 选择按比例计费模式:prorated_immediately(按时间比例扣费/抵扣,计费日重置为变更日)、difference_immediately(升级收全额新套餐费,降级差额转为信用,计费日重置)、full_immediately(升级收全额,降级也收全额且不抵扣,计费日重置)、do_not_bill(不扣费不抵扣,保留原计费日)。
- 选择扣款失败处理方式:on_payment_failure 设为 prevent_change(扣款失败则保留旧套餐)或 apply_change(即使扣款失败也应用新套餐,订阅可能变为 on_hold)。
- 预览套餐变更:调用 client.subscriptions.previewChangePlan('sub_xxxxx', { product_id, quantity, proration_billing_mode }),读取 immediate_charge.effective_at、immediate_charge.line_items、immediate_charge.summary 和 new_plan 向客户展示报价。
- 取消已排期的套餐变更:调用 client.subscriptions.cancelChangePlan('sub_xxxxx')。
- 立即取消订阅:调用 client.subscriptions.update('sub_xxxxx', { status: 'cancelled' }),访问权限立即撤销。
- 在计费周期末取消:调用 client.subscriptions.update('sub_xxxxx', { cancel_at_next_billing_date: true }),订阅保持 active 直到下个计费日再转为 expired;subscription.cancelled webhook 会带 cancel_at_next_billing_date: true 和 next_billing_date,据此安排权限撤销。
- 创建按需(off-session)扣费订阅:在 checkoutSessions.create 的 subscription_data 中传入 on_demand: { mandate_only: true },创建带授权但不自动续费的订阅,由你决定何时扣款。
- 执行按需扣费:调用 client.subscriptions.charge('sub_xxxxx', { product_price: 2500, product_currency: 'USD', product_description: '...' }),金额以最小货币单位表示(USD 为分,INR 为 paise);Dodo 不会自动重试按需扣费,需自行监听 payment.failed webhook 并实现重试逻辑和拒付过滤。
- 处理 on_hold 恢复:监听 subscription.on_hold webhook 通知客户并引导更新支付方式;恢复方式包括在 Settings → Recovery 开启支付重试(软拒付最多重试 8 次,窗口 1–30 天可配置,默认 13 天)、客户通过 Customer Portal 更新支付方式自动扣取欠款、或服务端手动更新支付方式后调用 updatePaymentMethod()。
- 配置催款(Dunning):为 on_hold 续费和客户门户取消发送最多 4 封可配置邮件;催款耗尽不会改变订阅状态,需自行处理最终结果。
- 创建客户自助门户:调用 client.customers.customerPortal.create('cus_xxxxx', { return_url: 'https://yoursite.com/account' }) 并重定向到 portalSession.link;门户链接 24 小时后过期,客户可查看订阅详情和续费日、立即或期末取消、在启用的 Product Collections 内升降级、更新支付方式并恢复 on_hold 订阅、查看账单历史和下载发票。
- 处理订阅 webhook 事件:subscription.active(授予访问权限)、subscription.renewed(记录续费、发送收据)、subscription.on_hold(通知客户、提供恢复)、subscription.plan_changed(更新权益)、subscription.cancelled(按 cancel_at_next_billing_date 安排权限撤销)、subscription.failed(通知客户、提供重试或新建订阅)、subscription.expired(撤销访问权限)。
- 在 webhook 处理器中先校验签名并读取原始 body,用 webhook-id 做幂等:以 UNIQUE 约束做原子插入来认领 webhook-id,把认领和权益变更放在同一数据库事务中,重复事件直接返回 received: true。
- 在 webhook 中处理取消时检查 cancel_at_next_billing_date:为 true 则调用 scheduleAccessRevocation(subscription_id, next_billing_date),否则调用 revokeAccessImmediately(subscription_id)。
- 使用信用额度权益:在订阅产品上附加 credit entitlement(如每月 10,000 tokens),每次续费自动发放额度,监听 credit.added 和 credit.deducted webhook 同步账本。
- 避免在 return_url 上授予访问权限:return_url 在订阅完全创建前就会被访问,Dodo 可能仍在处理授权或支付,必须等待 subscription.active webhook 再授予权限。
- 不要使用已废弃的 client.subscriptions.create,改用 Checkout Sessions 创建订阅。
- 不要假设每个 subscription.plan_changed 都代表付费升级成功:该事件也会在降级、附加项变更以及切换 cancel_at_next_billing_date 时触发,需检查 payload 和订阅当前 product_id。
- 不要混淆 on_hold 与暂停:on_hold 表示扣款失败而非客户暂停,没有通用的暂停/恢复操作,只能通过更新支付方式或等待重试恢复。
适用边界 · 注意事项
- 不使用 Dodo Payments 作为支付/订阅服务商的项目
- 不需要循环订阅、试用、升降级或按需扣费能力的场景