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
Panduan ini membuat langganan sesuai permintaan melalui sesi pembayaran (POST /checkouts), yang selalu mengembalikan checkout_url yang dihosting. Arahkan pelanggan ke sana untuk menyetujui mandat, dan atur return_url ke tempat mereka harus mendarat setelahnya.

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
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.
Success
Charge subscription yang bukan on-demand mungkin gagal. Pastikan subscription memiliki on_demand: true dalam detailnya sebelum melakukan charge.

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

Dodo Payments tidak melakukan auto-retry terhadap charge on-demand yang gagal. Anda bertanggung jawab atas retry policy. Ikuti panduan safe retry di bawah ini agar tidak ditandai oleh sistem deteksi fraud kami sebagai card testing.
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.
Pola retry burst dapat ditandai sebagai fraud atau dugaan card testing oleh sistem risk dan processor kami. Hindari retry yang berkelompok; ikuti jadwal backoff dan panduan penyelarasan waktu di bawah ini.

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)
Langkah terakhir: jika masih belum dibayar, tandai subscription sebagai belum dibayar atau batalkan, sesuai policy Anda. Beri tahu pelanggan selama periode tersebut agar memperbarui payment method mereka.

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 pada T + X days untuk 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_CARD
  • DO_NOT_HONOR
  • FRAUDULENT
  • PICKUP_CARD
  • AUTHENTICATION_FAILURE
  • LOST_CARD
Untuk daftar lengkap alasan penolakan dan apakah alasan tersebut dapat diperbaiki oleh pengguna, lihat dokumentasi Transaction Failures.
Lakukan retry hanya pada masalah soft/sementara (misalnya insufficient_funds, issuer_unavailable, processing_error, timeout network). Jika penolakan yang sama berulang, hentikan retry berikutnya sementara.

Panduan implementasi (tanpa kode)

  • Gunakan scheduler/queue yang menyimpan timestamp secara presisi; hitung upaya berikutnya pada offset waktu yang sama persis (misalnya T + 3 days pada HH:MM yang sama).
  • Simpan dan gunakan timestamp payment berhasil terakhir T untuk 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 active dan tetap dapat di-charge melalui POST /subscriptions/{id}/charge hingga tanggal pembatalan terjadwal.
  • cancel_at_next_billing_date diatur ke true pada subscription.
  • Webhook subscription.cancelled dikirimkan 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}
Atur status subscription ke cancelled untuk segera mengakhirinya. Mandate dicabut dan tidak ada charge berikutnya yang dapat dibuat.
cURL

Webhook saat pembatalan

Untuk membedakan pembatalan on-demand dari pembatalan subscription terjadwal dalam handler Anda, periksa flag on_demand milik subscription saat memproses webhook.

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
Untuk flow on-demand, fokus pada payment.succeeded dan payment.failed untuk merekonsiliasi charge berbasis penggunaan. Saat payment.failed diikuti oleh subscription.on_hold, lihat Menangani charge yang gagal untuk memulihkan subscription.

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_only disediakan saat pembuatan dan product_price disediakan 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.
Terakhir diubah pada 6 Agustus 2026