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 của doanh nghiệp (Settings → Subscriptions → Collect Plan Change Payments by Payment Link), effective_at: immediately và on_payment_failure: prevent_change. Xem Thu tiền qua Checkout Link.Bị bỏ qua bởi preview route.- Không được cung cấp /
null— các khoản giảm giá hiện có vớipreserve_on_plan_change=trueđược giữ lại nếu áp dụng cho product mới. [](array rỗng) — xóa tất cả khoản giảm giá hiện có khỏi subscription.["CODE_A", "CODE_B", ...]— thay thế mọi khoản giảm giá hiện có bằng tập mã stacked này.
discount_codes cho các tích hợp mới. Field này vẫn hoạt động để đảm bảo khả năng tương thích ngược, nhưng không thể kết hợp với discount_codes trong cùng request.immediately(mặc định): Áp dụng thay đổi gói ngay lập tứcnext_billing_date: Lên lịch thay đổi vào ngày thanh toán tiếp theo. Khách hàng giữ gói hiện tại cho đến khi kỳ thanh toán kết thúc.
next_billing_date cho các lần hạ cấp để khách hàng giữ được quyền lợi của gói hiện tại cho đến hết kỳ thanh toán.Handle Webhook Events
subscription.active: Thay đổi gói thành công, subscription đã được cập nhậtsubscription.plan_changed: Gói của subscription đã thay đổi (nâng cấp/hạ cấp/cập nhật addon)subscription.on_hold: Tính phí thay đổi gói thất bại, các lần gia hạn bị dừngpayment.succeeded: Tính phí ngay cho thay đổi gói thành côngpayment.failed: Tính phí ngay thất bại
Update Your Application State
- Cấp/thu hồi các tính năng dựa trên gói mới
- Cập nhật dashboard của khách hàng với thông tin gói mới
- Gửi email xác nhận về thay đổi gói
- Ghi log các thay đổi billing cho mục đích kiểm toán
Test and Monitor
- Kiểm thử tất cả proration mode với nhiều tình huống khác nhau
- Xác minh việc xử lý webhook hoạt động chính xác
- Theo dõi tỷ lệ thay đổi gói thành công
- Thiết lập cảnh báo cho các lần thay đổi gói thất bại
Preview thay đổi gói
Trước khi xác nhận thay đổi gói, hãy sử dụng Preview API để hiển thị chính xác khoản phí khách hàng sẽ phải trả:- Node.js SDK
- Python SDK
Change Plan API
Sử dụng Change Plan API để sửa đổi product, quantity và proration behavior cho một subscription đang active.Ví dụ bắt đầu nhanh
- Node.js SDK
- Python SDK
- Go SDK
- HTTP
200 OK ngay lập tức — trước khi bất kỳ khoản phí nào thực sự được thanh toán. Nội dung body (ChangePlanResponse) phụ thuộc vào cách thu tiền cho thay đổi:
collect_via_payment_link, kết quả được xử lý sau đó theo cơ chế bất đồng bộ — response chỉ cung cấp checkout link, subscription vẫn giữ gói hiện tại và không thể biết kết quả cho đến khi khách hàng thực sự hoàn tất thanh toán qua link đó.Trong cả hai trường hợp, không suy luận kết quả từ response này. Hãy xác nhận qua webhook (payment.succeeded, payment.failed, subscription.plan_changed) hoặc đọc lại subscription bằng GET /subscriptions/{subscription_id} — xem Điều gì xảy ra khi link chưa được thanh toán để biết riêng về trường hợp payment-link.Thu tiền qua Checkout Link
Theo mặc định, thay đổi gói ngay lập tức sẽ tính phí trực tiếp vào phương thức thanh toán đã lưu của subscription. Đặtcollect_via_payment_link: true để chuyển khách hàng đến trang checkout được lưu trữ thay thế — hữu ích khi không có phương thức thanh toán đã lưu được phép tính phí off-session, hoặc khi bạn muốn khách hàng chủ động xác nhận mức giá mới.
Yêu cầu
collect_via_payment_link: true chỉ thành công khi tất cả điều kiện sau đều được đáp ứng — nếu không, request sẽ thất bại với 422:
- Doanh nghiệp đã bật capability
allow_plan_change_via_payment_link(Settings → Subscriptions → Collect Plan Change Payments by Payment Link). effective_atlàimmediately(mặc định). Thay đổi theo lịch (next_billing_date) không cần trang checkout vì chỉ tính phí khi thay đổi được áp dụng.on_payment_failurehiệu lực phân giải thànhprevent_change. Bạn không cần gửi rõ field này — nếu giá trị mặc định cấp doanh nghiệp (xem Business & Collection Defaults bên dưới) đã làprevent_changethì việc bỏ qua field cũng đáp ứng điều kiện.apply_changerõ ràng hoặc giá trị mặc định được phân giải thànhapply_changesẽ thất bại với422.
collect_via_payment_link không chỉ áp dụng cho nâng cấp — nó áp dụng cho mọi thay đổi ngay lập tức dẫn đến khoản phí, bao gồm cả hạ cấp, miễn là đáp ứng các yêu cầu trên.proration_billing_mode: do_not_bill hoặc một mode khác vô tình có tổng giá trị bằng 0 trong chu kỳ này — thì không có gì để đưa lên checkout page. Không phát hành payment link, payment_link và các field liên quan trả về null, đồng thời thay đổi được áp dụng ngay lập tức, giống như khi không dùng collect_via_payment_link. Đây không phải 422; flag chỉ có hiệu lực khi có một khoản tiền dương cần thu. Nếu đặt collect_via_payment_link cho các thay đổi gói nói chung thay vì chỉ cho các lần nâng cấp rõ ràng, hãy gọi Preview Plan Change trước và chỉ yêu cầu link khi khoản tiền trong preview đáng để thu.
- Node.js SDK
- Python SDK
- HTTP
Điều gì xảy ra khi link chưa được thanh toán
- Subscription vẫn giữ gói hiện tại —
product_id,recurring_pre_tax_amountvànext_billing_dateđều không thay đổi cho đến khi link được thanh toán. - Một request
change-plantiếp theo trên cùng subscription sẽ bị từ chối với409 PendingPlanChangeExiststrong khi link đang chờ xử lý. Nếu cần, hãy hủy thay đổi theo lịch bằngDELETE /subscriptions/{subscription_id}/change-plan/scheduled, nhưng endpoint đó không hủy thay đổi payment-link đang chờ — chỉ thanh toán thành công hoặc hết hạn mới thực hiện việc này. - Khách hàng có thể thử lại thẻ trong cùng checkout session sau khi bị từ chối; gọi
change-planmới không phải cách retry. - Nếu link không bao giờ được thanh toán, link sẽ ngừng hoạt động sau
expires_on— subscription tự động có thể nhận request thay đổi gói mới sau đó không lâu. - Nếu đã có thay đổi theo lịch (
next_billing_date) và bạn thay thế bằngcancel_scheduled_change_plan: true, lịch ban đầu vẫn được giữ trong khi link chưa được thanh toán và chỉ bị hủy sau khi link được thanh toán — trong cùng transaction áp dụng gói mới.
Quản lý Addon
Khi thay đổi gói subscription, bạn cũng có thể sửa đổi addon:Áp dụng mã giảm giá
Bạn có thể áp dụng một hoặc nhiều mã giảm giá stacked khi thay đổi gói subscription (tối đa 20 mã, áp dụng theo thứ tự trong array). Điều này hữu ích khi cung cấp mức giá khuyến mãi cho các lần nâng cấp hoặc migration.- Node.js SDK
- Python SDK
- HTTP
Hành vi của discount khi thay đổi gói
discount_code dạng singular trên endpoint này đã ngừng sử dụng nhưng vẫn hoạt động để đảm bảo khả năng tương thích ngược — các integration hiện có không cần thay đổi ngay. Field này không thể kết hợp với discount_codes trong cùng request. Hãy chuyển sang dạng array khi thuận tiện.Các proration mode
Chọn cách tính phí khách hàng khi thay đổi gói:prorated_immediately
- Tính phí phần chênh lệch theo tỷ lệ trong chu kỳ hiện tại
- Nếu đang trial, tính phí ngay và chuyển sang gói mới ngay
- Hạ cấp: có thể tạo credit theo tỷ lệ, áp dụng cho các lần gia hạn sau
full_immediately
- Tính toàn bộ giá của gói mới ngay lập tức
- Bỏ qua thời gian còn lại của gói cũ
difference_immediately thuộc phạm vi subscription và khác với các entitlement của Credit-Based Billing. Chúng tự động được áp dụng cho các lần gia hạn sau của cùng subscription và không thể chuyển giữa các subscription.difference_immediately
- Nâng cấp: tính ngay khoản chênh lệch giá giữa gói cũ và gói mới
- Hạ cấp: cộng giá trị còn lại thành credit nội bộ cho subscription và tự động áp dụng khi gia hạn
do_not_bill
- Không tính toán khoản phí hoặc credit nào
- Khách hàng chuyển sang gói mới ngay lập tức mà không điều chỉnh billing
- Chu kỳ billing không thay đổi
- Phù hợp nhất cho migration hỗ trợ, chuyển sang gói miễn phí hoặc hấp thụ phần chênh lệch chi phí
Các tình huống ví dụ
Sử dụng nhất quán các số liệu chuẩn sau:- Gói hiện tại: Basic ở mức $30/tháng
- Gói mục tiêu khi nâng cấp: Pro ở mức $80/tháng
- Gói mục tiêu khi hạ cấp (từ Pro): Starter ở mức $20/tháng
- Chu kỳ billing: 30 ngày, bắt đầu từ January 1
- Thay đổi gói diễn ra vào January 16 (còn 15 ngày, đã sử dụng 15 ngày)
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
Cách mỗi mode xử lý billing
Xử lý lỗi thanh toán
Kiểm soát điều gì xảy ra khi thanh toán cho thay đổi gói thất bại bằng parameteron_payment_failure.
Các chế độ lỗi thanh toán
- prevent_change (Recommended for critical upgrades)
- apply_change (Default)
- Thay đổi gói được đánh dấu là “pending”
- Khách hàng vẫn có quyền truy cập gói hiện tại
- Subscription chỉ chuyển sang trạng thái
activesau khi thanh toán thành công - Hữu ích khi bạn muốn đảm bảo thanh toán trước khi cấp các tính năng nâng cấp
on_payment_failure sẽ sử dụng thiết lập mặc định cấp doanh nghiệp được cấu hình trong dashboard.Khi nào nên dùng từng mode
Mặc định cấp doanh nghiệp & thu tiền
Thay vì truyền parameter proration trong mỗi lần thay đổi gói, bạn có thể thiết lập hành vi nâng cấp & hạ cấp mặc định một lần ở cấp doanh nghiệp. Các giá trị mặc định này áp dụng cho mọi thay đổi gói từ customer portal và có thể được ghi đè theo từng product collection. Có các giá trị mặc định riêng cho nâng cấp và hạ cấp:Thứ tự phân giải
Với mỗi thay đổi gói cụ thể, từng thiết lập được phân giải theo thứ tự sau:Xử lý webhook
Theo dõi trạng thái subscription qua webhook để xác nhận thay đổi gói và các khoản thanh toán.Các loại event cần xử lý
subscription.active: subscription đã được kích hoạtsubscription.plan_changed: gói subscription đã thay đổi (nâng cấp/hạ cấp/thay đổi addon)subscription.on_hold: tính phí thất bại, các lần gia hạn bị dừngsubscription.renewed: gia hạn thành côngpayment.succeeded: thanh toán cho thay đổi gói hoặc gia hạn thành côngpayment.failed: thanh toán thất bại
Xác minh chữ ký và xử lý intent
- Next.js Route Handler
- Express.js
Các phương pháp hay nhất
Hãy làm theo các khuyến nghị sau để thay đổi gói subscription đáng tin cậy:Chiến lược thay đổi gói
- Kiểm thử kỹ lưỡng: Luôn kiểm thử thay đổi gói ở test mode trước khi đưa lên production
- Lựa chọn proration cẩn thận: Chọn proration mode phù hợp với mô hình kinh doanh
- Xử lý lỗi phù hợp: Triển khai error handling và retry logic thích hợp
- Theo dõi tỷ lệ thành công: Theo dõi tỷ lệ thay đổi gói thành công/thất bại và điều tra vấn đề
Triển khai webhook
- Xác minh chữ ký: Luôn xác thực chữ ký webhook để đảm bảo tính xác thực
- Triển khai idempotency: Xử lý phù hợp các event webhook trùng lặp
- Xử lý bất đồng bộ: Không chặn response webhook bằng các thao tác nặng
- Ghi log mọi thứ: Duy trì log chi tiết để debug và kiểm toán
Trải nghiệm người dùng
- Trao đổi rõ ràng: Thông báo cho khách hàng về các thay đổi billing và thời điểm áp dụng
- Cung cấp xác nhận: Gửi email xác nhận khi thay đổi gói thành công
- Xử lý trường hợp đặc biệt: Cân nhắc thời gian trial, proration và các khoản thanh toán thất bại
- Cập nhật UI ngay lập tức: Phản ánh thay đổi gói trong giao diện application
Các vấn đề thường gặp và giải pháp
Giải quyết các vấn đề điển hình gặp phải trong quá trình thay đổi gói subscription:Charge created but subscription not updated
Charge created but subscription not updated
- Xử lý webhook thất bại hoặc bị trì hoãn
- State của application không được cập nhật sau khi nhận webhook
- Vấn đề transaction cơ sở dữ liệu trong quá trình cập nhật state
- Triển khai xử lý webhook mạnh mẽ với retry logic
- Sử dụng các thao tác idempotent để cập nhật state
- Thêm monitoring để phát hiện và cảnh báo các event webhook bị bỏ lỡ
- Xác minh webhook endpoint có thể truy cập và phản hồi chính xác
Credits not applied after downgrade
Credits not applied after downgrade
- Kỳ vọng về proration mode: với
difference_immediately, hạ cấp tạo credit bằng toàn bộ chênh lệch giá của gói, trong khiprorated_immediatelytạo credit theo tỷ lệ dựa trên thời gian còn lại trong chu kỳ - Credit gắn với subscription cụ thể và không thể chuyển giữa các subscription
- Số dư credit không hiển thị trong dashboard khách hàng
- Sử dụng
difference_immediatelycho các lần hạ cấp khi muốn tự động tạo credit - Giải thích cho khách hàng rằng credit được áp dụng cho các lần gia hạn sau của cùng subscription
- Triển khai customer portal để hiển thị số dư credit
- Kiểm tra preview invoice tiếp theo để xem các credit đã áp dụng
Webhook signature verification fails
Webhook signature verification fails
- Webhook secret key không chính xác
- Raw request body bị thay đổi trước khi xác minh chữ ký
- Thuật toán xác minh chữ ký không đúng
- Xác minh bạn đang sử dụng
DODO_WEBHOOK_SECRETchính xác từ dashboard - Đọc raw request body trước bất kỳ JSON parsing middleware nào
- Sử dụng thư viện xác minh webhook tiêu chuẩn cho platform của bạn
- Kiểm thử việc xác minh chữ ký webhook trong môi trường development
Plan change fails with 422 error
Plan change fails with 422 error
- Subscription ID hoặc product ID không hợp lệ
- Subscription không ở trạng thái active
- Thiếu parameter bắt buộc
- Product không khả dụng cho thay đổi gói
- Xác minh subscription tồn tại và đang active
- Kiểm tra product ID hợp lệ và khả dụng
- Đảm bảo đã cung cấp tất cả parameter bắt buộc
- Xem tài liệu API để biết yêu cầu về parameter
Immediate charge fails during plan change
Immediate charge fails during plan change
- Không đủ tiền trong phương thức thanh toán của khách hàng
- Phương thức thanh toán hết hạn hoặc không hợp lệ
- Ngân hàng từ chối giao dịch
- Hệ thống phát hiện gian lận đã chặn khoản phí
- Xử lý phù hợp các event webhook
payment.failed - Thông báo khách hàng cập nhật phương thức thanh toán
- Triển khai retry logic cho các lỗi tạm thời
- Cân nhắc cho phép thay đổi gói dù khoản phí ngay lập tức thất bại
Subscription on hold after plan change
Subscription on hold after plan change
on_holdĐiều gì xảy ra:
Khi tính phí thay đổi gói thất bại, subscription tự động được chuyển sang trạng thái on_hold. Subscription sẽ không tự động gia hạn cho đến khi phương thức thanh toán được cập nhật.Giải pháp: Cập nhật phương thức thanh toán để kích hoạt lại subscriptionĐể kích hoạt lại subscription từ trạng thái on_hold sau khi thay đổi gói thất bại:- Cập nhật phương thức thanh toán bằng Update Payment Method API
- Tự động tạo charge: API tự động tạo charge cho các khoản còn phải trả
- Tạo invoice: Một invoice được tạo cho charge
- Xử lý thanh toán: Khoản thanh toán được xử lý bằng phương thức thanh toán mới
- Kích hoạt lại: Sau khi thanh toán thành công, subscription được kích hoạt lại về trạng thái
active
subscription.on_hold: Subscription bị tạm dừng (nhận được khi tính phí thay đổi gói thất bại)payment.succeeded: Thanh toán các khoản còn phải trả thành công (sau khi cập nhật phương thức thanh toán)subscription.active: Subscription được kích hoạt lại sau khi thanh toán thành công
- Thông báo ngay cho khách hàng khi tính phí thay đổi gói thất bại
- Cung cấp hướng dẫn rõ ràng về cách cập nhật phương thức thanh toán
- Theo dõi event webhook để kiểm tra trạng thái kích hoạt lại
- Cân nhắc triển khai retry logic tự động cho các lỗi thanh toán tạm thời
Update Payment Method API Reference
Kiểm thử implementation
Hãy làm theo các bước sau để kiểm thử kỹ implementation thay đổi gói subscription:Set up test environment
- Sử dụng API key test và product test
- Tạo subscription test với các loại gói khác nhau
- Cấu hình webhook endpoint test
- Thiết lập monitoring và logging
Test different proration modes
- Kiểm thử
prorated_immediatelyvới nhiều thời điểm khác nhau trong chu kỳ billing - Kiểm thử
difference_immediatelycho nâng cấp và hạ cấp - Kiểm thử
full_immediatelyđể đặt lại chu kỳ billing - Kiểm thử
do_not_billcho việc chuyển gói không tính phí/không credit - Xác minh phép tính credit chính xác
Test webhook handling
- Xác minh đã nhận tất cả event webhook liên quan
- Kiểm thử xác minh chữ ký webhook
- Xử lý phù hợp các event webhook trùng lặp
- Kiểm thử các tình huống xử lý webhook thất bại
Test error scenarios
- Kiểm thử với subscription ID không hợp lệ
- Kiểm thử với phương thức thanh toán hết hạn
- Kiểm thử lỗi mạng và timeout
- Kiểm thử khi không đủ tiền
Monitor in production
- Thiết lập cảnh báo cho các lần thay đổi gói thất bại
- Theo dõi thời gian xử lý webhook
- Theo dõi tỷ lệ thay đổi gói thành công
- Xem lại ticket hỗ trợ khách hàng về các vấn đề thay đổi gói
Xử lý lỗi
Xử lý phù hợp các lỗi API thường gặp trong implementation của bạn:HTTP Status Codes
200 OK
200 OK
collect_via_payment_link thành công, request này trả về checkout handle — xem Thu tiền qua Checkout Link. Nếu on_payment_failure=prevent_change, thay đổi gói vẫn ở trạng thái pending cho đến khi thanh toán thành công.400 Bad Request
400 Bad Request
404 Not Found
404 Not Found
409 Conflict
409 Conflict
PendingPlanChangeExists). Với thay đổi theo lịch, hãy hủy bằng DELETE /subscriptions/{subscription_id}/change-plan/scheduled trước khi gửi thay đổi mới. Với thay đổi payment-link đang chờ xử lý, không có endpoint hủy — subscription sẽ chấp nhận request thay đổi gói mới khi khách hàng thanh toán hoặc link hết hạn.422 Unprocessable Entity
422 Unprocessable Entity
collect_via_payment_link — doanh nghiệp chưa bật capability, effective_at không phải immediately hoặc on_payment_failure không phải prevent_change. Xem Yêu cầu.500 Internal Server Error
500 Internal Server Error
Định dạng error response
Bước tiếp theo
- Xem lại Change Plan API
- Tìm hiểu Credit-Based Billing
- Triển khai cảnh báo cho
subscription.on_hold - Xem Webhook Integration Guide