Skip to content

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 creation
params := &customer.CreateRequestParams{
Id: userID,
Email: email,
AutoCollection: enum.AutoCollectionOff, // ← Always OFF for Primer flow
}
// primer.go - Subscription creation
params := &subscription.CreateWithItemsRequestParams{
CustomerId: customerID,
AutoCollection: enum.AutoCollectionOff, // ← Always OFF for Primer flow
}

Rationale

Why OFF for Primer Customers?

  1. Single payment source: Primer is the authoritative payment collector
  2. Prevents race conditions: Primer and Chargebee can’t both attempt charging
  3. Explicit control: We choose when/how to retry failed renewals
  4. 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 = OFF
customer := 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 = OFF
subscription := primer.CreatePendingSubscription(ctx, ...)
retrieved, _ := cb.RetrieveSubscription(subscription.Id)
require.Equal(t, "off", retrieved.Subscription.AutoCollection)