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
wajib
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
Tentukan bagaimana saldo wallet pelanggan digunakan untuk menyelesaikan charge ini.
object
Metadata tambahan untuk payment. Jika dihilangkan, metadata subscription akan digunakan.
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
Menangani charge yang gagal
Saat charge terhadap subscription on-demand gagal, Anda menentukan tindakan berikutnya. Tidak seperti subscription terjadwal — ketika renewal yang gagal menghentikan billing otomatis berikutnya — subscription on-demand tetap dapat di-charge setelah kegagalan. Anda dapat memanggil endpoint charge lagi sebagai bagian dari logika retry Anda sendiri.Apa yang terjadi saat terjadi kegagalan
1
Charge attempt fails
Request
POST /subscriptions/{subscription_id}/charge akan mengembalikan error response atau selesai secara asynchronous dan mengirimkan webhook payment.failed dengan alasan penolakan.2
Subscription may transition to on_hold
Subscription dapat berpindah ke state
on_hold dan mengirimkan webhook subscription.on_hold (lihat Subscription States → On Hold). Ini adalah sinyal — bukan kunci. Untuk subscription on-demand, on_hold tidak mencegah Anda melakukan charge lagi.3
Retry the charge (your call)
Untuk flow on-demand, Dodo tidak melakukan auto-retry. Anda dapat memanggil
POST /subscriptions/{subscription_id}/charge lagi kapan saja untuk retry. Terapkan safe retry policy di bawah — gunakan exponential backoff, lewati hard decline, dan hindari pola burst — agar retry tidak ditandai oleh sistem fraud dan risk kami.4
Optionally, ask the customer for a new payment method
Jika retry terus gagal karena payment method itu sendiri bermasalah (kartu kedaluwarsa, akun ditutup, dan sebagainya), gunakan
POST /subscriptions/{subscription_id}/update-payment-method untuk mengumpulkan payment method baru dari pelanggan. Setelah berhasil, subscription kembali ke active dan webhook payment.succeeded diikuti oleh subscription.active akan dikirimkan.On-demand vs terjadwal: Untuk subscription terjadwal, Dodo menjalankan retry renewal dan dunning-nya sendiri. Untuk subscription on-demand, Anda bertanggung jawab atas retry policy karena hanya Anda yang mengetahui kapan charge berikutnya harus dilakukan (hal ini didorong oleh event penggunaan Anda, bukan kalender).
Urutan webhook untuk charge on-demand yang gagal
Event 3 dan 4 hanya dipicu setelah charge lanjutan berhasil.
Tanggung jawab retry
Subscription Dunning — rangkaian recovery melalui email bawaan — ditujukan untuk payment renewal yang gagal pada subscription terjadwal dan pembatalan yang dilakukan pelanggan. Fitur ini tidak dirancang untuk kegagalan charge on-demand. Berkomunikasilah langsung dengan pelanggan (misalnya melalui email transaksional atau prompt dalam aplikasi) saat Anda memutuskan bahwa payment method perlu diperbarui.Retry payment
Sistem deteksi fraud kami dapat memblokir pola retry yang agresif (dan menandainya sebagai kemungkinan card testing). Ikuti safe retry policy.Prinsip safe retry policy
- Mekanisme backoff: Gunakan exponential backoff di antara retry.
- Batas retry: Batasi jumlah retry (maksimal 3–4 upaya).
- Pemfilteran cerdas: Lakukan retry hanya pada kegagalan yang dapat di-retry (misalnya error network/issuer, saldo tidak mencukupi); jangan pernah melakukan retry pada hard decline.
- Pencegahan card testing: Jangan melakukan retry pada kegagalan seperti
DO_NOT_HONOR,STOLEN_CARD,LOST_CARD,PICKUP_CARD,FRAUDULENT,AUTHENTICATION_FAILURE. - Variasikan metadata (opsional): Jika Anda memelihara sistem retry sendiri, bedakan retry melalui metadata (misalnya
retry_attempt).
Jadwal retry yang disarankan (subscription)
- Upaya ke-1: Segera saat Anda membuat charge
- Upaya ke-2: Setelah 3 hari
- Upaya ke-3: Setelah 7 hari berikutnya (total 10 hari)
- Upaya ke-4 (terakhir): Setelah 7 hari berikutnya (total 17 hari)
Hindari retry burst; selaraskan dengan waktu authorization
- Jadikan timestamp authorization awal sebagai acuan retry untuk menghindari perilaku “burst” di seluruh portofolio Anda.
- Contoh: Jika pelanggan memulai trial atau mandate pada pukul 13.10 hari ini, jadwalkan retry lanjutan pada pukul 13.10 di hari-hari berikutnya sesuai backoff Anda (misalnya, +3 hari → 13.10, +7 hari → 13.10).
- Atau, jika Anda menyimpan waktu payment berhasil terakhir
T, jadwalkan upaya berikutnya padaT + X daysuntuk mempertahankan penyelarasan waktu dalam sehari.
Time zone dan DST: gunakan standar waktu yang konsisten untuk penjadwalan dan lakukan konversi hanya untuk tampilan agar interval tetap terjaga.
Kode penolakan yang tidak boleh di-retry
STOLEN_CARDDO_NOT_HONORFRAUDULENTPICKUP_CARDAUTHENTICATION_FAILURELOST_CARD
Untuk daftar lengkap alasan penolakan dan apakah alasan tersebut dapat diperbaiki oleh pengguna, lihat dokumentasi
Transaction Failures.
Panduan implementasi (tanpa kode)
- Gunakan scheduler/queue yang menyimpan timestamp secara presisi; hitung upaya berikutnya pada offset waktu yang sama persis (misalnya
T + 3 dayspada HH:MM yang sama). - Simpan dan gunakan timestamp payment berhasil terakhir
Tuntuk menghitung upaya berikutnya; jangan mengelompokkan beberapa subscription pada waktu yang sama persis. - Selalu evaluasi alasan penolakan terakhir; hentikan retry untuk hard decline dalam daftar pengecualian di atas.
- Batasi retry bersamaan per pelanggan dan per akun untuk mencegah lonjakan yang tidak disengaja.
- Berkomunikasilah secara proaktif: kirim email/SMS kepada pelanggan agar memperbarui payment method mereka sebelum upaya terjadwal berikutnya.
- Gunakan metadata hanya untuk observability (misalnya
retry_attempt); jangan pernah mencoba “menghindari” sistem fraud/risk dengan memutar field yang tidak penting.
Pembatalan
Subscription on-demand mengikuti flow pembatalan yang berbeda dari subscription terjadwal karena tidak ada siklus billing tetap yang dapat menjadi acuan tanggal berakhir segera.Perilaku Customer Portal
Saat pelanggan membatalkan subscription on-demand dari Customer Portal, pembatalan secara default dijadwalkan untuk tanggal billing berikutnya. Opsi Cancel Now sengaja tidak ditampilkan untuk subscription on-demand. Alasannya: subscription on-demand tidak memiliki tanggal renewal berulang yang dapat diprediksi — waktu charge berikutnya sepenuhnya ditentukan oleh event penggunaan Anda. Menjadwalkan pembatalan pada tanggal billing berikutnya membuat mandate tetap aktif hingga batas periode sehingga penggunaan yang sedang berjalan tetap dapat di-charge, lalu mengakhiri subscription dengan benar. Setelah pelanggan mengonfirmasi pembatalan:- Subscription tetap dalam state
activedan tetap dapat di-charge melaluiPOST /subscriptions/{id}/chargehingga tanggal pembatalan terjadwal. cancel_at_next_billing_datediatur ketruepada subscription.- Webhook
subscription.cancelleddikirimkan saat pembatalan mulai berlaku.
Jika Anda perlu mengakhiri subscription segera (misalnya sebagai respons terhadap refund atau permintaan dukungan), batalkan secara programmatic melalui API, bukan dengan mengandalkan flow Customer Portal.
Membatalkan secara programmatic
Anda dapat membatalkan subscription on-demand melalui API kapan saja. Anda mengontrol apakah pembatalan dilakukan segera atau dijadwalkan. Endpoint: PATCH /subscriptions/{subscription_id}- Cancel immediately
- Cancel at next billing date
Atur
status subscription ke cancelled untuk segera mengakhirinya. Mandate dicabut dan tidak ada charge berikutnya yang dapat dibuat.cURL
Webhook saat pembatalan
Melacak hasil dengan webhook
Implementasikan penanganan webhook untuk melacak perjalanan pelanggan. Lihat Implementing Webhooks.- subscription.active: Mandate diotorisasi dan subscription diaktifkan
- subscription.failed: Pembuatan gagal (misalnya kegagalan mandate)
- subscription.on_hold: Subscription ditempatkan pada hold (misalnya state belum dibayar)
- subscription.cancelled: Subscription sepenuhnya dibatalkan (lihat Pembatalan)
- payment.succeeded: Charge berhasil
- payment.failed: Charge gagal
Pengujian dan langkah berikutnya
1
Create in test mode
Gunakan test API key Anda untuk membuat subscription, lalu buka
checkout_url yang dikembalikan dan selesaikan mandate.2
Trigger a charge
Panggil endpoint charge dengan
product_price kecil (misalnya 100) dan verifikasi bahwa Anda menerima payment.succeeded.3
Go live
Beralih ke live API key Anda setelah memvalidasi event dan pembaruan state internal.
Pemecahan masalah
- 422 Invalid Request: Pastikan
on_demand.mandate_onlydisediakan saat pembuatan danproduct_pricedisediakan untuk charge. - Error currency: Jika Anda mengganti
product_currency, pastikan currency tersebut didukung untuk akun dan pelanggan Anda. - Tidak menerima webhook: Verifikasi konfigurasi URL webhook dan secret signature Anda.