How to Prevent Double Billing During SaaS Exits
Last updated:
Double billing is the most visible and damaging mistake you can make during a subscription migration. When customers see two charges for the same service, trust erodes immediately. This guide covers the strategies and safeguards to prevent it.
How Double Billing Happens
Double billing occurs when both the old and new subscriptions charge the customer for the same billing period. Common causes:
Scenario 1: Premature New Subscription
You create the new subscription before canceling the old one, and both hit their billing dates.
Timeline:
- Day 1: Old subscription bills customer
- Day 5: You create new subscription (bills immediately or on anchor)
- Day 15: Old subscription bills again (wasn’t canceled)
Scenario 2: Wrong Proration Settings
You create the new subscription without disabling proration, causing an immediate prorated charge.
Scenario 3: Race Condition
You cancel the old subscription and create the new one, but the old subscription’s invoice had already been finalized.
Scenario 4: Timezone Confusion
The billing anchor is set incorrectly due to timezone differences, causing the new subscription to bill at the wrong time.
Prevention Strategy 1: Atomic Migration
The safest approach is to treat the migration as an atomic operation:
- Read the old subscription’s current state
- Create the new subscription with matching billing anchor
- Verify the new subscription is active
- Cancel the old subscription only after verification
Never reverse this order. Never cancel the old subscription without confirming the new one exists.
Prevention Strategy 2: Billing Anchor Alignment
When creating the new subscription, set the billing anchor to match the original:
// Read original anchor
const originalAnchor = oldSubscription.billing_cycle_anchor;
// Create new subscription with same anchor
await stripe.subscriptions.create({
customer: newCustomerId,
items: [{ price: newPriceId }],
billing_cycle_anchor: originalAnchor,
proration_behavior: 'none'
});
The proration_behavior: 'none' is critical. Without it, Stripe may charge immediately for a prorated period.
Prevention Strategy 3: Idempotent Execution
Design your migration to be safely re-runnable:
- Before creating a subscription, check if one already exists for that customer
- Track which subscriptions have been migrated in your database
- Use idempotency keys for Stripe API calls
If a migration fails partway, you can safely re-run without creating duplicates.
Prevention Strategy 4: Deferred Cancellation
Instead of immediate cancellation, consider canceling at period end:
await stripe.subscriptions.update(oldSubscriptionId, {
cancel_at_period_end: true
});
This ensures the old subscription doesn’t renew but doesn’t immediately stop service. However, this approach requires careful timing to avoid overlap billing.
Prevention Strategy 5: Invoice Draft Review
Before finalizing the new subscription, review any draft invoices:
const invoices = await stripe.invoices.list({
subscription: newSubscriptionId,
status: 'draft'
});
// Review invoice amount and date before finalizing
This lets you catch unexpected charges before they hit the customer.
What MoveMRR Does
MoveMRR implements all these safeguards automatically:
- Validates state before action: Confirms old subscription status before proceeding
- Sets correct billing anchors: Preserves original renewal dates
- Disables proration: Prevents immediate prorated charges
- Verifies before canceling: Only deactivates old subscriptions after confirming new ones
- Logs everything: Complete audit trail for troubleshooting
- Idempotent design: Safe to re-run without duplicates
Recovery If Double Billing Occurs
If double billing happens despite precautions:
- Refund immediately: Issue a full refund for the duplicate charge
- Communicate proactively: Don’t wait for customers to complain
- Document the incident: Record what happened for process improvement
- Review the audit log: Understand the sequence of events
Pre-Migration Checklist
Before executing any migration:
- Confirmed billing anchor dates for all subscriptions
- Verified API keys have correct permissions
- Tested with a small subset first
- Have refund process ready if needed
- Customer communication drafted