6.2 KiB
Dodo Payments Plan Change & Cancellation Fix
Summary
Fixed the subscription management system to properly handle plan changes and cancellations using the correct Dodo Payments API endpoints.
Issues Fixed
1. Paid-to-Paid Plan Changes
- Problem: When users switched between paid plans (e.g., Starter → Professional), the app was creating a new checkout session instead of using Dodo's Change Plan API
- Solution: Implemented
changeDodoSubscriptionPlan()function that usesPOST /subscriptions/{subscription_id}/change-planwithdifference_immediatelyproration mode - Behavior:
- Upgrades: Charges the price difference for the current billing period
- Downgrades: Applies remaining value as credit to future renewals
- No service interruption during the change
2. Paid-to-Free Downgrades
- Problem: Downgrading to the free "Hobby" plan wasn't properly cancelling the Dodo subscription
- Solution: Enhanced the logic to call
cancelDodoSubscription()when switching from a paid plan to hobby - Behavior: Subscription is cancelled with Dodo, user downgraded to free tier immediately
3. Cancel Button
- Problem: Cancel button was calling general account update endpoint which may not have properly cancelled the Dodo subscription
- Solution: Already uses dedicated
/api/subscription/cancelendpoint which properly callscancelDodoSubscription() - Behavior: Cancels subscription at Dodo and marks billing status as cancelled
Technical Implementation
Server-Side Changes (server.js)
1. New Function: changeDodoSubscriptionPlan()
async function changeDodoSubscriptionPlan(user, newPlan, billingCycle, currency)
- Uses Dodo's official Change Plan API endpoint
- Handles proration automatically
- Proper error handling and logging
- Updates subscription product while maintaining continuity
2. Updated: handleAccountSettingsUpdate()
Enhanced to handle three scenarios:
- Paid → Paid: Calls
changeDodoSubscriptionPlan()with Dodo API - Paid → Free: Calls
cancelDodoSubscription()and downgrades to hobby - Free → Paid: Returns error requiring checkout flow (correct behavior)
Frontend Changes (settings.html)
Updated: saveAccount() Function
Completely restructured to handle different plan change scenarios:
-
Paid-to-Paid Changes:
- Calls
/api/accountwhich uses Change Plan API - Shows appropriate confirmation with proration explanation
- Immediate update without checkout
- Calls
-
Paid-to-Free Downgrades:
- Warns user about feature loss
- Cancels subscription via
/api/account - Immediate downgrade
-
Free-to-Paid Upgrades:
- Redirects to checkout flow (correct)
- Uses
/api/subscription/checkout
-
Other Settings:
- Simple account updates (email, currency)
Dodo Payments API Integration
Change Plan API
Endpoint: POST /subscriptions/{subscription_id}/change-plan
Request Body:
{
"product_id": "prod_xxx",
"quantity": 1,
"proration_billing_mode": "difference_immediately"
}
Proration Mode: difference_immediately
- Upgrades: Immediate charge for price difference
- Downgrades: Credit applied to subscription for future renewals
- Benefits: Simple, fair billing without complex proration calculations
Cancel Subscription API
Endpoints Used:
DELETE /subscriptions/{subscription_id}(immediate cancellation)PATCH /subscriptions/{subscription_id}withcancel_at_next_billing_date: true(fallback)
The implementation tries immediate cancellation first, then falls back to end-of-period cancellation if needed.
User Experience Improvements
Clear Messaging
- Upgrade messages explain immediate charges
- Downgrade messages explain credit application
- Free plan changes warn about feature loss
Seamless Transitions
- Paid-to-paid changes happen instantly without checkout
- No service interruption during plan changes
- Proper proration handled by Dodo
Confirmation Dialogs
- All plan changes require explicit confirmation
- Different messages for upgrades vs downgrades
- Warning icons for cancellations and downgrades
Testing Checklist
- Upgrade (Starter → Professional): Verify immediate charge for difference
- Downgrade (Professional → Starter): Verify credit applied to account
- Cancel (any paid → hobby): Verify Dodo subscription cancelled
- Free to Paid (hobby → starter): Verify checkout flow triggered
- Cancel Button: Verify subscription cancelled at Dodo
- Webhook Handling: Verify
subscription.plan_changedwebhook updates user plan
Environment Variables Required
All existing Dodo environment variables remain the same:
DODO_PAYMENTS_API_KEYDODO_PAYMENTS_ENV(test/live)- Subscription product IDs for each plan/cycle/currency combination
API Endpoints Modified
POST /api/account
Now handles three plan change scenarios differently:
- Paid→Paid: Uses Change Plan API
- Paid→Free: Cancels subscription
- Free→Paid: Returns error requiring checkout
POST /api/subscription/cancel
Already working correctly - cancels Dodo subscription properly
Webhook Events
The system now properly responds to:
subscription.plan_changed: Confirms plan change succeededsubscription.canceled: Confirms cancellationpayment.succeeded: Confirms upgrade paymentsubscription.on_hold: Handles failed plan change payments
Documentation References
Notes
- The
difference_immediatelyproration mode is ideal for most SaaS applications - Credits from downgrades are automatically applied by Dodo to future renewals
- The Change Plan API uses existing payment method - no new payment collection needed
- All plan changes are logged for audit purposes