Overview
On-demand subscriptions let you authorize a customer’s payment method once and then charge variable amounts whenever you need, instead of on a fixed schedule. This feature is available for all accounts—no approval required. Use this guide to:- Create an on-demand subscription (authorize a mandate with optional initial price)
- Trigger subsequent charges with custom amounts
- Track outcomes using webhooks
Prerequisites
- Dodo Payments merchant account and API key
- Webhook secret configured and an endpoint to receive events
- A subscription product in your catalog
How on-demand works
- You create a subscription with the
on_demandobject to authorize a payment method and optionally collect an initial charge. - Later, you create charges against that subscription with custom amounts using the dedicated charge endpoint.
- You listen to webhooks (e.g.,
payment.succeeded,payment.failed) to update your system.
Create an on-demand subscription
Endpoint: POST /checkouts Key request fields (body):Please find them in Create Checkout Session
Create an on-demand subscription
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
Charge an on-demand subscription
After the mandate is authorized, create charges as needed. Endpoint: POST /subscriptions/{subscription_id}/charge Key request fields (body):Charge request body parameters
Charge request body parameters
integer
bắt buộc
Amount to charge (in the smallest currency unit). Example: to charge $25.00, pass
2500.string
Optional currency override for the charge.
string
Optional description override for this charge.
boolean
If true, includes adaptive currency fees within
product_price. If false, fees are added on top.object
Chỉ định cách số dư ví của khách hàng được sử dụng để thanh toán khoản phí này.
object
Metadata bổ sung cho payment. Nếu bị bỏ qua, metadata của subscription sẽ được sử dụng.
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
Xử lý các khoản phí không thành công
Khi khoản phí đối với một subscription on-demand không thành công, bạn quyết định bước tiếp theo. Không giống các subscription theo lịch — trong đó một lần gia hạn không thành công sẽ dừng việc tính phí tự động tiếp theo — subscription on-demand vẫn có thể được tính phí sau khi không thành công. Bạn có thể gọi lại charge endpoint trong logic retry của riêng mình.Điều gì xảy ra khi không thành công
1
Charge attempt fails
Request
POST /subscriptions/{subscription_id}/charge sẽ trả về error response hoặc hoàn tất bất đồng bộ và phát ra webhook payment.failed kèm lý do bị từ chối.2
Subscription may transition to on_hold
Subscription có thể chuyển sang trạng thái
on_hold và phát ra webhook subscription.on_hold (xem Subscription States → On Hold). Đây là một tín hiệu — không phải khóa. Đối với subscription on-demand, on_hold không ngăn bạn tính phí lại.3
Retry the charge (your call)
Đối với các flow on-demand, Dodo không tự động retry. Bạn có thể gọi lại
POST /subscriptions/{subscription_id}/charge bất cứ lúc nào để retry. Hãy áp dụng safe retry policy bên dưới — sử dụng exponential backoff, bỏ qua hard decline và tránh các pattern theo đợt — để các lần retry không bị hệ thống fraud và risk của chúng tôi đánh dấu.4
Optionally, ask the customer for a new payment method
Nếu các lần retry liên tục không thành công vì chính payment method gặp vấn đề (thẻ hết hạn, tài khoản bị đóng, v.v.), hãy sử dụng
POST /subscriptions/{subscription_id}/update-payment-method để thu thập payment method mới từ khách hàng. Khi thành công, subscription sẽ trở về active và các webhook payment.succeeded, sau đó là subscription.active, sẽ được phát ra.On-demand và theo lịch: Đối với subscription theo lịch, Dodo thực hiện retry gia hạn và dunning của riêng mình. Đối với subscription on-demand, bạn chịu trách nhiệm về retry policy vì chỉ bạn biết khi nào khoản phí tiếp theo nên được thực hiện (khoản phí này phụ thuộc vào các usage event của bạn, không phải lịch).
Trình tự webhook khi khoản phí on-demand không thành công
Event 3 và 4 chỉ được phát ra sau khi một khoản phí tiếp theo thành công.
Trách nhiệm retry
Subscription Dunning — chuỗi email khôi phục tích hợp sẵn — chỉ áp dụng cho các payment gia hạn không thành công trên subscription theo lịch và các trường hợp hủy do khách hàng khởi tạo. Tính năng này không được thiết kế cho các khoản phí on-demand không thành công. Hãy trao đổi trực tiếp với khách hàng (ví dụ: qua transactional email hoặc lời nhắc trong ứng dụng) khi bạn xác định payment method cần được cập nhật.Retry payment
Hệ thống phát hiện gian lận của chúng tôi có thể chặn các pattern retry quá mức (và có thể đánh dấu chúng là hành vi card testing tiềm ẩn). Hãy tuân theo retry policy an toàn.Nguyên tắc cho retry policy an toàn
- Cơ chế backoff: Sử dụng exponential backoff giữa các lần retry.
- Giới hạn retry: Giới hạn tổng số lần retry (tối đa 3–4 lần thử).
- Lọc thông minh: Chỉ retry đối với các lỗi có thể retry (ví dụ: lỗi network/issuer, không đủ tiền); không bao giờ retry hard decline.
- Ngăn chặn card testing: Không retry các lỗi như
DO_NOT_HONOR,STOLEN_CARD,LOST_CARD,PICKUP_CARD,FRAUDULENT,AUTHENTICATION_FAILURE. - Thay đổi metadata (tùy chọn): Nếu duy trì hệ thống retry riêng, hãy phân biệt các lần retry bằng metadata (ví dụ:
retry_attempt).
Lịch retry đề xuất (subscription)
- Lần thử thứ 1: Ngay lập tức khi bạn tạo khoản phí
- Lần thử thứ 2: Sau 3 ngày
- Lần thử thứ 3: Sau thêm 7 ngày (tổng cộng 10 ngày)
- Lần thử thứ 4 (cuối cùng): Sau thêm 7 ngày nữa (tổng cộng 17 ngày)
Tránh retry theo đợt; căn chỉnh theo thời điểm authorization
- Neo các lần retry theo timestamp authorization ban đầu để tránh hành vi “retry theo đợt” trên toàn bộ danh mục của bạn.
- Ví dụ: Nếu khách hàng bắt đầu trial hoặc mandate lúc 1:10 chiều hôm nay, hãy lên lịch các lần retry tiếp theo lúc 1:10 chiều vào những ngày sau theo backoff của bạn (ví dụ: +3 ngày → 1:10 chiều, +7 ngày → 1:10 chiều).
- Ngoài ra, nếu bạn lưu thời điểm payment thành công gần nhất
T, hãy lên lịch lần thử tiếp theo vàoT + X daysđể duy trì căn chỉnh theo thời gian trong ngày.
Múi giờ và DST: sử dụng một chuẩn thời gian nhất quán để lên lịch, và chỉ chuyển đổi khi hiển thị nhằm duy trì các khoảng thời gian.
Mã từ chối không nên retry
STOLEN_CARDDO_NOT_HONORFRAUDULENTPICKUP_CARDAUTHENTICATION_FAILURELOST_CARD
Để xem danh sách đầy đủ các lý do bị từ chối và biết lý do nào có thể được người dùng khắc phục, hãy xem tài liệu
Transaction Failures.
Hướng dẫn triển khai (không có code)
- Sử dụng scheduler/queue có khả năng lưu timestamp chính xác; tính thời điểm thử tiếp theo theo đúng độ lệch thời gian trong ngày (ví dụ:
T + 3 daysvào cùng HH:MM). - Duy trì và tham chiếu timestamp payment thành công gần nhất
Tđể tính lần thử tiếp theo; không dồn nhiều subscription vào cùng một thời điểm. - Luôn đánh giá lý do từ chối gần nhất; dừng retry đối với hard decline trong danh sách bỏ qua ở trên.
- Giới hạn số lần retry đồng thời trên mỗi khách hàng và mỗi account để ngăn các đợt tăng đột biến ngoài ý muốn.
- Chủ động trao đổi: gửi email/SMS cho khách hàng để cập nhật payment method trước lần thử tiếp theo theo lịch.
- Chỉ sử dụng metadata cho mục đích observability (ví dụ:
retry_attempt); không bao giờ cố gắng “né tránh” hệ thống fraud/risk bằng cách xoay vòng các field không quan trọng.
Hủy
Subscription on-demand có flow hủy khác với subscription theo lịch vì không có chu kỳ tính phí cố định làm mốc cho ngày kết thúc ngay lập tức.Hành vi của Customer Portal
Khi khách hàng hủy subscription on-demand từ Customer Portal, việc hủy sẽ được lên lịch vào ngày tính phí tiếp theo theo mặc định. Tùy chọn Cancel Now cố ý không được hiển thị cho subscription on-demand. Lý do là subscription on-demand không có ngày gia hạn định kỳ có thể dự đoán — thời điểm tính phí tiếp theo hoàn toàn do các usage event của bạn quyết định. Việc lên lịch hủy vào ngày tính phí tiếp theo giữ mandate hoạt động cho đến cuối kỳ để mọi usage đang xử lý vẫn có thể được tính phí, sau đó kết thúc subscription một cách rõ ràng. Sau khi khách hàng xác nhận hủy:- Subscription vẫn ở trạng thái
activevà tiếp tục có thể được tính phí quaPOST /subscriptions/{id}/chargecho đến ngày hủy theo lịch. cancel_at_next_billing_dateđược đặt thànhtruetrên subscription.- Một webhook
subscription.cancelledđược phát ra khi việc hủy có hiệu lực.
Nếu cần kết thúc subscription ngay lập tức (ví dụ: để phản hồi yêu cầu hoàn tiền hoặc yêu cầu hỗ trợ), hãy hủy subscription bằng API thay vì dựa vào flow của customer portal.
Hủy bằng lập trình
Bạn có thể hủy subscription on-demand qua API bất cứ lúc nào. Bạn kiểm soát việc hủy diễn ra ngay lập tức hay theo lịch. Endpoint: PATCH /subscriptions/{subscription_id}- Cancel immediately
- Cancel at next billing date
Đặt
status của subscription thành cancelled để kết thúc subscription ngay lập tức. Mandate bị thu hồi và không thể tạo thêm khoản phí nào.cURL
Webhook khi hủy
Theo dõi kết quả bằng webhook
Triển khai xử lý webhook để theo dõi hành trình của khách hàng. Xem Implementing Webhooks.- subscription.active: Mandate đã được authorize và subscription được kích hoạt
- subscription.failed: Tạo không thành công (ví dụ: mandate không thành công)
- subscription.on_hold: Subscription được đặt ở trạng thái on hold (ví dụ: trạng thái chưa thanh toán)
- subscription.cancelled: Subscription được hủy hoàn toàn (xem Hủy)
- payment.succeeded: Khoản phí thành công
- payment.failed: Khoản phí không thành công
Kiểm thử và các bước tiếp theo
1
Create in test mode
Sử dụng test API key để tạo subscription, sau đó mở
checkout_url được trả về và hoàn tất mandate.2
Trigger a charge
Gọi charge endpoint với một
product_price nhỏ (ví dụ: 100) và xác minh rằng bạn nhận được payment.succeeded.3
Go live
Chuyển sang live API key sau khi đã xác thực các event và cập nhật state nội bộ.
Khắc phục sự cố
- 422 Invalid Request: Đảm bảo
on_demand.mandate_onlyđược cung cấp khi tạo vàproduct_priceđược cung cấp cho các khoản phí. - Lỗi currency: Nếu bạn override
product_currency, hãy xác nhận currency đó được hỗ trợ cho account và khách hàng của bạn. - Không nhận được webhook: Xác minh cấu hình webhook URL và signature secret.