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 capability(Settings → Subscriptions → Collect Plan Change Payments by Payment Link), effective_at: immediately 및 on_payment_failure: prevent_change가 필요합니다. Collecting Payment via a Checkout Link을 참조하세요.preview route에서는 무시됩니다.- 제공하지 않음 /
null— 새 product에 적용 가능한 경우preserve_on_plan_change=true가 있는 기존 discount가 유지됩니다. [](빈 배열) — subscription에서 모든 기존 discount를 제거합니다.["CODE_A", "CODE_B", ...]— 기존 discount를 이 stacked set으로 대체합니다.
discount_codes를 사용하세요. 이 field는 backward compatibility를 위해 계속 작동하지만, 동일한 request에서 discount_codes와 함께 사용할 수 없습니다.immediately(기본값): 플랜 변경을 즉시 적용합니다.next_billing_date: 다음 billing date에 변경을 예약합니다. billing period가 끝날 때까지 고객은 현재 플랜을 유지합니다.
next_billing_date를 사용하여 고객이 billing period 종료 시점까지 현재 플랜의 혜택을 유지하도록 하세요.Handle Webhook Events
subscription.active: 플랜 변경 성공, subscription 업데이트됨subscription.plan_changed: Subscription plan 변경됨(upgrade/downgrade/addon 업데이트)subscription.on_hold: 플랜 변경 charge 실패, renewal 중지됨payment.succeeded: 플랜 변경을 위한 즉시 charge 성공payment.failed: 즉시 charge 실패
Update Your Application State
- 새 플랜에 따라 기능을 부여하거나 취소
- 새 플랜 세부정보로 customer dashboard 업데이트
- 플랜 변경 확인 email 전송
- audit 목적으로 billing 변경 사항 기록
Test and Monitor
- 다양한 시나리오에서 모든 proration mode 테스트
- webhook 처리가 올바르게 작동하는지 확인
- 플랜 변경 성공률 모니터링
- 실패한 플랜 변경에 대한 alert 설정
플랜 변경 미리보기
플랜 변경을 확정하기 전에 Preview API를 사용하여 고객에게 실제로 청구될 금액을 정확히 표시하세요:- Node.js SDK
- Python SDK
Change Plan API
Change Plan API를 사용하여 활성 subscription의 product, quantity 및 proration 동작을 수정하세요.빠른 시작 예시
- Node.js SDK
- Python SDK
- Go SDK
- HTTP
200 OK를 반환합니다. body(ChangePlanResponse)의 내용은 변경 사항을 수금한 방식에 따라 달라집니다:
collect_via_payment_link request의 경우 결과가 나중에 비동기적으로 결정됩니다. response는 checkout link만 전달하고, subscription은 현재 플랜을 유지하며, 고객이 해당 link에서 실제로 결제를 완료하기 전까지 결과를 알 수 없습니다.어느 경우든 이 response에서 결과를 추론하지 마세요. webhook(payment.succeeded, payment.failed, subscription.plan_changed)을 통해 확인하거나 GET /subscriptions/{subscription_id}로 subscription을 다시 조회하세요. payment-link의 경우에는 What Happens While the Link Is Unpaid를 참조하세요.Checkout Link을 통한 결제 수금
기본적으로 즉시 플랜 변경은 subscription의 저장된 payment method에 직접 charge합니다.collect_via_payment_link: true를 설정하면 고객을 호스팅된 checkout 페이지로 보낼 수 있습니다. off-session으로 charge할 수 있는 저장된 payment method가 없거나 고객이 새 가격을 직접 확인하도록 하려는 경우에 유용합니다.
Requirements
collect_via_payment_link: true는 다음 조건을 모두 충족할 때만 성공합니다. 그렇지 않으면 422와 함께 request가 실패합니다:
- 비즈니스에서
allow_plan_change_via_payment_linkcapability가 활성화되어 있어야 합니다(Settings → Subscriptions → Collect Plan Change Payments by Payment Link). effective_at가immediately여야 합니다(기본값). 예약된 변경(next_billing_date)은 적용될 때까지 아무것도 청구되지 않으므로 checkout 페이지가 필요하지 않습니다.- effective
on_payment_failure가prevent_change로 resolve되어야 합니다. 명시적으로 전송할 필요는 없습니다. 비즈니스 수준 기본값(아래 Business & Collection Defaults 참조)이 이미prevent_change이면 field를 생략해도 이 조건을 충족합니다. 명시적인apply_change또는 resolve된 기본값이apply_change이면422와 함께 실패합니다.
collect_via_payment_link는 upgrade에만 한정되지 않습니다. 위 요구사항을 충족하는 한 downgrade를 포함하여 charge가 발생하는 모든 즉시 변경에 적용됩니다.proration_billing_mode: do_not_bill 또는 이번 cycle의 합계가 0이 되는 다른 mode)에는 checkout 페이지에 추가할 항목이 없습니다. payment link가 발급되지 않고 payment_link 등은 null로 반환되며, collect_via_payment_link 없이 처리하는 경우와 동일하게 변경이 즉시 적용됩니다. 이는 422가 아닙니다. 이 flag는 수금할 양수 금액이 있을 때만 적용됩니다. 명확한 upgrade가 아닌 일반적인 플랜 변경에 collect_via_payment_link를 설정하는 경우 먼저 Preview Plan Change를 호출하고, preview된 금액을 수금할 가치가 있을 때만 link를 요청하세요.
- Node.js SDK
- Python SDK
- HTTP
Link가 결제되지 않은 동안 발생하는 일
- subscription은 현재 플랜을 유지합니다. link가 결제될 때까지
product_id,recurring_pre_tax_amount및next_billing_date는 모두 변경되지 않습니다. - link가 pending인 동안 동일한 subscription에 대한 추가
change-planrequest는409 PendingPlanChangeExists와 함께 거부됩니다. 필요한 경우DELETE /subscriptions/{subscription_id}/change-plan/scheduled로 예약된 변경을 취소할 수 있지만, 해당 endpoint는 pending payment-link 변경을 취소하지 않습니다. 성공적인 결제 또는 만료만 이를 처리합니다. - decline 후에도 고객은 동일한 checkout session에서 card 결제를 재시도할 수 있습니다. 새로운
change-plancall은 재시도 방법이 아닙니다. - link가 결제되지 않으면
expires_on후 작동을 중지하며, subscription은 곧 새 플랜 변경 request를 받을 수 있는 상태가 됩니다. - 예약된 변경(
next_billing_date)이 이미 존재하고 이를cancel_scheduled_change_plan: true로 대체한 경우, link가 결제되지 않은 동안 기존 schedule은 유지됩니다. link가 결제된 후 새 플랜이 적용되는 동일한 transaction에서만 취소됩니다.
Addon 관리
subscription 플랜을 변경할 때 addon도 수정할 수 있습니다:Discount Code 적용
subscription 플랜을 변경할 때 하나 이상의 stacked discount code를 적용할 수 있습니다(최대 20개, 배열 순서대로 적용). upgrade 또는 migration에 promotional pricing을 제공할 때 유용합니다.- Node.js SDK
- Python SDK
- HTTP
플랜 변경 시 discount 동작
discount_code field는 deprecated 상태지만 backward compatibility를 위해 계속 작동합니다. 기존 integration을 즉시 변경할 필요는 없습니다. 동일한 request에서 discount_codes와 함께 사용할 수 없습니다. 편리한 시점에 array 형식으로 migration하세요.Proration mode
플랜 변경 시 고객에게 청구할 방식을 선택하세요:prorated_immediately
- 현재 cycle의 부분 차액을 charge합니다.
- trial 중이면 즉시 charge하고 지금 새 플랜으로 전환합니다.
- Downgrade: 향후 renewal에 적용되는 prorated credit이 생성될 수 있습니다.
full_immediately
- 새 플랜의 전체 금액을 즉시 charge합니다.
- 기존 플랜의 남은 기간을 무시합니다.
difference_immediately를 사용한 downgrade로 생성된 credit은 subscription 범위에 속하며 Credit-Based Billing entitlement와는 별개입니다. 동일한 subscription의 향후 renewal에 자동으로 적용되며 subscription 간에 이전할 수 없습니다.difference_immediately
- Upgrade: 기존 플랜과 새 플랜의 가격 차액을 즉시 charge합니다.
- Downgrade: 남은 가치를 subscription의 internal credit으로 추가하고 renewal에 자동 적용합니다.
do_not_bill
- charge 또는 credit을 계산하지 않습니다.
- billing 조정 없이 고객이 즉시 새 플랜으로 전환합니다.
- billing cycle은 변경되지 않습니다.
- courtesy migration, free plan 전환 또는 비용 차액을 흡수하는 경우에 적합합니다.
예시 시나리오
다음 기준 숫자를 일관되게 사용하세요:- 현재 플랜: Basic, 월 $30
- Upgrade 대상: Pro, $80
- Downgrade 대상(Pro에서): Starter, $20
- Billing cycle: 30일, January 1에 시작
- 플랜 변경일: January 16(15일 남음, 15일 사용)
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
각 mode의 billing 처리 방식
Payment Failure 처리
on_payment_failure parameter를 사용하여 플랜 변경 payment가 실패할 때의 동작을 제어하세요.
Payment Failure Mode
- prevent_change (Recommended for critical upgrades)
- apply_change (Default)
- 플랜 변경이 “pending”으로 표시됩니다.
- 고객은 현재 플랜에 계속 액세스할 수 있습니다.
- 성공적인 payment 이후에만 subscription이
activestate로 이동합니다. - 업그레이드된 기능을 제공하기 전에 payment를 확인하려는 경우에 유용합니다.
on_payment_failure parameter는 dashboard에 설정된 비즈니스 수준 기본값을 사용합니다.각 mode를 사용하는 시점
Business & Collection Defaults
모든 플랜 변경에 proration parameter를 전달하는 대신 비즈니스 수준에서 기본 upgrade 및 downgrade 동작을 한 번 설정할 수 있습니다. 이러한 기본값은 모든 customer-portal 플랜 변경에 적용되며, product collection별로 재정의할 수 있습니다. Upgrade와 downgrade에는 별도의 기본값이 있습니다:Resolution order
특정 플랜 변경에서 각 setting은 다음 순서로 resolve됩니다:webhook 처리
webhook을 통해 subscription state를 추적하여 플랜 변경과 payment를 확인하세요.처리할 Event type
subscription.active: subscription 활성화subscription.plan_changed: subscription plan 변경(upgrade/downgrade/addon 변경)subscription.on_hold: charge 실패, renewal 중지subscription.renewed: renewal 성공payment.succeeded: 플랜 변경 또는 renewal payment 성공payment.failed: payment 실패
Signature 검증 및 intent 처리
- Next.js Route Handler
- Express.js
Best Practices
안정적인 subscription plan 변경을 위해 다음 권장사항을 따르세요:플랜 변경 전략
- 철저히 테스트: production 전에 항상 test mode에서 플랜 변경을 테스트하세요.
- Proration을 신중하게 선택: 비즈니스 모델에 맞는 proration mode를 선택하세요.
- 실패를 적절히 처리: 적절한 error handling 및 retry logic을 구현하세요.
- 성공률 모니터링: 플랜 변경 성공/실패율을 추적하고 문제를 조사하세요.
Webhook 구현
- Signature 검증: authenticity를 확인하기 위해 항상 webhook signature를 검증하세요.
- Idempotency 구현: 중복 webhook event를 적절히 처리하세요.
- 비동기로 처리: 무거운 작업으로 webhook response를 차단하지 마세요.
- 모두 기록: debugging 및 audit 목적으로 상세한 log를 유지하세요.
User Experience
- 명확하게 전달: billing 변경 및 시점을 고객에게 알리세요.
- 확인 제공: 성공적인 플랜 변경에 대해 confirmation email을 보내세요.
- Edge case 처리: trial period, proration 및 payment 실패를 고려하세요.
- UI 즉시 업데이트: 애플리케이션 interface에 플랜 변경을 반영하세요.
일반적인 문제 및 해결 방법
subscription 플랜 변경 중 발생하는 일반적인 문제를 해결하세요:Charge created but subscription not updated
Charge created but subscription not updated
- Webhook 처리가 실패했거나 지연됨
- webhook 수신 후 application state가 업데이트되지 않음
- state 업데이트 중 database transaction 문제
- retry logic를 포함한 견고한 webhook handling 구현
- state 업데이트에 idempotent operation 사용
- 누락된 webhook event를 감지하고 alert하는 monitoring 추가
- webhook endpoint가 액세스 가능하고 올바르게 응답하는지 확인
Credits not applied after downgrade
Credits not applied after downgrade
- Proration mode에 대한 예상 차이: downgrade는
difference_immediately에서 전체 플랜 가격 차액을 credit으로 제공하는 반면,prorated_immediately는 cycle의 남은 시간을 기준으로 prorated credit을 생성함 - Credit은 subscription별로 적용되며 subscription 간에 이전되지 않음
- 고객 dashboard에 credit balance가 표시되지 않음
- 자동 credit을 원할 때 downgrade에
difference_immediately사용 - credit은 동일한 subscription의 향후 renewal에 적용된다고 고객에게 설명
- credit balance를 표시하도록 customer portal 구현
- 다음 invoice preview에서 적용된 credit 확인
Webhook signature verification fails
Webhook signature verification fails
- 잘못된 webhook secret key
- signature 검증 전에 raw request body가 수정됨
- 잘못된 signature verification algorithm
- dashboard의 올바른
DODO_WEBHOOK_SECRET를 사용하는지 확인 - JSON parsing middleware 전에 raw request body 읽기
- platform에 맞는 standard webhook verification library 사용
- development environment에서 webhook signature verification 테스트
Plan change fails with 422 error
Plan change fails with 422 error
- 유효하지 않은 subscription ID 또는 product ID
- Subscription이 active state가 아님
- 필수 parameter 누락
- Product를 플랜 변경에 사용할 수 없음
- subscription이 존재하고 active인지 확인
- product ID가 유효하고 사용 가능한지 확인
- 모든 필수 parameter가 제공되었는지 확인
- parameter 요구사항에 대한 API documentation 검토
Immediate charge fails during plan change
Immediate charge fails during plan change
- 고객 payment method의 잔액 부족
- payment method 만료 또는 유효하지 않음
- 은행에서 transaction 거부
- Fraud detection이 charge 차단
payment.failedwebhook event를 적절히 처리- 고객에게 payment method 업데이트를 알림
- 일시적 실패에 대한 retry logic 구현
- 즉시 charge가 실패한 플랜 변경을 허용하는 방안 고려
Subscription on hold after plan change
Subscription on hold after plan change
on_hold state로 이동함발생하는 일:
플랜 변경 charge가 실패하면 subscription은 자동으로 on_hold state가 됩니다. payment method를 업데이트할 때까지 subscription은 자동으로 renewal되지 않습니다.해결 방법: payment method를 업데이트하여 subscription 재활성화플랜 변경 실패 후 on_hold state의 subscription을 재활성화하려면:- payment method 업데이트: Update Payment Method API 사용
- 자동 charge 생성: API가 미납 잔액에 대한 charge를 자동으로 생성
- Invoice 생성: charge에 대한 invoice 생성
- Payment 처리: 새 payment method를 사용하여 payment 처리
- 재활성화: payment 성공 시 subscription이
activestate로 재활성화
subscription.on_hold: Subscription이 hold 상태가 됨(플랜 변경 charge 실패 시 수신)payment.succeeded: 미납 잔액에 대한 payment 성공(payment method 업데이트 후)subscription.active: payment 성공 후 subscription 재활성화
- 플랜 변경 charge 실패 시 고객에게 즉시 알림
- payment method 업데이트 방법에 대한 명확한 안내 제공
- 재활성화 상태 추적을 위해 webhook event 모니터링
- 일시적인 payment 실패에 대한 자동 retry logic 구현 고려
Update Payment Method API Reference
구현 테스트
subscription plan 변경 구현을 철저히 테스트하려면 다음 단계를 따르세요:Set up test environment
- test API key와 test product 사용
- 다양한 plan type으로 test subscription 생성
- test webhook endpoint 구성
- monitoring 및 logging 설정
Test different proration modes
- 다양한 billing cycle 위치에서
prorated_immediately테스트 - upgrade 및 downgrade에
difference_immediately테스트 - billing cycle을 reset하도록
full_immediately테스트 - charge/credit 없는 플랜 전환에
do_not_bill테스트 - credit 계산이 올바른지 확인
Test webhook handling
- 관련된 모든 webhook event가 수신되는지 확인
- webhook signature verification 테스트
- 중복 webhook event를 적절히 처리
- webhook 처리 실패 시나리오 테스트
Test error scenarios
- 유효하지 않은 subscription ID로 테스트
- 만료된 payment method로 테스트
- network failure 및 timeout 테스트
- 잔액 부족 상태로 테스트
Monitor in production
- 실패한 플랜 변경에 대한 alert 설정
- webhook 처리 시간 모니터링
- 플랜 변경 성공률 추적
- 플랜 변경 문제에 대한 customer support ticket 검토
Error Handling
구현에서 일반적인 API error를 적절히 처리하세요:HTTP Status Codes
200 OK
200 OK
collect_via_payment_link request를 제외하면 response body는 비어 있으며, 해당 request는 checkout handle을 반환합니다. Collecting Payment via a Checkout Link을 참조하세요. on_payment_failure=prevent_change인 경우 payment가 성공할 때까지 플랜 변경이 pending 상태로 유지됩니다.400 Bad Request
400 Bad Request
404 Not Found
404 Not Found
409 Conflict
409 Conflict
PendingPlanChangeExists)이 존재합니다. 예약된 변경의 경우 새 변경을 제출하기 전에 DELETE /subscriptions/{subscription_id}/change-plan/scheduled로 취소하세요. pending payment-link 변경에는 cancel endpoint가 없습니다. 고객이 결제하거나 link가 만료된 후에만 subscription이 새 플랜 변경 request를 받을 수 있습니다.422 Unprocessable Entity
422 Unprocessable Entity
collect_via_payment_link에 적합하지 않습니다. 비즈니스에 capability가 활성화되지 않았거나, effective_at가 immediately가 아니거나, on_payment_failure가 prevent_change가 아닐 수 있습니다. Requirements를 참조하세요.500 Internal Server Error
500 Internal Server Error
Error Response Format
다음 단계
- Change Plan API 검토
- Credit-Based Billing 살펴보기
subscription.on_hold에 대한 alert 구현- Webhook Integration Guide 확인