Langsung ke konten utama

Change Plan API

Full API docs for updating subscriptions.

Plan Change Preview

See charge amounts before changing plans.

Integration Guide

Step-by-step subscription setup.

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
Plan changes can trigger an immediate charge depending on the proration mode you choose.

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
For detailed setup instructions, see our Integration Guide.

Step-by-Step Implementation Guide

Follow this comprehensive guide to implement subscription plan changes in your application:
1

Understand Plan Change Requirements

Before implementing, determine:
  • 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
Test plan changes thoroughly in test mode before implementing in production.
2

Choose Your Proration Strategy

Select the billing approach that aligns with your business needs:
Best for: SaaS applications wanting to charge fairly for unused time
  • 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
3

Implement the Change Plan API

Use the Change Plan API to modify subscription details:
subscription_id
string
wajib
The ID of the active subscription to modify.
product_id
string
wajib
The new product ID to change the subscription to.
quantity
integer
default:"1"
Number of units for the new plan (for seat-based products).
proration_billing_mode
string
wajib
How to handle immediate billing: prorated_immediately, full_immediately, difference_immediately, or do_not_bill.
addons
array
Optional addons for the new plan. Leaving this empty removes any existing addons.
on_payment_failure
string
Controls behavior when the plan change payment fails:
  • prevent_change: Keep subscription on current plan until payment succeeds
  • apply_change (default): Apply plan change immediately regardless of payment outcome
If not specified, uses the business-level default setting.
discount_codes
array
Kode diskon tambahan opsional untuk diterapkan ke paket baru (maks 20, diterapkan dalam urutan array). Perilaku tergantung pada apa yang Anda berikan:
  • Tidak disediakan / null — diskon yang ada dengan preserve_on_plan_change=true dipertahankan jika berlaku untuk produk baru.
  • [] (array kosong) — menghapus semua diskon yang ada dari langganan.
  • ["CODE_A", "CODE_B", ...] — menggantikan diskon yang ada dengan set baru ini.
discount_code
string
usang
Dihapus — lebih memilih discount_codes untuk integrasi baru. Bidang ini masih berfungsi untuk kompatibilitas ke belakang, tetapi tidak dapat digabungkan dengan discount_codes dalam permintaan yang sama.
effective_at
string
default:"immediately"
Kapan mengajukan perubahan paket:
  • immediately (default): Terapkan segera perubahan paket
  • next_billing_date: Jadwalkan perubahan untuk tanggal penagihan berikutnya. Pelanggan mempertahankan paket mereka saat ini hingga periode penagihan berakhir.
Gunakan next_billing_date untuk penurunan versi sehingga pelanggan tetap mendapatkan manfaat paket mereka saat ini hingga akhir periode penagihan.
4

Handle Webhook Events

Mengatur penanganan webhook untuk melacak hasil perubahan paket:
  • subscription.active: Perubahan paket berhasil, langganan diperbarui
  • subscription.plan_changed: Paket langganan berubah (peningkatan/penurunan versi/pembaruan addon)
  • subscription.on_hold: Biaya perubahan paket gagal, langganan dijeda
  • payment.succeeded: Biaya langsung untuk perubahan paket berhasil
  • payment.failed: Biaya langsung gagal
Selalu verifikasi tanda tangan webhook dan terapkan pemrosesan acara idempoten.
5

Update Your Application State

Berdasarkan acara webhook, perbarui aplikasi Anda:
  • Berikan/cabut fitur berdasarkan paket baru
  • Perbarui dasbor pelanggan dengan detail paket baru
  • Kirim email konfirmasi tentang perubahan paket
  • Catat perubahan penagihan untuk tujuan audit
6

Test and Monitor

Uji implementasi Anda secara menyeluruh:
  • Uji semua mode proratasi dengan berbeda skenario
  • Verifikasi penanganan webhook berfungsi dengan benar
  • Monitor tingkat keberhasilan perubahan paket
  • Siapkan peringatan untuk perubahan paket yang gagal
Implementasi perubahan paket langganan Anda sekarang siap untuk digunakan produksi.

Pratinjau Perubahan Paket

Sebelum melakukan perubahan paket, gunakan Preview API untuk menunjukkan kepada pelanggan berapa tepatnya yang akan mereka bayar:
const preview = await client.subscriptions.previewChangePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately'
});

// Show customer the charge before confirming
console.log('Immediate charge:', preview.immediate_charge.summary);
console.log('New plan details:', preview.new_plan);
Gunakan API pratinjau untuk membuat dialog konfirmasi yang menunjukkan kepada pelanggan jumlah tepat yang akan mereka bayar sebelum mereka mengonfirmasi perubahan paket.

API Perubahan Paket

Gunakan API Perubahan Paket untuk memodifikasi produk, kuantitas, dan perilaku proratasi untuk langganan aktif.

Contoh cepat memulai

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode', // defaults to 'live_mode'
});

async function changePlan() {
  const result = await client.subscriptions.changePlan('sub_123', {
    product_id: 'prod_new',
    quantity: 3,
    proration_billing_mode: 'prorated_immediately',
    on_payment_failure: 'prevent_change', // Optional: control behavior on payment failure
  });
  console.log(result.status, result.invoice_id, result.payment_id);
}

changePlan();
Success
{
  "status": "processing",
  "subscription_id": "sub_123",
  "invoice_id": "inv_789",
  "payment_id": "pay_456",
  "proration_billing_mode": "prorated_immediately"
}
Fields seperti invoice_id dan payment_id hanya dikembalikan ketika biaya langsung dan/atau faktur dibuat selama perubahan paket. Selalu andalkan acara webhook (misalnya, payment.succeeded, subscription.plan_changed) untuk mengonfirmasi hasil.
Jika biaya langsung gagal, langganan mungkin berpindah ke subscription.on_hold hingga pembayaran berhasil.

Mengelola Addon

Saat mengubah paket langganan, Anda juga dapat memodifikasi addon:
// Add addons to the new plan
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [
    { addon_id: 'addon_123', quantity: 2 }
  ]
});

// Remove all existing addons
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [] // Empty array removes all existing addons
});
Addon termasuk dalam perhitungan proratasi dan akan dikenakan biaya sesuai dengan mode proratasi yang dipilih.

Menerapkan Kode Diskon

Anda dapat menerapkan satu atau lebih kode diskon tambahan saat mengubah paket langganan (maks 20, diterapkan dalam urutan array). Ini berguna untuk menawarkan harga promosi pada peningkatan atau migrasi.
// Apply stacked discount codes during plan change
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately',
  discount_codes: ['UPGRADE20']
});

Perilaku diskon pada perubahan paket

Nilai discount_codesPerilaku
Tidak disediakan / nullDiskon yang ada dengan preserve_on_plan_change=true secara otomatis dipertahankan jika berlaku untuk produk baru.
[] (array kosong)Semua diskon yang ada dihapus dari langganan.
["CODE_A", "CODE_B", ...]Menggantikan diskon yang ada dengan set tambahan ini, divalidasi dan diterapkan dalam urutan array.
Bidang tunggal discount_code di endpoint ini dihapus tetapi masih berfungsi untuk kompatibilitas ke belakang — integrasi yang ada tidak perlu mengubah segera. Tidak dapat digabungkan dengan discount_codes dalam permintaan yang sama. Migrasikan ke bentuk array saat nyaman.
Gunakan Preview Plan Change API dengan discount_codes untuk menunjukkan kepada pelanggan berapa banyak yang akan mereka hemat sebelum mengonfirmasi perubahan paket.

Mode Prorasi

Pilih cara untuk menagih pelanggan saat mengubah paket:

prorated_immediately

  • Menagih perbedaan parsial dalam siklus saat ini
  • Jika dalam percobaan, langsung menagih dan beralih ke paket baru sekarang
  • Penurunan versi: dapat menghasilkan kredit prorated yang diterapkan ke pembaruan mendatang

full_immediately

  • Menagih jumlah penuh dari paket baru secara langsung
  • Mengabaikan waktu yang tersisa dari paket lama
Kredit yang dibuat oleh penurunan versi menggunakan difference_immediately diterapkan pada ruang lingkup langganan dan berbeda dari hak Penagihan Berbasis Kredit. Mereka secara otomatis diaplikasikan pada pembaruan di masa mendatang dari langganan yang sama dan tidak dapat dipindahtangankan antara langganan.

difference_immediately

  • Peningkatan: langsung menagih perbedaan harga antara paket lama dan baru
  • Penurunan versi: tambahkan nilai yang tersisa sebagai kredit internal pada langganan dan terapkan secara otomatis saat pembaruan

do_not_bill

  • Tidak ada biaya atau kredit yang dihitung
  • Pelanggan beralih ke paket baru segera tanpa penyesuaian penagihan
  • Siklus penagihan tetap tidak berubah
  • Terbaik untuk migrasi sopan santun, perpindahan paket gratis, atau menyerap perbedaan biaya
Fiturprorated_immediatelydifference_immediatelyfull_immediatelydo_not_bill
Biaya upgradePerbedaan sesuai prorated untuk sisa hariPerbedaan harga penuh antara paketHarga penuh paket baruTidak ada biaya
Kredit downgradeKredit prorated untuk sisa hariPerbedaan harga penuh sebagai kreditTidak ada kreditTidak ada kredit
Siklus penagihanDireset menjadi hari iniDireset menjadi hari iniDireset menjadi hari iniTidak berubah
Perilaku percobaanAkhiri percobaan, kenakan biaya segeraAkhiri percobaan, kenakan biaya segeraAkhiri percobaan, kenakan biaya penuhAkhiri percobaan, tidak ada biaya
Terbaik untukPenagihan berbasis waktu adilMatematika upgrade/downgrade sederhanaMereset siklus penagihanMigrasi gratis atau peralihan bersahabat
KompleksitasSedang (penghitungan hari)Rendah (pengurangan sederhana)Rendah (biaya penuh)Tidak ada

Contoh skenario

Gunakan angka kanonik ini secara konsisten:
  • Paket saat ini: Basic di $30/bulan
  • Target peningkatan: Pro di $80/bulan
  • Target penurunan (dari Pro): Starter di $20/bulan
  • Siklus penagihan: 30 hari, dimulai pada 1 Januari
  • Perubahan paket terjadi pada 16 Januari (15 hari tersisa, 15 hari digunakan)
Step 1: Calculate unused credit from current plan
  Unused days = 15 out of 30 days
  Credit = $30 × (15/30) = $15.00

Step 2: Calculate prorated cost of new plan
  Remaining days = 15 out of 30 days
  New plan cost = $80 × (15/30) = $40.00

Step 3: Calculate immediate charge
  Charge = New plan cost − Credit
  Charge = $40.00 − $15.00 = $25.00

→ Customer pays $25.00 now
→ Billing cycle resets to today (January 16)
→ Next renewal (Feb 15): $80.00/month
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately'
})
Step 1: Calculate unused credit from current plan
  Unused days = 15 out of 30 days
  Credit = $80 × (15/30) = $40.00

Step 2: Calculate prorated cost of new plan
  Remaining days = 15 out of 30 days
  New plan cost = $20 × (15/30) = $10.00

Step 3: Calculate credit balance
  Credit = $40.00 − $10.00 = $30.00

→ No charge — $30.00 credit added to subscription
→ Billing cycle resets to today (January 16)
→ Credit auto-applies to future renewals
→ Next renewal (Feb 15): $20.00 − $20.00 (from credit) = $0.00 ($10.00 credit remaining)
→ Following renewal (Mar 15): $20.00 − $10.00 remaining credit = $10.00
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_starter',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately'
})
Immediate charge = New plan price − Old plan price
                 = $80 − $30
                 = $50.00

→ Customer pays $50.00 now (regardless of cycle position)
→ Billing cycle resets to today (January 16)
→ Next renewal (Feb 15): $80.00/month
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'difference_immediately'
})
Credit = Old plan price − New plan price
       = $80 − $20
       = $60.00

→ No charge — $60.00 credit added to subscription
→ Credit auto-applies to future renewals
→ Next renewal: $20.00 − $20.00 (from credit) = $0.00
→ Following renewal: $20.00 − $20.00 (from credit) = $0.00
→ Third renewal: $20.00 − $20.00 (from remaining credit) = $0.00
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_starter',
  quantity: 1,
  proration_billing_mode: 'difference_immediately'
})
Immediate charge = Full new plan price = $80.00

→ Customer pays $80.00 now
→ No credit for unused time on old plan
→ Billing cycle resets to today (January 16)
→ Next renewal: February 16 at $80.00/month
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'full_immediately'
})
Current: Basic plan ($30/month), no add-ons
New: Pro plan ($80/month) + Extra Seats add-on ($10/seat × 3 seats = $30/month)
Change on day 16 of 30 (15 days remaining)

Step 1: Credit from current plan
  Credit = $30 × (15/30) = $15.00

Step 2: Prorated cost of new plan + add-ons
  New plan = $80 × (15/30) = $40.00
  Add-ons = $30 × (15/30) = $15.00
  Total new = $55.00

Step 3: Immediate charge
  Charge = $55.00 − $15.00 = $40.00

→ Customer pays $40.00 now
→ Next renewal: $80.00 + $30.00 = $110.00/month
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately',
  addons: [
    { addon_id: 'addon_seats', quantity: 3 }
  ]
})

Bagaimana setiap mode memproses penagihan

Pilih prorated_immediately untuk perhitungan waktu yang adil; pilih full_immediately untuk memulai ulang penagihan; gunakan difference_immediately untuk peningkatan sederhana dan kredit otomatis pada penurunan versi; atau gunakan do_not_bill untuk mengganti paket tanpa penyesuaian penagihan.

Penanganan Kegagalan Pembayaran

Kontrol apa yang terjadi ketika pembayaran perubahan paket gagal menggunakan parameter on_payment_failure.

Mode Kegagalan Pembayaran

Jika tidak ditentukan, parameter on_payment_failure menggunakan pengaturan default tingkat bisnis Anda yang dikonfigurasi di dasbor.

Kapan Menggunakan Setiap Mode

SkenarioMode yang DirekomendasikanAlasan
Meningkatkan ke fitur premiumprevent_changePastikan pembayaran sebelum memberikan akses
Peningkatan jumlah (lebih banyak kursi)prevent_changeMencegah penggunaan tanpa pembayaran
Menurunkan paketapply_changePelanggan mengurangi pengeluaran
Pelanggan perusahaan terpercayaapply_changeRisiko lebih rendah dari ketidakpembayaran
Konversi percobaan ke berbayarprevent_changeMomen pembayaran kritis

Default Bisnis & Koleksi

Daripada memasukkan parameter prorated pada setiap perubahan paket, Anda dapat menetapkan perilaku default untuk upgrade & downgrade sekali di tingkat bisnis. Default ini berlaku untuk semua perubahan paket portal pelanggan, dan dapat ditimpa sesuai koleksi produk. Ada default terpisah untuk upgrade dan downgrade:
PengaturanBidang (upgrade / downgrade)Default (upgrade)Default (downgrade)
Kapan paket baru dimulaieffective_at_on_upgrade / effective_at_on_downgradeimmediatelynext_billing_date
Bagaimana cara pelanggan dikenakan biayaproration_billing_mode_on_upgrade / proration_billing_mode_on_downgradedifference_immediatelydifference_immediately
Jika pembayaran pelanggan gagalon_payment_failureapply_changeapply_change
Konfigurasi default bisnis di bawah Pengaturan → Langganan, dan mengesampingkan koleksi pada setiap koleksi produk. Setiap bidang koleksi bersifat independen — biarkan tidak diatur untuk mewarisi dari default bisnis, atau tetapkan nilai untuk menimpanya hanya untuk koleksi tersebut.

Urutan Resolusi

Untuk setiap perubahan paket, setiap pengaturan diselesaikan dalam urutan ini:
per-request value (Change Plan API) → collection field (if set) → business field → system default
Nilai yang diteruskan secara eksplisit ke API Perubahan Paket selalu menang. Default bisnis dan koleksi hanya berlaku ketika tidak ada nilai eksplisit yang disediakan — yang merupakan kasus untuk semua perubahan paket yang dimulai dari portal pelanggan.
Pengaturan umum: biarkan upgrade immediately + difference_immediately sehingga pelanggan membayar perbedaannya dan mendapatkan akses segera, dan biarkan downgrade pada next_billing_date sehingga pelanggan mempertahankan paket mereka saat ini hingga siklus berakhir.

Menangani Webhooks

Lacak status langganan melalui webhooks untuk mengonfirmasi perubahan paket dan pembayaran.

Jenis Peristiwa untuk Ditangani

  • subscription.active: langganan diaktifkan
  • subscription.plan_changed: paket langganan berubah (perubahan upgrade/downgrade/addon)
  • subscription.on_hold: pembayaran gagal, langganan dihentikan
  • subscription.renewed: perpanjangan berhasil
  • payment.succeeded: pembayaran untuk perubahan paket atau perpanjangan berhasil
  • payment.failed: pembayaran gagal
Kami merekomendasikan untuk mendorong logika bisnis dari peristiwa langganan dan menggunakan peristiwa pembayaran untuk konfirmasi dan rekonsiliasi.

Verifikasi Tanda Tangan dan Tangani Intent

import { NextRequest, NextResponse } from 'next/server';

export async function POST(req) {
  const webhookId = req.headers.get('webhook-id');
  const webhookSignature = req.headers.get('webhook-signature');
  const webhookTimestamp = req.headers.get('webhook-timestamp');
  const secret = process.env.DODO_WEBHOOK_SECRET;

  const payload = await req.text();
  // verifySignature is a placeholder – in production, use a Standard Webhooks library
  const { valid, event } = await verifySignature(
    payload,
    { id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
    secret
  );
  if (!valid) return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });

  switch (event.type) {
    case 'subscription.active':
      // mark subscription active in your DB
      break;
    case 'subscription.plan_changed':
      // refresh entitlements and reflect the new plan in your UI
      break;
    case 'subscription.on_hold':
      // notify user to update payment method
      break;
    case 'subscription.renewed':
      // extend access window
      break;
    case 'payment.succeeded':
      // reconcile payment for plan change
      break;
    case 'payment.failed':
      // log and alert
      break;
    default:
      // ignore unknown events
      break;
  }

  return NextResponse.json({ received: true });
}
Untuk skema payload terperinci, lihat Payload webhook Langganan dan Payload webhook Pembayaran.

Praktik Terbaik

Ikuti rekomendasi ini untuk perubahan paket langganan yang andal:

Strategi Perubahan Paket

  • Uji secara menyeluruh: Selalu uji perubahan paket dalam mode uji sebelum produksi
  • Pilih proration dengan hati-hati: Pilih mode proration yang paling sesuai dengan model bisnis Anda
  • Tangani kegagalan dengan baik: Terapkan penanganan kesalahan dan logika coba ulang yang tepat
  • Pantau tingkat keberhasilan: Lacak tingkat keberhasilan/kegagalan perubahan paket dan selidiki masalah

Implementasi Webhook

  • Verifikasi tanda tangan: Selalu validasi tanda tangan webhook untuk memastikan keaslian
  • Terapkan idempotensi: Tangani duplikasi peristiwa webhook dengan baik
  • Proses secara asinkron: Jangan memblokir respon webhook dengan operasi berat
  • Catat semuanya: Pertahankan catatan rinci untuk debugging dan audit

Pengalaman Pengguna

  • Komunikasikan dengan jelas: Informasikan kepada pelanggan tentang perubahan dan waktu penagihan
  • Berikan konfirmasi: Kirim konfirmasi email untuk perubahan paket yang berhasil
  • Tangani kasus tepi: Pertimbangkan periode percobaan, perhitungan prorated, dan pembayaran yang gagal
  • Perbarui UI segera: Refleksikan perubahan paket di antarmuka aplikasi Anda

Masalah Umum dan Solusi

Selesaikan masalah-masalah umum yang dihadapi selama perubahan paket langganan:
Gejala: Panggilan API berhasil tetapi langganan tetap pada paket lamaPenyebab umum:
  • Pemrosesan webhook gagal atau tertunda
  • Status aplikasi tidak diperbarui setelah menerima webhooks
  • Masalah transaksi database selama pembaruan status
Solusi:
  • Terapkan penanganan webhook yang andal dengan logika coba ulang
  • Gunakan operasi idempoten untuk pembaruan status
  • Tambahkan pemantauan untuk mendeteksi dan memberi tahu jika ada peristiwa webhook yang terlewatkan
  • Verifikasi endpoint webhook dapat diakses dan merespon dengan benar
Gejala: Pelanggan melakukan downgrade tetapi tidak melihat saldo kreditPenyebab umum:
  • Ekspektasi mode proration: Downgrade memberi kredit perbedaan harga paket penuh dengan difference_immediately, sedangkan prorated_immediately membuat kredit prorated berdasarkan sisa waktu dalam siklus
  • Kredit bersifat spesifik untuk langganan dan tidak berpindah antar langganan
  • Saldo kredit tidak terlihat di dasbor pelanggan
Solusi:
  • Gunakan difference_immediately untuk downgrade jika Anda menginginkan kredit otomatis
  • Jelaskan kepada pelanggan bahwa kredit berlaku untuk perpanjangan langganan yang sama di masa depan
  • Terapkan portal pelanggan untuk menunjukkan saldo kredit
  • Periksa pratinjau faktur berikutnya untuk melihat kredit yang diterapkan
Gejala: Peristiwa webhook ditolak karena tanda tangan tidak validPenyebab umum:
  • Kunci rahasia webhook salah
  • Badan permintaan mentah diubah sebelum verifikasi tanda tangan
  • Algoritma verifikasi tanda tangan salah
Solusi:
  • Verifikasi Anda menggunakan DODO_WEBHOOK_SECRET yang benar dari dasbor
  • Baca badan permintaan mentah sebelum middleware parsing JSON
  • Gunakan perpustakaan verifikasi webhook standar untuk platform Anda
  • Uji verifikasi tanda tangan webhook di lingkungan pengembangan
Gejala: API mengembalikan kesalahan 422 Entitas Tidak Dapat DiprosesPenyebab umum:
  • ID langganan atau produk tidak valid
  • Langganan tidak dalam keadaan aktif
  • Parameter yang diperlukan hilang
  • Produk tidak tersedia untuk perubahan paket
Solusi:
  • Verifikasi langganan ada dan aktif
  • Periksa ID produk valid dan tersedia
  • Pastikan semua parameter yang diperlukan disediakan
  • Tinjau dokumentasi API untuk persyaratan parameter
Gejala: Perubahan paket dimulai tetapi biaya segera gagalPenyebab umum:
  • Dana tidak mencukupi pada metode pembayaran pelanggan
  • Metode pembayaran kadaluwarsa atau tidak valid
  • Bank menolak transaksi
  • Deteksi penipuan memblokir biaya
Solusi:
  • Tangani acara webhook payment.failed dengan tepat
  • Beritahu pelanggan untuk memperbarui metode pembayaran
  • Terapkan logika coba ulang untuk kegagalan sementara
  • Pertimbangkan mengizinkan perubahan paket dengan biaya langsung yang gagal
Gejala: Biaya perubahan paket gagal dan langganan pindah ke status on_holdApa yang terjadi: Ketika biaya perubahan paket gagal, langganan secara otomatis ditempatkan di status on_hold. Langganan tidak akan diperpanjang secara otomatis hingga metode pembayaran diperbarui.Solusi: Perbarui metode pembayaran untuk mengaktifkan kembali langgananUntuk mengaktifkan kembali langganan dari status on_hold setelah perubahan paket gagal:
  1. Perbarui metode pembayaran menggunakan API Perbarui Metode Pembayaran
  2. Pembuatan biaya otomatis: API secara otomatis membuat biaya untuk sisa tagihan
  3. Pembuatan faktur: Faktur dihasilkan untuk biaya tersebut
  4. Pemrosesan pembayaran: Pembayaran diproses menggunakan metode pembayaran baru
  5. Reaktivasi: Setelah pembayaran berhasil, langganan diaktifkan kembali ke status active
// Reactivate subscription from on_hold after failed plan change
async function reactivateAfterFailedPlanChange(subscriptionId) {
  // Update payment method - automatically creates charge for remaining dues
  const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
    type: 'new',
    return_url: 'https://example.com/return'
  });
  
  if (response.payment_id) {
    console.log('Charge created for remaining dues:', response.payment_id);
    console.log('Payment link:', response.payment_link);
    
    // Redirect customer to payment_link to complete payment
    // Monitor webhooks for:
    // 1. payment.succeeded - charge succeeded
    // 2. subscription.active - subscription reactivated
  }
  
  return response;
}

// Or use existing payment method if available
async function reactivateWithExistingPaymentMethod(subscriptionId, paymentMethodId) {
  const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
    type: 'existing',
    payment_method_id: paymentMethodId
  });
  
  // Monitor webhooks for payment.succeeded and subscription.active
  return response;
}
Acara webhook untuk dipantau:
  • subscription.on_hold: Langganan ditangguhkan (diterima saat biaya perubahan paket gagal)
  • payment.succeeded: Pembayaran untuk sisa tagihan berhasil (setelah memperbarui metode pembayaran)
  • subscription.active: Langganan diaktifkan kembali setelah pembayaran berhasil
Praktik terbaik:
  • Beritahu pelanggan segera ketika biaya perubahan paket gagal
  • Berikan instruksi yang jelas tentang cara memperbarui metode pembayaran mereka
  • Pantau acara webhook untuk melacak status reaktivasi
  • Pertimbangkan mengimplementasikan logika coba ulang otomatis untuk kegagalan pembayaran sementara

Update Payment Method API Reference

Lihat dokumentasi API lengkap untuk memperbarui metode pembayaran dan mengaktifkan kembali langganan.

Menguji Implementasi Anda

Ikuti langkah-langkah ini untuk menguji implementasi perubahan paket langganan Anda secara menyeluruh:
1

Set up test environment

  • Gunakan kunci API uji dan produk uji
  • Buat langganan uji dengan berbagai jenis paket
  • Konfigurasikan endpoint webhook uji
  • Siapkan pemantauan dan pencatatan
2

Test different proration modes

  • Uji prorated_immediately dengan berbagai posisi siklus penagihan
  • Uji difference_immediately untuk upgrade dan downgrade
  • Uji full_immediately untuk mereset siklus penagihan
  • Uji do_not_bill untuk peralihan paket tanpa biaya/kredit
  • Verifikasi perhitungan kredit benar
3

Test webhook handling

  • Verifikasi semua peristiwa webhook terkait diterima
  • Uji verifikasi tanda tangan webhook
  • Tangani duplikasi peristiwa webhook dengan baik
  • Uji skenario kegagalan pemrosesan webhook
4

Test error scenarios

  • Uji dengan ID langganan tidak valid
  • Uji dengan metode pembayaran kadaluarsa
  • Uji kegagalan jaringan dan waktu habis
  • Uji dengan dana tidak mencukupi
5

Monitor in production

  • Siapkan peringatan untuk perubahan paket yang gagal
  • Pantau waktu pemrosesan webhook
  • Lacak tingkat keberhasilan perubahan paket
  • Tinjau tiket dukungan pelanggan untuk masalah perubahan paket

Penanganan Kesalahan

Tangani kesalahan API umum dengan baik dalam implementasi Anda:

Kode Status HTTP

Permintaan perubahan paket diproses dengan sukses. Langganan sedang diperbarui dan pemrosesan pembayaran telah dimulai.
Parameter permintaan tidak valid. Periksa bahwa semua bidang yang diperlukan disediakan dan diformat dengan benar.
Kunci API tidak valid atau hilang. Verifikasi DODO_PAYMENTS_API_KEY Anda benar dan memiliki izin yang benar.
ID langganan tidak ditemukan atau tidak milik akun Anda.
Langganan tidak dapat diubah (misal, sudah dibatalkan, produk tidak tersedia, dll.).
Terjadi kesalahan server. Coba kembali permintaan setelah jeda singkat.

Format Respons Kesalahan

{
  "error": {
    "code": "subscription_not_found",
    "message": "The subscription with ID 'sub_123' was not found",
    "details": {
      "subscription_id": "sub_123"
    }
  }
}

Langkah berikutnya

Terakhir diubah pada 18 Juni 2026