CRUMB a card from devarno-cloud

Billing Architecture

smo1 intermediate 7 min read

ELI5

SMO1 uses a payment service called Polar to handle subscriptions. Think of Polar as the cash register: it collects money, prints receipts, and tells SMO1 “this person is now a Pro member.” SMO1 listens for these messages via webhooks — automatic notifications sent whenever something changes (payment succeeds, subscription cancels, card expires). To avoid charging someone twice by accident, every webhook has a unique ID that SMO1 remembers forever.

Technical Deep Dive

Billing Flow

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e8f4f8', 'primaryTextColor': '#2d3748', 'primaryBorderColor': '#90cdf4', 'lineColor': '#718096', 'secondaryColor': '#f0fff4', 'tertiaryColor': '#fefcbf'}}}%%
sequenceDiagram
autonumber
actor U as User
participant M as meow-web
participant P as purr-api
participant Pol as Polar
participant DB as PostgreSQL
U->>M: Click "Upgrade to Pro"
M->>P: POST /api/billing/checkout
P->>Pol: Create checkout session
Pol-->>P: Checkout URL
P-->>M: Checkout URL
M-->>U: Redirect to Polar checkout
U->>Pol: Enter payment details
Pol->>Pol: Process payment
Pol->>P: Webhook: subscription.created
P->>DB: UPSERT subscription
P-->>Pol: 200 OK
Pol-->>U: Payment confirmation
U->>M: Return to dashboard
M->>P: GET /api/user
P-->>M: Updated tier: pro

Subscription Tiers

TierPriceLinks/monthRate limitFeatures
free$01060/hrBasic links, basic analytics
pro$9/mo or $90/yr100600/hrCustom slugs, QR codes, advanced analytics
business$49/mo or $490/yr1,0006,000/hrTeams, API access, priority support
early_adopterLegacy501,000/hrGrandfathered users from beta

Polar Integration

SDK: Polar Go SDK (github.com/polarsource/polar-go)

Operations:

  • CreateCheckout — generates a Polar-hosted checkout URL
  • GetSubscription — retrieves current subscription status
  • CreateCustomerPortal — generates a self-service portal URL (update card, cancel, view invoices)
  • CancelSubscription — initiates cancellation (end of period)
  • ReactivateSubscription — reverses a pending cancellation

Webhook Processing

Endpoint: POST /api/webhooks/polar

Security:

  1. Verify HMAC-SHA256 signature of the request body against POLAR_WEBHOOK_SECRET
  2. Parse event JSON
  3. Check webhook_events table for existing event_id
  4. If exists: return 200 (idempotent, already processed)
  5. If new: process event, insert event_id into webhook_events, return 200

Webhook types handled:

EventAction
subscription.createdCreate subscription record, set tier
subscription.activeConfirm activation, update status
subscription.updatedUpdate tier, period end, cancel flag
subscription.canceledMark as canceled, schedule downgrade
subscription.revokedImmediate revocation, downgrade to free

Idempotency Table

CREATE TABLE webhook_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id TEXT UNIQUE NOT NULL, -- Polar's event ID
event_type TEXT NOT NULL,
processed_at TIMESTAMPTZ DEFAULT NOW(),
payload JSONB
);

The event_id uniqueness constraint guarantees exactly-once processing even if Polar retries the webhook.

Tier Update Logic

When a webhook is processed, purr-api maps the Polar price_id to a SMO1 tier:

var priceToTier = map[string]string{
"price_pro_monthly": "pro",
"price_pro_yearly": "pro",
"price_business_monthly": "business",
"price_business_yearly": "business",
}

The user’s subscription_tier, subscription_status, subscription_period_end, and polar_subscription_id are updated atomically.

Development Fallback

When POLAR_API_KEY is not configured (kitten environment):

  • Checkout creation returns a fake URL (https://example.com/checkout/fake)
  • Webhook verification is skipped
  • All users remain on free tier unless manually overridden in the database

This allows full frontend development without a live payment provider.

Key Terms

  • Polar → Payment provider (alternative to Stripe) with subscription management, checkout, and customer portal
  • Webhook → HTTP callback sent by Polar to purr-api when subscription events occur
  • Idempotency → Guarantee that processing the same event twice has the same effect as processing it once
  • HMAC-SHA256 → Cryptographic signature verifying the webhook genuinely came from Polar
  • Customer portal → Self-service URL where users manage cards, view invoices, and cancel subscriptions
  • Price-to-tier mapping → Configuration mapping Polar price IDs to SMO1 subscription tiers

Q&A

Q: Why Polar instead of Stripe? A: Polar is designed for SaaS subscriptions with simpler pricing models and lower fees for small businesses. It also provides built-in customer portals and webhook handling comparable to Stripe.

Q: What happens if a webhook fails? A: Polar retries with exponential backoff. purr-api returns 200 only after successful processing. If processing fails (e.g., database error), purr-api returns 5xx, and Polar retries. The idempotency table ensures retries are safe.

Q: Can a user have multiple active subscriptions? A: No. The schema enforces one subscription per user. Upgrading from Pro to Business cancels the old subscription and creates a new one.

Q: How does the frontend know when a user’s tier changes? A: The dashboard polls /api/user on navigation. After a successful checkout, the user is redirected back to the dashboard, which re-fetches user data and shows the updated tier badge.

Examples

Think of the billing system like a gym membership:

  • Polar is the gym’s corporate office — they handle payments, print membership cards, and manage contracts
  • Checkout is the front desk sign-up form — you fill in your details and hand over your credit card
  • Webhook is the email the gym sends the local branch saying “Alex just signed up for Premium”
  • Idempotency is the front desk checking their logbook: “We already processed Alex’s sign-up email yesterday, so we ignore the duplicate”
  • Customer portal is the member website where you update your card, download invoices, or cancel your membership
  • Tier mapping is the price list on the wall: $30/month = Basic, $60/month = Premium, $100/month = Elite

neighbors on the map