const preview = await client.subscriptions.previewChangePlan('sub_123', { product_id: 'prod_pro', quantity: 1, proration_billing_mode: 'prorated_immediately'});// Show customer the charge before confirmingconsole.log('Immediate charge:', preview.immediate_charge.summary);console.log('New plan details:', preview.new_plan);
preview = client.subscriptions.preview_change_plan( subscription_id="sub_123", product_id="prod_pro", quantity=1, proration_billing_mode="prorated_immediately")# Show customer the charge before confirmingprint("Immediate charge:", preview.immediate_charge.summary)print("New plan details:", preview.new_plan)
Upgrade: Basic ($30) → Pro ($80) with prorated_immediately
Step 1: Calculate unused credit from current plan Unused days = 15 out of 30 days Credit = $30 × (15/30) = $15.00Step 2: Calculate prorated cost of new plan Remaining days = 15 out of 30 days New plan cost = $80 × (15/30) = $40.00Step 3: Calculate immediate charge Charge = New plan cost − Credit Charge = $40.00 − $15.00 = $25.00→ Customer pays $25.00 now→ Next renewal (Feb 1): $80.00/month
Downgrade: Pro ($80) → Starter ($20) with prorated_immediately
Step 1: Calculate unused credit from current plan Unused days = 15 out of 30 days Credit = $80 × (15/30) = $40.00Step 2: Calculate prorated cost of new plan Remaining days = 15 out of 30 days New plan cost = $20 × (15/30) = $10.00Step 3: Calculate credit balance Credit = $40.00 − $10.00 = $30.00→ No charge — $30.00 credit added to subscription→ Credit auto-applies to future renewals→ Next renewal (Feb 1): $20.00 − $30.00 credit = $0.00→ Following renewal (Mar 1): $20.00 − $10.00 remaining credit = $10.00
Upgrade: Basic ($30) → Pro ($80) with difference_immediately
Immediate charge = New plan price − Old plan price = $80 − $30 = $50.00→ Customer pays $50.00 now (regardless of cycle position)→ Next renewal (Feb 1): $80.00/month
Upgrade: Basic ($30) → Pro ($80) with full_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
Mid-cycle upgrade with add-ons using prorated_immediately
Current: Basic plan ($30/month), no add-onsNew: 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.00Step 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.00Step 3: Immediate charge Charge = $55.00 − $15.00 = $40.00→ Customer pays $40.00 now→ Next renewal: $80.00 + $30.00 = $110.00/month
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 });}
{ "error": { "code": "subscription_not_found", "message": "The subscription with ID 'sub_123' was not found", "details": { "subscription_id": "sub_123" } }}