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.

Cara Kerja On-Demand

  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.

Membuat Subscription On-Demand

Endpoint: POST /checkouts Key request fields (body):
Please find them in Create Checkout Session

Membuat Subscription On-Demand

Success

Menagih Subscription On-Demand

After the mandate is authorized, create charges as needed. Endpoint: POST /subscriptions/{subscription_id}/charge Key request fields (body):
integer
wajib
Jumlah yang akan ditagih (dalam unit terkecil mata uang). Contoh: untuk menagih $25.00, teruskan 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
Menagih subscription yang bukan on-demand akan gagal dengan 400 (SUBSCRIPTION_NOT_ON_DEMAND). Pastikan subscription memiliki on_demand: true sebelum menagihnya. Subscription on-demand juga tidak dapat mengubah plan: POST /subscriptions/{subscription_id}/change-plan mengembalikan 422 untuk subscription tersebut.

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.

Yang Terjadi Saat Gagal

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 status on_hold dan mengirim webhook subscription.on_hold (lihat Status Subscription → On Hold). Ini adalah sinyal—bukan penguncian. Untuk subscription on-demand, on_hold tidak mencegah Anda menagih lagi. Charge baru ditolak dengan 409 saat pembayaran sebelumnya masih tertunda, dan dengan 429 setelah lebih dari empat pembayaran gagal sejak pembayaran terakhir yang berhasil.
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 pada 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 Pembayaran

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 Kebijakan Retry yang Aman

  • 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 Bertubi-tubi; Selaraskan dengan Waktu Otorisasi

  • 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 Programatis

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

Menetapkan atau menghapus cancel_at_next_billing_date tidak mengirim webhook khusus. Untuk melacak pembatalan terjadwal, baca cancel_at_next_billing_date dari respons API atau dari payload subscription.updated berikutnya.
Untuk membedakan pembatalan on-demand dari pembatalan subscription terjadwal di handler Anda, periksa flag on_demand milik subscription saat memproses webhook.

Lacak Hasil dengan Webhook

Implementasikan penanganan webhook untuk melacak perjalanan customer. Lihat Webhooks.
  • subscription.active: Mandate diotorisasi dan subscription diaktifkan
  • subscription.failed: Pembuatan gagal (misalnya, kegagalan mandate)
  • subscription.on_hold: Subscription ditangguhkan (misalnya, status belum dibayar)
  • subscription.cancelled: Subscription dibatalkan sepenuhnya (lihat Pembatalan)
  • payment.succeeded: Charge berhasil
  • payment.failed: Charge gagal
Untuk alur on-demand, fokus pada payment.succeeded dan payment.failed untuk merekonsiliasi charge berbasis penggunaan. Jika 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 API key pengujian 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 API key live setelah Anda memvalidasi event dan pembaruan state internal.

Pemecahan Masalah

  • 422 Invalid Request: Pastikan on_demand.mandate_only diberikan saat pembuatan dan product_price diberikan untuk charge.
  • Currency errors: Jika Anda mengganti product_currency, pastikan mata uang tersebut didukung untuk akun dan customer Anda.
  • No webhooks received: Verifikasi URL webhook dan konfigurasi secret tanda tangan Anda.
Terakhir diubah pada 26 September 2026