Change Plan API
Plan Change Preview
Integration Guide
What is a subscription upgrade or downgrade?
Changing plans lets you move a customer between subscription tiers or quantities. Use it to:- Align pricing with usage or features
- Move from monthly to annual (or vice versa)
- Adjust quantity for seat-based products
When to use plan changes
- Upgrade when a customer needs more features, usage, or seats
- Downgrade when usage decreases
- Migrate users to a new product or price without cancelling their subscription
Plan Change Flow
Prerequisites
Before implementing subscription plan changes, ensure you have:- A Dodo Payments merchant account with active subscription products
- API credentials (API key and webhook secret key) from the dashboard
- An existing active subscription to modify
- Webhook endpoint configured to handle subscription events
Step-by-Step Implementation Guide
Follow this comprehensive guide to implement subscription plan changes in your application:Understand Plan Change Requirements
- Which subscription products can be changed to which others
- What proration mode fits your business model
- How to handle failed plan changes gracefully
- Which webhook events to track for state management
Choose Your Proration Strategy
- prorated_immediately
- difference_immediately
- full_immediately
- do_not_bill
- Calculates exact prorated amount based on remaining cycle time
- Charges a prorated amount based on unused time remaining in the cycle
- Provides transparent billing to customers
Implement the Change Plan API
prorated_immediately, full_immediately, difference_immediately, or do_not_bill.prevent_change: Keep subscription on current plan until payment succeedsapply_change(default): Apply plan change immediately regardless of payment outcome
allow_plan_change_via_payment_link milik business (Settings → Subscriptions → Collect Plan Change Payments by Payment Link), effective_at: immediately, dan on_payment_failure: prevent_change. Lihat Collecting Payment via a Checkout Link.Diabaikan oleh preview route.- Tidak diberikan /
null— diskon yang ada denganpreserve_on_plan_change=truedipertahankan jika berlaku untuk produk baru. [](array kosong) — menghapus semua diskon yang ada dari subscription.["CODE_A", "CODE_B", ...]— mengganti diskon yang ada dengan set stacked ini.
discount_codes untuk integrasi baru. Field ini masih berfungsi untuk kompatibilitas mundur, tetapi tidak dapat digabungkan dengan discount_codes dalam request yang sama.immediately(default): Terapkan perubahan paket segeranext_billing_date: Jadwalkan perubahan untuk tanggal penagihan berikutnya. Customer mempertahankan paket saat ini hingga periode penagihan berakhir.
next_billing_date untuk downgrade agar customer tetap memperoleh manfaat paket saat ini hingga akhir periode penagihan.Handle Webhook Events
subscription.active: Perubahan paket berhasil, subscription diperbaruisubscription.plan_changed: Paket subscription diubah (upgrade/downgrade/pembaruan addon)subscription.on_hold: Penagihan perubahan paket gagal, renewal dihentikanpayment.succeeded: Penagihan segera untuk perubahan paket berhasilpayment.failed: Penagihan segera gagal
Update Your Application State
- Berikan/cabut fitur berdasarkan paket baru
- Perbarui dashboard customer dengan detail paket baru
- Kirim email konfirmasi tentang perubahan paket
- Catat perubahan billing untuk keperluan audit
Test and Monitor
- Uji semua mode proration dengan berbagai skenario
- Pastikan penanganan webhook berfungsi dengan benar
- Pantau tingkat keberhasilan perubahan paket
- Siapkan alert untuk perubahan paket yang gagal
Preview Perubahan Paket
Sebelum mengonfirmasi perubahan paket, gunakan Preview API untuk menunjukkan secara tepat kepada customer jumlah yang akan ditagihkan:- Node.js SDK
- Python SDK
Change Plan API
Gunakan Change Plan API untuk mengubah produk, kuantitas, dan perilaku proration pada subscription aktif.Contoh quick start
- Node.js SDK
- Python SDK
- Go SDK
- HTTP
200 OK — sebelum penagihan apa pun benar-benar diselesaikan. Isi body (ChangePlanResponse) bergantung pada cara perubahan tersebut ditagihkan:
collect_via_payment_link, hasilnya diselesaikan kemudian secara asynchronous — response hanya memberikan tautan checkout, subscription tetap menggunakan paket saat ini, dan hasilnya belum diketahui sampai customer menyelesaikan pembayaran melalui tautan tersebut.Bagaimanapun, jangan menyimpulkan hasil dari response ini. Konfirmasikan melalui webhook (payment.succeeded, payment.failed, subscription.plan_changed) atau dengan membaca ulang subscription menggunakan GET /subscriptions/{subscription_id} — lihat What Happens While the Link Is Unpaid khusus untuk kasus payment-link.Mengumpulkan Pembayaran melalui Tautan Checkout
Secara default, perubahan paket segera menagih metode pembayaran tersimpan milik subscription secara langsung. Aturcollect_via_payment_link: true untuk mengarahkan customer ke halaman checkout yang di-host — berguna ketika tidak ada metode pembayaran tersimpan yang boleh Anda tagih secara off-session, atau ketika Anda ingin customer mengonfirmasi harga baru secara aktif.
Persyaratan
collect_via_payment_link: true hanya berhasil jika semua kondisi berikut terpenuhi — jika tidak, request gagal dengan 422:
- Business telah mengaktifkan capability
allow_plan_change_via_payment_link(Settings → Subscriptions → Collect Plan Change Payments by Payment Link). effective_atadalahimmediately(default). Perubahan terjadwal (next_billing_date) tidak pernah memerlukan halaman checkout karena tidak ada penagihan sampai perubahan diterapkan.on_payment_failureefektif menghasilkanprevent_change. Anda tidak perlu mengirimkannya secara eksplisit — jika default tingkat business (lihat Business & Collection Defaults di bawah) sudah berupaprevent_change, penghilangan field ini juga memenuhi persyaratan.apply_changeeksplisit, atau default terselesaikan berupaapply_change, akan gagal dengan422.
collect_via_payment_link tidak terbatas pada upgrade — ini berlaku untuk perubahan segera apa pun yang menghasilkan penagihan, termasuk downgrade, selama persyaratan di atas terpenuhi.proration_billing_mode: do_not_bill, atau mode lain yang kebetulan menghasilkan jumlah bersih nol pada siklus ini — tidak ada yang dapat dimasukkan ke halaman checkout. Tidak ada payment link yang diterbitkan, payment_link dan lainnya akan mengembalikan null, dan perubahan langsung diterapkan, sama seperti jika collect_via_payment_link tidak digunakan. Ini bukan 422; flag hanya berlaku ketika ada jumlah positif yang perlu dikumpulkan. Jika Anda menetapkan collect_via_payment_link secara umum pada perubahan paket, bukan hanya pada upgrade yang jelas, panggil Preview Plan Change terlebih dahulu dan hanya minta tautan ketika jumlah hasil preview layak dikumpulkan.
- Node.js SDK
- Python SDK
- HTTP
Yang Terjadi Selama Tautan Belum Dibayar
- Subscription tetap menggunakan paket saat ini —
product_id,recurring_pre_tax_amount, dannext_billing_datesemuanya tidak berubah sampai tautan dibayar. - Request
change-planberikutnya pada subscription yang sama ditolak dengan409 PendingPlanChangeExistsselama tautan masih tertunda. Batalkan perubahan terjadwal denganDELETE /subscriptions/{subscription_id}/change-plan/scheduledjika diperlukan, tetapi endpoint tersebut tidak membatalkan perubahan payment-link yang tertunda — hanya pembayaran yang berhasil atau masa berlaku yang berakhir yang dapat melakukannya. - Customer dapat mencoba kembali menggunakan kartu pada sesi checkout yang sama setelah penolakan; pemanggilan
change-planbaru bukan jalur untuk mencoba kembali. - Jika tautan tidak pernah dibayar, tautan berhenti berfungsi setelah
expires_on— subscription secara otomatis dapat menerima request perubahan paket baru tidak lama kemudian. - Jika perubahan terjadwal (
next_billing_date) sudah ada dan Anda menggantinya dengancancel_scheduled_change_plan: true, jadwal awal tetap berlaku selama tautan belum dibayar, dan baru dibatalkan setelah tautan dibayar — dalam transaksi yang sama saat paket baru diterapkan.
Mengelola Addon
Saat mengubah paket subscription, Anda juga dapat mengubah addon:Menerapkan Kode Diskon
Anda dapat menerapkan satu atau beberapa kode diskon stacked saat mengubah paket subscription (maks. 20, diterapkan sesuai urutan array). Ini berguna untuk menawarkan harga promosi pada upgrade atau migrasi.- Node.js SDK
- Python SDK
- HTTP
Perilaku diskon saat perubahan paket
discount_code pada endpoint ini deprecated, tetapi masih berfungsi untuk kompatibilitas mundur — integrasi yang ada tidak perlu segera diubah. Field ini tidak dapat digabungkan dengan discount_codes dalam request yang sama. Migrasikan ke bentuk array jika sudah siap.Mode Proration
Pilih cara menagih customer saat mengubah paket:prorated_immediately
- Menagih selisih sebagian untuk siklus saat ini
- Jika sedang trial, segera menagih dan beralih ke paket baru
- Downgrade: dapat menghasilkan kredit prorata yang diterapkan pada renewal mendatang
full_immediately
- Menagih jumlah penuh paket baru segera
- Mengabaikan sisa waktu dari paket lama
difference_immediately memiliki cakupan subscription dan berbeda dari entitlement Credit-Based Billing. Kredit tersebut secara otomatis diterapkan pada renewal mendatang untuk subscription yang sama dan tidak dapat dipindahtangankan antar-subscription.difference_immediately
- Upgrade: segera menagih selisih harga antara paket lama dan baru
- Downgrade: menambahkan nilai tersisa sebagai kredit internal ke subscription dan menerapkannya otomatis pada renewal
do_not_bill
- Tidak ada penagihan atau kredit yang dihitung
- Customer segera beralih ke paket baru tanpa penyesuaian billing
- Siklus billing tetap tidak berubah
- Cocok untuk migrasi sebagai bentuk goodwill, perpindahan ke paket gratis, atau penyerapan perbedaan biaya
Skenario contoh
Gunakan angka kanonis berikut secara konsisten:- Paket saat ini: Basic seharga $30/bulan
- Target upgrade: Pro seharga $80/bulan
- Target downgrade (dari Pro): Starter seharga $20/bulan
- Siklus billing: 30 hari, dimulai pada January 1
- Perubahan paket terjadi pada January 16 (tersisa 15 hari, 15 hari telah digunakan)
Upgrade: Basic ($30) → Pro ($80) with prorated_immediately
Upgrade: Basic ($30) → Pro ($80) with prorated_immediately
Downgrade: Pro ($80) → Starter ($20) with prorated_immediately
Downgrade: Pro ($80) → Starter ($20) with prorated_immediately
Upgrade: Basic ($30) → Pro ($80) with difference_immediately
Upgrade: Basic ($30) → Pro ($80) with difference_immediately
Downgrade: Pro ($80) → Starter ($20) with difference_immediately
Downgrade: Pro ($80) → Starter ($20) with difference_immediately
Upgrade: Basic ($30) → Pro ($80) with full_immediately
Upgrade: Basic ($30) → Pro ($80) with full_immediately
Mid-cycle upgrade with add-ons using prorated_immediately
Mid-cycle upgrade with add-ons using prorated_immediately
Cara setiap mode memproses billing
Menangani Kegagalan Pembayaran
Kontrol apa yang terjadi ketika pembayaran perubahan paket gagal menggunakan parameteron_payment_failure.
Mode Kegagalan Pembayaran
- prevent_change (Recommended for critical upgrades)
- apply_change (Default)
- Perubahan paket ditandai sebagai “pending”
- Customer tetap memiliki akses ke paket saat ini
- Subscription berpindah ke status
activehanya setelah pembayaran berhasil - Berguna ketika Anda ingin memastikan pembayaran sebelum memberikan fitur yang ditingkatkan
on_payment_failure menggunakan pengaturan default tingkat business yang dikonfigurasi di dashboard.Kapan Menggunakan Setiap Mode
Default Business & Collection
Daripada mengirim parameter proration pada setiap perubahan paket, Anda dapat menetapkan perilaku default upgrade & downgrade sekali di tingkat business. Default ini berlaku untuk semua perubahan paket melalui customer portal dan dapat ditimpa pada setiap product collection. Terdapat default terpisah untuk upgrade dan downgrade:Urutan resolusi
Untuk perubahan paket apa pun, setiap pengaturan di-resolve dalam urutan berikut:Menangani webhook
Lacak status subscription melalui webhook untuk mengonfirmasi perubahan paket dan pembayaran.Jenis event yang perlu ditangani
subscription.active: subscription diaktifkansubscription.plan_changed: paket subscription diubah (upgrade/downgrade/perubahan addon)subscription.on_hold: penagihan gagal, renewal dihentikansubscription.renewed: renewal berhasilpayment.succeeded: pembayaran untuk perubahan paket atau renewal berhasilpayment.failed: pembayaran gagal
Memverifikasi signature dan menangani intent
- Next.js Route Handler
- Express.js
Praktik Terbaik
Ikuti rekomendasi berikut untuk perubahan paket subscription yang andal:Strategi Perubahan Paket
- Uji secara menyeluruh: Selalu uji perubahan paket dalam test mode sebelum production
- Pilih proration dengan cermat: Pilih mode proration yang sesuai dengan model bisnis Anda
- Tangani kegagalan dengan baik: Terapkan penanganan error dan logika retry yang tepat
- Pantau tingkat keberhasilan: Lacak tingkat keberhasilan/kegagalan perubahan paket dan selidiki masalah
Implementasi Webhook
- Verifikasi signature: Selalu validasi signature webhook untuk memastikan keaslian
- Terapkan idempotensi: Tangani event webhook duplikat dengan baik
- Proses secara asynchronous: Jangan menghambat response webhook dengan operasi berat
- Catat semuanya: Simpan log terperinci untuk debugging dan audit
User Experience
- Berkomunikasi dengan jelas: Beri tahu customer tentang perubahan dan waktu billing
- Berikan konfirmasi: Kirim konfirmasi email untuk perubahan paket yang berhasil
- Tangani kasus khusus: Pertimbangkan periode trial, proration, dan pembayaran yang gagal
- Perbarui UI segera: Tampilkan perubahan paket pada antarmuka aplikasi Anda
Masalah Umum dan Solusi
Atasi masalah umum yang ditemui selama perubahan paket subscription:Charge created but subscription not updated
Charge created but subscription not updated
- Pemrosesan webhook gagal atau tertunda
- State aplikasi tidak diperbarui setelah menerima webhook
- Masalah transaksi database saat memperbarui state
- Terapkan penanganan webhook yang kuat dengan logika retry
- Gunakan operasi idempoten untuk pembaruan state
- Tambahkan monitoring untuk mendeteksi dan memberi alert atas event webhook yang terlewat
- Pastikan endpoint webhook dapat diakses dan merespons dengan benar
Credits not applied after downgrade
Credits not applied after downgrade
- Ekspektasi mode proration: downgrade memberikan kredit selisih harga paket penuh dengan
difference_immediately, sedangkanprorated_immediatelymembuat kredit prorata berdasarkan sisa waktu dalam siklus - Kredit bersifat spesifik untuk subscription dan tidak berpindah antar-subscription
- Saldo kredit tidak terlihat di dashboard customer
- Gunakan
difference_immediatelyuntuk downgrade jika Anda menginginkan kredit otomatis - Jelaskan kepada customer bahwa kredit berlaku untuk renewal mendatang pada subscription yang sama
- Implementasikan customer portal untuk menampilkan saldo kredit
- Periksa preview invoice berikutnya untuk melihat kredit yang diterapkan
Webhook signature verification fails
Webhook signature verification fails
- Secret key webhook salah
- Raw request body diubah sebelum verifikasi signature
- Algoritma verifikasi signature salah
- Pastikan Anda menggunakan
DODO_WEBHOOK_SECRETyang benar dari dashboard - Baca raw request body sebelum middleware parsing JSON apa pun
- Gunakan library verifikasi webhook standar untuk platform Anda
- Uji verifikasi signature webhook di environment development
Plan change fails with 422 error
Plan change fails with 422 error
- ID subscription atau ID produk tidak valid
- Subscription tidak dalam state aktif
- Parameter wajib tidak ada
- Produk tidak tersedia untuk perubahan paket
- Pastikan subscription ada dan aktif
- Periksa ID produk valid dan tersedia
- Pastikan semua parameter wajib diberikan
- Tinjau dokumentasi API untuk persyaratan parameter
Immediate charge fails during plan change
Immediate charge fails during plan change
- Dana pada metode pembayaran customer tidak mencukupi
- Metode pembayaran kedaluwarsa atau tidak valid
- Bank menolak transaksi
- Deteksi fraud memblokir penagihan
- Tangani event webhook
payment.faileddengan tepat - Beri tahu customer untuk memperbarui metode pembayaran
- Terapkan logika retry untuk kegagalan sementara
- Pertimbangkan untuk mengizinkan perubahan paket meskipun penagihan segera gagal
Subscription on hold after plan change
Subscription on hold after plan change
on_holdYang terjadi:
Ketika penagihan perubahan paket gagal, subscription secara otomatis ditempatkan dalam state on_hold. Subscription tidak akan melakukan renewal secara otomatis sampai metode pembayaran diperbarui.Solusi: Perbarui metode pembayaran untuk mengaktifkan kembali subscriptionUntuk mengaktifkan kembali subscription dari state on_hold setelah perubahan paket gagal:- Perbarui metode pembayaran menggunakan Update Payment Method API
- Pembuatan penagihan otomatis: API secara otomatis membuat penagihan untuk jumlah terutang yang tersisa
- Pembuatan invoice: Invoice dibuat untuk penagihan tersebut
- Pemrosesan pembayaran: Pembayaran diproses menggunakan metode pembayaran baru
- Pengaktifan kembali: Setelah pembayaran berhasil, subscription diaktifkan kembali ke state
active
subscription.on_hold: Subscription ditahan (diterima saat penagihan perubahan paket gagal)payment.succeeded: Pembayaran untuk jumlah terutang yang tersisa berhasil (setelah metode pembayaran diperbarui)subscription.active: Subscription diaktifkan kembali setelah pembayaran berhasil
- Beri tahu customer segera ketika penagihan perubahan paket gagal
- Berikan instruksi yang jelas tentang cara memperbarui metode pembayaran
- Pantau event webhook untuk melacak status pengaktifan kembali
- Pertimbangkan untuk menerapkan logika retry otomatis bagi kegagalan pembayaran sementara
Update Payment Method API Reference
Menguji Implementasi Anda
Ikuti langkah-langkah berikut untuk menguji implementasi perubahan paket subscription Anda secara menyeluruh:Set up test environment
- Gunakan API key test dan produk test
- Buat subscription test dengan berbagai jenis paket
- Konfigurasikan endpoint webhook test
- Siapkan monitoring dan logging
Test different proration modes
- Uji
prorated_immediatelydengan berbagai posisi dalam siklus billing - Uji
difference_immediatelyuntuk upgrade dan downgrade - Uji
full_immediatelyuntuk mengatur ulang siklus billing - Uji
do_not_billuntuk perpindahan paket tanpa penagihan/kredit - Pastikan perhitungan kredit sudah benar
Test webhook handling
- Pastikan semua event webhook yang relevan diterima
- Uji verifikasi signature webhook
- Tangani event webhook duplikat dengan baik
- Uji skenario kegagalan pemrosesan webhook
Test error scenarios
- Uji dengan ID subscription yang tidak valid
- Uji dengan metode pembayaran yang kedaluwarsa
- Uji kegagalan jaringan dan timeout
- Uji dengan dana yang tidak mencukupi
Monitor in production
- Siapkan alert untuk perubahan paket yang gagal
- Pantau waktu pemrosesan webhook
- Lacak tingkat keberhasilan perubahan paket
- Tinjau tiket dukungan customer untuk masalah perubahan paket
Penanganan Error
Tangani error API umum dengan baik dalam implementasi Anda:HTTP Status Codes
200 OK
200 OK
collect_via_payment_link yang berhasil, yang mengembalikan handle checkout — lihat Collecting Payment via a Checkout Link. Jika on_payment_failure=prevent_change, perubahan paket tetap tertunda sampai pembayaran berhasil.400 Bad Request
400 Bad Request
404 Not Found
404 Not Found
409 Conflict
409 Conflict
PendingPlanChangeExists). Untuk perubahan terjadwal, batalkan dengan DELETE /subscriptions/{subscription_id}/change-plan/scheduled sebelum mengirim yang baru. Untuk perubahan payment-link yang tertunda, tidak ada endpoint pembatalan — subscription menerima request perubahan paket baru setelah customer membayar atau tautan kedaluwarsa.422 Unprocessable Entity
422 Unprocessable Entity
collect_via_payment_link — business belum mengaktifkan capability, effective_at bukan immediately, atau on_payment_failure bukan prevent_change. Lihat Requirements.500 Internal Server Error
500 Internal Server Error
Format Error Response
Langkah berikutnya
- Tinjau Change Plan API
- Jelajahi Credit-Based Billing
- Implementasikan alert untuk
subscription.on_hold - Lihat Webhook Integration Guide