Primer Payment Gateway
ADR 003: Primer as Payment Gateway
Date: 2026-04
Status: Accepted
Context: Billing system requires PCI-compliant payment processing with vaulting support.
Problem
Payment processing requires:
- PCI compliance (no card data in our systems)
- Payment vaulting (save card for renewals)
- 3D Secure support (SCA compliance)
- Multiple payment method support
- Webhook + polling for payment confirmation
Which payment gateway should be the primary collector?
Decision
Use Primer as the primary payment processor for all Primer checkout flows:
- Primer handles payment processing & vaulting
- Chargebee handles subscription & invoice management
- Payments are recorded in Chargebee after Primer confirms
Implementation
// 1. Chargebee subscription created in "future" statesubscription := createPendingSubscription(customerID, itemPriceID, couponCode)// status = "future", start_date = 10 years from now// auto_collection = OFF (Primer handles collection)
// 2. Get Primer client token for frontendclientToken := primer.GetClientToken(apiKey)primerSession := primer.CreateClientSession(clientToken, invoice.ID)return CheckoutResponse{ ClientToken: primerSession.ClientToken, OrderID: invoice.ID,}
// 3. User completes payment in Primer UI// (happens in frontend, not our code)
// 4. Primer webhook or polling detects payment confirmed// (submitpaidinvoice.go or events/poller.go)
// 5. Record payment in Chargebeeprocessor.RecordPayment(ctx, &PaymentRequest{ InvoiceID: invoiceID, TransactionID: primerTransactionID, Amount: invoiceAmount, Source: domain.PaymentSourcePrimerWebhook, // or PaymentSourcePrimerPolling})// → Activates subscription// → Chargebee records payment// → EventPoller then syncs creditsRationale
Why Primer?
- PCI compliance: Primer handles card tokenization; we never touch raw card data
- Vaulting built-in: Cards saved automatically; renewal payments use vaulted tokens
- 3D Secure native: Automatic SCA/3DS handling; compatible with strong customer auth
- Fast checkout: Client-side integration; minimal latency
- Multiple sources: Supports cards, ACH, local payment methods
Why Chargebee for Subscriptions?
- Subscription state machine: Chargebee’s subscription model matches our needs
- Invoice management: Automatic invoice generation for renewals
- Tax calculations: Chargebee handles VAT/sales tax
- Audit trail: All billing actions logged in Chargebee
- Analytics: Revenue recognition, MRR calculations
Separation of Concerns
| Responsibility | Owner |
|---|---|
| Payment processing (card → money) | Primer |
| Subscription state | Chargebee |
| Invoice generation | Chargebee |
| Payment recording | Us (payments/processor.go) |
| Credit calculation | Us (events/credits.go) |
| Analytics notification | Us (events/analytics.go) |
Consequences
Positive
- No PCI burden: Primer handles compliance
- Flexible payment methods: Supports anything Primer supports
- Renewal automation: Chargebee generates invoices; Primer charges vaulted cards
- Audit trail: Chargebee records everything
Negative
- Two-system complexity: Primer ↔ Chargebee reconciliation needed
- Idempotency required: Must handle retried payment confirmations
- Webhook + polling: Need both mechanisms for reliability
- Coordination: Payment → Chargebee → Credit allocation requires orchestration
Testing
// Test: Primer checkout creates pending subscriptionresponse := handler.CreatePrimerCheckout(userID, itemPriceID)require.NotEmpty(t, response.ClientToken)require.NotEmpty(t, response.OrderID)
// Verify subscription is pendingsub := subscriptions.GetActiveSubscription(userID)require.Nil(t, sub) // Not active yetpendingSub := subscriptions.FindPendingSubscription(userID)require.Equal(t, domain.SubscriptionStatusFuture, pendingSub.Status)
// Test: Payment recording activates subscriptionprocessor.RecordPayment(ctx, &PaymentRequest{ InvoiceID: response.OrderID, Amount: expectedAmount, Source: domain.PaymentSourcePrimerWebhook,})
// Verify subscription is now activeactive := subscriptions.GetActiveSubscription(userID)require.Equal(t, domain.SubscriptionStatusActive, active.Status)Related ADRs
- 001: Deferred Activation — Prevents access until Primer confirms payment
- 004: Auto-Collection Off — Why Primer, not Chargebee, collects
- 002: Polling Over Webhooks — Ensures Primer payments are detected