Skip to main content

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
For a general subscription setup, see the Subscription Integration Guide.

Prerequisites

  • Dodo Payments merchant account and API key
  • Webhook secret configured and an endpoint to receive events
  • A subscription product in your catalog
Hướng dẫn này tạo đăng ký theo yêu cầu thông qua phiên thanh toán (POST /checkouts), luôn trả về một checkout_url được lưu trữ. Chuyển hướng khách hàng đến đó để phê duyệt yêu cầu, và đặt return_url tới nơi họ nên đến sau đó.

How on-demand works

  1. You create a subscription with the on_demand object to authorize a payment method and optionally collect an initial charge.
  2. Later, you create charges against that subscription with custom amounts using the dedicated charge endpoint.
  3. 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

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):
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.
Success
Việc tính phí cho một subscription không phải on-demand có thể không thành công. Hãy đảm bảo subscription có on_demand: true trong thông tin chi tiết trước khi tính phí.

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

Dodo Payments không tự động retry các khoản phí on-demand không thành công. Bạn chịu trách nhiệm về retry policy. Hãy làm theo hướng dẫn retry an toàn bên dưới để tránh bị hệ thống phát hiện gian lận của chúng tôi đánh dấu là card testing.
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.
Các pattern retry theo đợt có thể bị hệ thống risk và processor của chúng tôi đánh dấu là gian lận hoặc nghi ngờ card testing. Tránh các lần retry tập trung; hãy tuân theo lịch backoff và hướng dẫn căn chỉnh thời gian bên dưới.

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)
Bước cuối: nếu vẫn chưa được thanh toán, hãy đánh dấu subscription là chưa thanh toán hoặc hủy subscription, tùy theo policy của bạn. Thông báo cho khách hàng trong khoảng thời gian này để họ cập nhật payment method.

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ào T + 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_CARD
  • DO_NOT_HONOR
  • FRAUDULENT
  • PICKUP_CARD
  • AUTHENTICATION_FAILURE
  • LOST_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.
Chỉ retry đối với các vấn đề tạm thời/mềm (ví dụ: insufficient_funds, issuer_unavailable, processing_error, timeout network). Nếu cùng một lý do từ chối lặp lại, hãy tạm dừng các lần retry tiếp theo.

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 days và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 active và tiếp tục có thể được tính phí qua POST /subscriptions/{id}/charge cho đến ngày hủy theo lịch.
  • cancel_at_next_billing_date được đặt thành true trê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}
Đặ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

Để phân biệt việc hủy subscription on-demand với việc hủy subscription theo lịch trong handler, hãy kiểm tra flag on_demand của subscription khi xử lý webhook.

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
Đối với các flow on-demand, hãy tập trung vào payment.succeededpayment.failed để đối soát các khoản phí dựa trên usage. Khi payment.failed được theo sau bởi subscription.on_hold, hãy xem Xử lý các khoản phí không thành công để khôi phục subscription.

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.
Lần sửa đổi cuối 6 tháng 8, 2026