Auto-Collection Off
ADR 004: Chargebee Auto-Collection Disabled
Date: 2026-04
Status: Accepted
Context: Payment processing split between Primer and Chargebee requires explicit control over payment collection.
Problem
Chargebee’s auto_collection setting controls whether Chargebee attempts payment collection:
- ON: Chargebee automatically charges customers using saved payment methods
- OFF: Chargebee does not initiate charges; external system (Primer) handles it
Which should be the default? How do we prevent duplicate charges?
Decision
Set auto_collection = OFF for all Primer-based subscriptions.
This ensures:
- Primer is the only system that initiates payment collection
- Chargebee records payments but never charges independently
- Prevents double-charging (Primer + Chargebee both attempting payment)
Implementation
// primer.go - Customer creationparams := &customer.CreateRequestParams{ Id: userID, Email: email, AutoCollection: enum.AutoCollectionOff, // ← Always OFF for Primer flow}
// primer.go - Subscription creationparams := &subscription.CreateWithItemsRequestParams{ CustomerId: customerID, AutoCollection: enum.AutoCollectionOff, // ← Always OFF for Primer flow}Rationale
Why OFF for Primer Customers?
- Single payment source: Primer is the authoritative payment collector
- Prevents race conditions: Primer and Chargebee can’t both attempt charging
- Explicit control: We choose when/how to retry failed renewals
- Vaulting consistency: Primer vaults cards; Chargebee should not use them independently
Chargebee Role Under auto_collection = OFF
Chargebee still:
- Generates invoices for renewal periods
- Tracks invoice status (
payment_due,paid,voided) - Maintains payment history
- Calculates MRR and revenue metrics
- But does NOT attempt payment collection
When Would auto_collection = ON?
For Chargebee-only subscriptions (not Primer):
- User manually created subscription in Chargebee (rare)
- Internal testing/admin operations
- Legacy integrations
Consequences
Positive
- No double-charging: Chargebee and Primer never conflict
- Clear responsibility: Primer owns payment collection
- Renewal control: We decide retry strategy via
ListUnpaidRenewalInvoices()
Negative
- Manual renewal tracking: We must poll for unpaid renewal invoices
- Retry complexity: We implement dunning retry logic (not Chargebee’s built-in)
- Configuration check: Easy to misconfigure; must verify setting in Chargebee UI
Testing
// Test: All Primer customers have auto_collection = OFFcustomer := primer.CreateOrGetCustomer(ctx, userID, email)cb := chargebee.New()retrieved, _ := cb.RetrieveCustomer(customer.Id)require.Equal(t, "off", retrieved.Customer.AutoCollection)
// Test: All Primer subscriptions have auto_collection = OFFsubscription := primer.CreatePendingSubscription(ctx, ...)retrieved, _ := cb.RetrieveSubscription(subscription.Id)require.Equal(t, "off", retrieved.Subscription.AutoCollection)Related ADRs
- 003: Primer Payment Gateway — Why Primer is the payment authority
- 006: Saga Pattern — How we handle failed payment recovery without Chargebee’s auto-retry