From 83d3b513be8725b9720785f74fc12ccfa3742b98 Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Thu, 17 Sep 2026 20:48:35 +1000 Subject: [PATCH 01/12] new drafts --- README.md | 17 +- how-to/entitlement-checks.md | 2 +- how-to/sync-chargebee-entities.md | 330 ++++++++++++++++++++ how-to/usage-based-billing.md | 283 +++++++++++++++++ pointer/public/information-architecture.png | Bin 0 -> 366806 bytes 5 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 how-to/sync-chargebee-entities.md create mode 100644 how-to/usage-based-billing.md create mode 100644 pointer/public/information-architecture.png diff --git a/README.md b/README.md index 5e403d0..92eefb5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Chargebee Reference Architecture +**Status**: This is a work in progress - please expect continuous updates until it's marked ready for public consumption. + +--- + @@ -14,7 +18,7 @@ The purpose of this repository is to showcase the best practices around integrat ## Audience -- Architects and solution consultants who are looking for Chargebee's recommended practices when integrating various products with their platform +- Architects and solution consultants who are looking for Chargebee's recommended practices when integrating our products with their platform - Developers who are looking to validate their integration code and ensure all edge-cases are covered @@ -42,12 +46,20 @@ Each how-to is broken down into self-contained topics which addresses a specific These topics are written in plain markdown with mermaid diagrams wherever possible. It is written and edited by humans, but can be consumed by agents as well. +## Where it fits in + +While the Chargebee [docs](https://chargebee.com/docs) and [apidocs](https://apidocs.chargebee.com) serve as the main source of reference from the product and engineering teams, they aren't structured for consuming topics in thin vertical slices - something the content in this repo aims to do. + +![information architecture](./pointer/public/information-architecture.png) + ### Index | Topic | Status | Last Updated | |-------|--------|--------------| | [Integrating Chargebee webhooks](./how-to/webhooks.md) | In Review | | | [Enforcing entitlement checks](./how-to/entitlement-checks.md) | In Review | | +| [Sync Chargebee entities](./how-to/sync-chargebee-entities.md) | Draft | | +| [Usage based billing](./how-to/usage-based-billing.md) | Draft | | ## Reference App @@ -62,8 +74,7 @@ These topics are written in plain markdown with mermaid diagrams wherever possib It was built from the ground up with the help of coding agents and Chargebee's official libraries and SDK. Although a considerable amount of code was generated via LLMs, the architecture and design of the app, and the how-to topics themselves are completely written and reviewed by humans. -The purpose of building pointer along with the how-to topics is to ensure that they cover practical aspects of integrating Chargebee with a monetizable product. While the Chargebee [docs](https://chargebee.com/docs) and [apidocs](https://apidocs.chargebee.com) serve as the main source of reference from the product and engineering teams, they don't offer a comprehensive, traceable view of how to integrate a Chargebee platform feature end-to-end. - +The purpose of building pointer along with the how-to topics is to ensure that they cover practical aspects of integrating Chargebee with a monetizable product. We aim to continuously evolve and expand the reference architecture as we build out Pointer with additional features - this mimics how an actual product team would go about integrating billing/subscription/entitlement/usage with their app. A high level overview of the pointer architecture is shown below. More details on the tech stack, components, infrastructure are detailed in [ARCHITECTURE.md](./pointer/ARCHITECTURE.md) diff --git a/how-to/entitlement-checks.md b/how-to/entitlement-checks.md index a895290..67325da 100644 --- a/how-to/entitlement-checks.md +++ b/how-to/entitlement-checks.md @@ -302,7 +302,7 @@ async function loadCustomerEntitlements(customerId: string) { * If `entitlement.is_enabled === false`, the customer _does not_ have access to the entitlement, regardless of the value returned * Customer-level entitlements do not carry over to a new subscription, so a plan change will not move them -## Demo App Implementation Notes +## See It Running In The Demo App Entitlements are used across the app to provide various features to the end-users. For example, a user with a free subscription gets access to a smaller list of LLM models along with a limited set of tokens and requests per month. Subscription upgrades trigger the refresh of the customer's entitlement snapshot, and the hot-path avoids hitting the Chargebee API. diff --git a/how-to/sync-chargebee-entities.md b/how-to/sync-chargebee-entities.md new file mode 100644 index 0000000..4af310c --- /dev/null +++ b/how-to/sync-chargebee-entities.md @@ -0,0 +1,330 @@ +# How To Sync And Reconcile Chargebee Entities + +Once webhooks are configured, your application needs a local database copy of billing state to authorize requests, enforce plan tiers, and render account dashboards without calling Chargebee on every HTTP request. This topic covers: + +* Keeping a fast local mirror of customers, subscriptions, and catalog entities in sync with Chargebee +* Processing updates safely in a background worker using resource_version guards +* Handling subscription upgrade races between synchronous checkout mutations and asynchronous webhooks +* Recovering from missed webhook events or cold starts using event replay and list APIs + +**Important**: Chargebee is the single source of truth for billing state. Your app maintains a read-only mirror. Application requests read from the local mirror, while state-changing operations route to the Chargebee API first. + +## Setup + +* Product Catalog 2.0 configured with items, item prices, and entitlements +* Webhook endpoint enqueuing events into a durable queue (see webhook guide) +* Background worker consuming events and writing to your local database +* A tracking table (e.g. chargebee_resource_version) storing the latest applied version per resource + + +## High Level Architecture + +```mermaid + sequenceDiagram + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + participant App + participant Queue + participant Worker + participant DB + Chargebee->>App: Webhook event (customer, subscription) + App->>Queue: Enqueue event + App-->>Chargebee: 200 OK + Queue->>Worker: Deliver event + Worker->>DB: Check stored resource_version + alt Incoming version is newer + Worker->>DB: Upsert entity & update version cursor + Worker-->>Queue: Ack + else Stale or duplicate event + Worker-->>Queue: Ack (no-op) + end +``` + +### Flow + +1. An event occurs in Chargebee (subscription created, plan changed, invoice paid) +2. Chargebee posts the webhook to your application endpoint +3. The endpoint enqueues the message durably and returns 200 OK +4. The background worker picks up the message and inspects resource_version +5. If the version is newer than the database mirror, the worker upserts the row and commits the new version cursor + + +## 1. How Entity Mirroring Works + +Your local database acts as a read mirror, not an authority. Mirror only entities required to authenticate users, check permissions, and render core UI. + +| Entity | What to mirror | Why mirror locally | +|-------------------|--------------------|-----------------| +| Customer | id, email, app user_id / org_id | Joins billing entities to your application accounts | +| Subscription | id, customer_id, status, current_term_end, item_price_id | Gates feature access, checks renewal status, displays active plan | +| Catalog | item_id, item_price_id, pricing tiers | Renders pricing pages and maps plans to feature gates | +| Invoice / Payment | Fetch on demand (or mirror id, status, amount if billing history is in-app) | Infrequently accessed; can be queried via API or hosted portal | + +```typescript +// Read access tier from local mirror without calling Chargebee +const subscription = await db.subscription.findUnique({ + where: { userId: session.userId, status: "active" }, +}); +if (!subscription) { + throw new ForbiddenError("Active subscription required"); +} +``` + +✅ Recommended: Store external IDs (chargebeeCustomerId, chargebeeSubscriptionId) on user records as join keys + +✅ Recommended: Skip mirroring resources your app only accesses through the hosted billing portal (e.g., historical invoice PDFs) + +⚠️ Not recommended: Treating local database columns as authoritative for subscription status; local edits that bypass Chargebee cause data drift + + +## 2. What The Worker Does + +The background worker is the sole writer for the entity mirror. It processes messages asynchronously to decouple third-party webhook delivery from user-facing traffic. + +```mermaid +sequenceDiagram + participant Queue + participant Worker + participant DB + Queue->>Worker: subscription_changed + Worker->>DB: Read applied resource_version + alt Stored version >= incoming version + Worker-->>Queue: Ack (skip stale) + else Stored version < incoming version + Worker->>DB: Upsert subscription row + Worker->>DB: Commit new resource_version + Worker-->>Queue: Ack + end + ``` + +The worker executes this sequence for each message: + +1. Extract entity payloads from event.content (e.g., content.subscription, content.customer) +2. Verify freshness against the stored resource_version for that specific resource +3. Upsert entity fields into local tables inside a database transaction +4. Advance the version cursor in the tracking table +5. Acknowledge the queue message + +```typescript +// Worker pipeline: guard, upsert, commit, ack +if (await isEventStale(event)) return ack(); +await db.transaction(async (tx) => { + await upsertSubscription(tx, event.content.subscription); + await commitResourceVersion(tx, "subscription", event.content.subscription); +}); +await ack(); +``` + +✅ Recommended: Commit the entity update and the version cursor in the same database transaction + +✅ Recommended: Acknowledge the queue message only after the database transaction succeeds + +⚠️ Not recommended: Running database upserts inline within the HTTP webhook endpoint + + +## 3. Handling Ordering With resource_version + +Chargebee webhooks can arrive out of order. Every versioned object inside event.content carries a resource_version (a millisecond timestamp counter) that increments on every mutation. + +```mermaid +sequenceDiagram + participant Queue + participant Worker + participant DB + Note over Queue,Worker: Event A (rv=100) delayed in network
Event B (rv=200) arrives first + Queue->>Worker: Event B (rv=200) + Worker->>DB: Stored is 0 -> Upsert entity & set rv=200 + Worker-->>Queue: Ack + Queue->>Worker: Event A (rv=100) + Worker->>DB: Stored is 200 -> 100 <= 200 -> Skip + Worker-->>Queue: Ack (no-op) + ``` + + Rules for version tracking: + + • Track version per resource type and ID (subscription:sub_123, customer:cust_456). Versions advance independently across different entities. + • Do not rely on event occurred_at for deduplication; occurred_at represents when the event triggered, while resource_version represents the entity mutation state. + +```typescript +// Check if incoming version is older than stored version +const stored = await getStoredVersion(resourceType, resourceId); +if (stored && incomingResourceVersion <= stored.version) { + return true; // Stale event; safe to ignore +} +``` + +```sql +-- Track monotonic high-water marks per resource +INSERT INTO chargebee_resource_version (resource_key, version, updated_at) +VALUES ($1, $2, NOW()) +ON CONFLICT (resource_key) DO UPDATE +SET version = EXCLUDED.version, updated_at = NOW() +WHERE EXCLUDED.version > chargebee_resource_version.version; +``` + +✅ Recommended: Compare resource_version for every individual entity in event.content + +⚠️ Not recommended: Maintaining a single global version cursor for the entire site + +## 4. Handling Subscription Upgrades (Preventing Split-Brain) + +When a customer upgrades in the app, two paths execute: the synchronous API mutation (or checkout redirect) and the asynchronous subscription_changed webhook. If the app writes locally before the webhook arrives, writes can race and corrupt state. + +```mermaid +sequenceDiagram + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + participant Client + participant App + participant DB + participant Worker + Client->>App: Upgrade plan + App->>Chargebee: Update subscription via API + Chargebee-->>App: Updated subscription (rv=2000) + App->>DB: Optimistic upsert (rv=2000) + App-->>Client: 200 OK (immediate access) + Chargebee-)Worker: Webhook subscription_changed (rv=2000) + Worker->>DB: Check version (stored rv=2000) + Worker-->>Worker: Incoming <= stored -> No-op (Ack) +``` + +To avoid split-brain and provide immediate UI feedback: + +1. Mutate via Chargebee first: Call Chargebee's subscription update API. +2. Apply immediate response: Upsert the local database row using the response body and record its resource_version. +3. Allow webhook to no-op: When the corresponding webhook arrives, the worker sees incoming.resource_version <= stored.resource_version and cleanly discards it. + +```typescript +// Synchronous upgrade mutation +const { subscription } = await chargebee.subscription.updateForItems(subId, { + subscription_items: [{ item_price_id: newPlanPriceId }], +}); +// Update local mirror using API response; webhook will no-op later +await syncSubscriptionMirror(subscription); +``` + +✅ Recommended: Update local mirror state using the Chargebee API response immediately after checkout/upgrade mutations + +⚠️ Not recommended: Updating local subscription status optimistically before Chargebee responds + +⚠️ Not recommended: Waiting purely for the webhook after checkout without refreshing locally, which causes visible upgrade lag for the user + + +## 5. Reconciling Gaps After Outages + +Webhooks can be missed due to queue outages, bad deployments, or networking incidents. Chargebee retries webhooks for up to 2 days, but extended downtime requires proactive reconciliation. + +```mermaid +sequenceDiagram + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + participant Reconciler + participant DB + Note over Reconciler,DB: Scheduled or post-incident reconciliation + Reconciler->>DB: Get high-water mark timestamp + Reconciler->>Chargebee: List events (occurred_at[after] = timestamp) + loop For each event + Reconciler->>DB: Upsert entity if rv > stored_rv + end + Reconciler->>DB: Update high-water mark +``` + +Use two reconciliation strategies depending on the gap window: + +### Option 1: Event Replay (Gaps < 90 Days) + +Chargebee retains events for 90 days. Walk the List events API (https://apidocs.chargebee.com/docs/api/events/list-events) and pass each event directly into the worker's processing logic. + +```typescript +// Replay events from last known cursor +const events = await chargebee.event.list({ + "occurred_at[after]": lastSyncTimestamp, + "sort_by[asc]": "occurred_at", + limit: 100, +}); +for (const entry of events.list) { + await processWebhookEvent(entry.event); +} +``` + +### Option 2: Full Entity Backfill (Gaps > 90 Days or Cold Starts) + +If event retention has lapsed, paginate the resource list endpoints directly using next_offset. + +```typescript +// Paginating current entity snapshots +let offset: string | undefined; +do { + const result = await chargebee.subscription.list({ limit: 100, offset }); + for (const entry of result.list) { + await syncSubscriptionMirror(entry.subscription); + } + offset = result.next_offset; +} while (offset); +``` + +✅ Recommended: Run a daily reconciliation cron to catch any drifted rows + +✅ Recommended: Reuse the same idempotent worker logic for event replay and webhooks + +⚠️ Not recommended: Deleting local rows during reconciliation if an event is missing; verify current status with retrieve first + + +## 6. Cold Start And Backfill + +When standing up a fresh environment or recovering an empty database: + +```mermaid + sequenceDiagram + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + participant Script as Bootstrap Script + participant DB + Script->>Chargebee: List items & item prices + Chargebee-->>Script: Catalog entities + Script->>DB: Upsert catalog mirror + Script->>Chargebee: Paginate customers & subscriptions + Chargebee-->>Script: Customer & subscription snapshots + Script->>DB: Upsert entities & seed resource_version cursors +``` + +Bootstrap sequence: + +1. Pull Catalog: Paginate items and item prices from Chargebee and populate catalog tables. +2. Pull Active Subscriptions: Paginate customers and subscriptions. Link Chargebee customers to internal users via metadata or email. +3. Seed Version Cursors: Populate the chargebee_resource_version table with current timestamps from the API response so incoming webhooks do not replay older state. +4. Enable Webhook Ingress: Enable worker processing. Steady-state updates take over seamlessly. + +✅ Recommended: Seed version cursors during backfill to avoid unnecessary worker replay + +⚠️ Not recommended: Relying on webhooks alone to populate state on an empty database + +## See It Running In The Demo App + +* [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts) — SQS message consumer pipeline: runs stale checks, enforces parent dependencies, applies upserts, and commits version cursors. +* [`pointer/lib/webhooks/webhook-guards.ts`](../pointer/lib/webhooks/webhook-guards.ts) — Implements isEventStale, versionedResources, and commitVersions backed by the chargebee_resource_version table. +* [`pointer/lib/subscriptions.ts`](../pointer/lib/subscriptions.ts) — Queries active subscription status and item price limits from PostgreSQL for incoming AI requests. +* [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts) — Defines and deploys the Product Catalog 2.0 structure (items, prices, and entitlements) into Chargebee. + + +## Go-Live Checklist + +- [ ] Does the app treat Chargebee as the single source of truth, routing mutations to Chargebee before updating local state? + +- [ ] Is every entity upsert guarded by a check against that resource's stored resource_version? + +- [ ] Does the worker commit the entity changes and the version high-water mark inside the same database transaction? + +- [ ] Does the worker acknowledge queue messages only after successful persistence? + +- [ ] Are subscription upgrades applied to the local mirror immediately using the API response to eliminate UI lag? + +- [ ] Does a reconciliation job exist to detect and heal gaps via the List events API (for gaps < 90 days) or resource list APIs? + +- [ ] Are all catalog entities mapped to Product Catalog 2.0 (items and item_prices) rather than legacy plans? + +- [ ] Does the cold-start backfill seed resource_version tracking records so initial webhooks do not process as duplicate writes? diff --git a/how-to/usage-based-billing.md b/how-to/usage-based-billing.md new file mode 100644 index 0000000..14fef31 --- /dev/null +++ b/how-to/usage-based-billing.md @@ -0,0 +1,283 @@ +# How To Implement Usage-Based Billing + +Turning raw product activity (API hits, LLM tokens, compute hours) into accurate Chargebee invoices requires decoupling your high-volume product traffic from billing ingestion. This topic covers: + +* Recording usage on the hot path without adding latency or risking data loss +* Batching and synchronizing usage events to Chargebee's Advanced Usage-Based Billing (UBB) API +* Enforcing real-time quotas locally while letting Chargebee handle period-end rating and invoicing +* Reconciling invoice lines back to your local raw event history + +**Important**: Chargebee is the system of record for billing aggregation and invoicing, but your app is the authority for real-time enforcement. Never call Chargebee's API inside customer-facing request paths. + +## Setup + +- Product Catalog 2.0 with [metered features](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/defining-metered-features) and linked pricing configured +- A fast local buffer (Redis Stream or durable message queue) on the request path +- A local event store (PostgreSQL) for audit trails and billing reconciliation +- A background worker to flush event batches to Chargebee's [Usage Events API](https://apidocs.chargebee.com/docs/api/usage_events) + +## 1. How Usage Ingestion Works +When a customer performs a billable action, record the event locally and return immediately. Ingesting synchronously to Chargebee adds latency and fails customer requests if the billing API is slow. + +```mermaid +sequenceDiagram + participant Client + participant App + participant Buffer as Redis Stream / Queue + + Client->>App: API request (tokens / compute) + App->>App: Execute action + App->>Buffer: Buffer usage event (deduplication_id) + App-->>Client: 200 OK (immediate response) +``` + +### Flow +1. Customer executes an action (e.g. LLM completion, file upload). +2. The application computes usage and assigns a stable `deduplication_id` (such as a UUIDv7 trace ID). +3. The event is written to a fast local buffer (sub-millisecond). +4. The API response returns to the customer without waiting on third-party networks. + +```typescript +// Fast, non-blocking usage recording on the request path +await stream.xadd("usage_events", "*", "event", JSON.stringify({ + subscription_id: subscriptionId, + usage_timestamp: Date.now(), + deduplication_id: requestId, + properties: { input_tokens: 150, output_tokens: 420 } +})); + ``` +✅ **Recommended**: Buffer usage events in-memory or in an append-only stream (sub-millisecond) before returning to the caller. + +⚠️ **Not recommended**: Calling Chargebee's ingest endpoint inline during a user request. External latency degrades user experience and creates cascading timeouts. + + +## 2. What The Worker Does +The background worker drains buffered events, writes them to your local database for reconciliation, and flushes batches to Chargebee. + +```mermaid +sequenceDiagram + participant Buffer as Stream / Queue + participant Worker + participant DB as Local Store + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + + Buffer->>Worker: Pull batch (up to 500) + Worker->>DB: Archive batch locally + Worker->>Chargebee: POST /usage_events/batch + Chargebee-->>Worker: 200 Accepted + Worker->>Buffer: Ack / trim processed entries +``` + +### Flow + +1. Read a batch of up to 500 events from the buffer. +2. Persist the raw events to PostgreSQL (the reconciliation source of truth). +3. Check the 12-hour timestamp cutoff; dead-letter expired events. +4. Call Chargebee's `batchIngest` endpoint. +5. Acknowledge and delete entries from the buffer only after Chargebee accepts the batch. + +```typescript +// Flush up to 500 events per batch and mark synced +await chargebee.usageEvent.batchIngest({ events: batch.map((e) => e.payload) }); +await db.usageEvents.markSynced(batch.map((e) => e.deduplicationId)); +await stream.xack("usage_events", "worker_group", batch.map((e) => e.streamId)); +``` + +**Key points**: + +* Chargebee's batch endpoint accepts a maximum of **500 events** per request. +* Acknowledge messages only after Chargebee returns `200`. +* If Chargebee is temporarily unavailable, leave messages on the buffer and retry with exponential backoff. + +✅ **Recommended**: Keep batches large (up to 500) to minimize HTTP round-trips and preserve API quota. + +⚠️ **Not recommended**: Acknowledging the queue before Chargebee returns `200`. A worker crash mid-flight permanently loses billable revenue. + + +## 3. Enforcing Limits: App vs Chargebee +Chargebee calculates usage asynchronously for periodic billing. It cannot evaluate real-time rate limits or quota boundaries at millisecond request speeds. + +```mermaid +sequenceDiagram + participant Client + participant Gateway as App / Gate + participant Cache as Redis (Local Quota) + + Client->>Gateway: API Request + Gateway->>Cache: Increment & check current quota + alt Quota available + Cache-->>Gateway: OK (within allowance) + Gateway-->>Client: 200 OK (process request) + else Quota exceeded + Cache-->>Gateway: Limit exceeded + Gateway-->>Client: 429 Too Many Requests + end +``` + +### Flow + +1. Store allowance thresholds in your local cache (synced via plan entitlements). +2. Increment and check customer counters in Redis on every request. +3. Reject or throttle immediately with `429` or `402` when the limit is breached. +4. Let Chargebee rate overages or aggregate totals at the end of the billing cycle. + +```typescript +// Real-time quota check in local cache; zero external API latency +const current = await redis.incrby(`quota:${subscriptionId}:tokens`, requestedTokens); +if (current > allowance) { + throw new QuotaExceededError("Daily token limit exceeded. Upgrade or add credits."); +} +``` + +✅ **Recommended**: Enforce hard limits and concurrency caps against local counters in Redis. + +⚠️ **Not recommended**: Polling Chargebee's `usage_charges` endpoint on each request to determine whether to allow user actions. + +## 4. Handling Upgrades and Overages +When a customer exhausts their included allowance, they either purchase credit top-ups, upgrade their tier, or spill over into metered pay-as-you-go pricing. + +```mermaid +sequenceDiagram + participant Customer + participant App + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + + Customer->>Chargebee: Complete checkout (Upgrade / Top-up) + Chargebee-->>Customer: Redirect with checkout success + Customer->>App: Return to app + App->>Chargebee: Fetch updated entitlements + Chargebee-->>App: New limits + App->>App: Reset local Redis counters + App-->>Customer: Feature unlocked immediately +``` + +### Flow +1. Customer reaches quota limit; UI displays an upgrade or credit pack prompt. +2. Customer completes checkout via Chargebee Checkout or customer portal. +3. On redirect back to the app, the app immediately fetches updated entitlements from Chargebee. +4. Reset the local usage counters in Redis so the customer can resume work without waiting for webhooks. + +```typescript +// Optimistic entitlement refresh on checkout completion +await chargebee.subscriptionEntitlement.subscriptionEntitlementsForSubscription(subId); +await redis.del(`quota:${subId}:tokens`); +``` + +✅ **Recommended**: Reset local enforcement counters immediately on the checkout return handler. + +⚠️ **Not recommended**: Waiting for the `subscription_entitlements_updated` webhook to unlock limits. Webhook delivery delays make users wait minutes after paying. + +## 5. Handling Duplicate and Late Events +Networks drop and workers retry. Chargebee deduplicates using a composite key: `(subscription_id, usage_timestamp, deduplication_id)`. + +```sql +-- Local idempotent table prevents double-counting inside your database +INSERT INTO usage_events (subscription_id, usage_timestamp, deduplication_id, properties) +VALUES ($1, $2, $3, $4) +ON CONFLICT (deduplication_id) DO NOTHING; +``` + +### The 12-Hour Backdating Window + +Chargebee's Usage Events API enforces a strict constraint: `usage_timestamp` must be within the **last 12 hours**. +| Scenario | Behavior | Resolution | +|:---|:---|:---| +| **Worker retry (< 12 hours)** | Chargebee deduplicates using `deduplication_id`. | Safe to retry entire batch. | +| **Worker backlog (> 12 hours)** | Chargebee rejects the event with an error. | Split expired events, move to dead-letter storage, and ingest via Chargebee S3/file bulk import. | +| **Duplicate delivery** | Chargebee ignores the replayed event. | Send stable idempotency key on all requests. | + +```typescript +// Drop or route expired events before sending batch to Chargebee +const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000; +const isExpired = (event: UsageEvent) => (Date.now() - event.usage_timestamp) > TWELVE_HOURS_MS; +``` + +✅ **Recommended**: Use a deterministic UUID or request trace ID as your `deduplication_id`. + +⚠️ **Not recommended**: Infinitely retrying batches containing events older than 12 hours. The entire batch will fail repeatedly. + + +## 6. Invoicing and Period-Close Reconciliation +At billing period close, Chargebee aggregates ingested events according to your metered feature SQL definitions and generates an invoice line item. + +```mermaid +sequenceDiagram + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + participant Webhook as Webhook Worker + participant DB as Local Store + + Chargebee->>Webhook: invoice_generated webhook + Webhook->>Chargebee: GET /usage_summaries (period window) + Webhook->>DB: Query local raw event sum (period window) + alt Sum matches within tolerance + Webhook->>DB: Mark invoice reconciled + else Drift detected + Webhook->>DB: Flag billing drift & alert on-call + end +``` + +### Flow + +1. Receive the `invoice_generated` webhook event. +2. Query Chargebee's `usage_summaries` API for the invoiced subscription and period dates. +3. Compute the sum of raw events stored in PostgreSQL for the same period. +4. Compare the quantities and trigger an alert if drift exceeds a safety threshold. + +```typescript +// Verify billed invoice quantity matches local event totals +const localSum = await db.getEventSum(subscriptionId, featureId, periodStart, periodEnd); +const drift = Math.abs(invoiceItem.quantity - localSum); + +if (drift > TOLERANCE) { + await alertOnCall(Usage drift on ${subscriptionId}: CB=${invoiceItem.quantity}, Local=${localSum}); +} +``` + +✅ **Recommended**: Compare local raw-event rollups against Chargebee's `usage_summaries` automatically on every invoice. + +⚠️ **Not recommended**: Overwriting local usage data with Chargebee's invoice values without auditing discrepancies. + + +## See It Running In The Demo App + + +- [`pointer/lib/usage/events.ts`](../pointer/lib/usage/events.ts) — The non-blocking domain write API (`recordUsageEvent`). Enqueues generation usage without delaying the AI response. + +- [`pointer/lib/usage/stream.ts`](../pointer/lib/usage/stream.ts) — Redis Streams buffer implementing consumer groups, pending-entry reclaiming, and batch reads. + +- [`pointer/lib/usage/flush.ts`](../pointer/lib/usage/flush.ts) — The batch pump that archives entries to PostgreSQL, partitions expired events (> 12 hours), and ingests batches to Chargebee. + +- [`pointer/lib/usage/ingest.ts`](../pointer/lib/usage/ingest.ts) — Wrapper for Chargebee's `usageEvent.batchIngest` API handling batch limits and retry counts. + +- [`pointer/workers/usage-flush-loop.ts`](../pointer/workers/usage-flush-loop.ts) — Long-running background loop in the worker container driving periodic flushes. + +- [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts) — Declares metered features (`Input tokens`, `Output tokens`, `Credits consumed`, `Generations`) and maps them to Chargebee event properties. + + +## Go-Live Checklist + +- [ ] Is Product Catalog 2.0 active with metered features configured in Chargebee? + +- [ ] Is the usage recording call on the request path non-blocking (buffered via Redis or queue)? + +- [ ] Does every usage event carry a unique `deduplication_id`, `subscription_id`, and millisecond `usage_timestamp`? + +- [ ] Are events batched up to a maximum of 500 records before calling Chargebee's ingest endpoint? + +- [ ] Does the worker retain events locally in PostgreSQL before calling third-party endpoints? + +- [ ] Are messages acknowledged on the buffer only after Chargebee returns `200`? + +- [ ] Does the worker partition out events older than 12 hours so stale payloads don't block fresh batches? + +- [ ] Are quotas and rate limits enforced locally against cache, without synchronous Chargebee API calls? + +- [ ] Does the checkout success handler refresh entitlements and reset cache counters immediately? + +- [ ] Is there an automated reconciliation job comparing `invoice_generated` quantities against local event sums? diff --git a/pointer/public/information-architecture.png b/pointer/public/information-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..2d894adb7ff8aaa07b47a34c7f607fe7c6223442 GIT binary patch literal 366806 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%0Dmn4Oupq!=2Fz2Gro~Mv%1nrzqy-ZGY^I>9PMK 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�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

JXH`PoTo@r=x;93-*f|;;&LtIgBiRx{ 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

(aW4Ou79zlmH`%&JeX}JVcu@{65pIEh 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
  • ?O^!SYieVd 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 QolPd^EzT94xf1dJ078$;7XSbN literal 0 HcmV?d00001 From 03da42c68cd2371d09a74fd04302bbbd7e258548 Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Mon, 28 Sep 2026 21:14:39 +1000 Subject: [PATCH 02/12] fix grammar and typos --- README.md | 24 +++++++++---------- .../flow_visualization.html} | 0 pointer/infra/README.md | 5 ---- 3 files changed, 12 insertions(+), 17 deletions(-) rename pointer/{architecture.html => docs/flow_visualization.html} (100%) diff --git a/README.md b/README.md index 92eefb5..9f3e90b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The purpose of this repository is to showcase the best practices around integrat - A collection of in-depth, technical how-tos which cover the Chargebee recommended best practices when integrating various billing and usage related Chargebee products -- A opinionated, fully functional and deployable application built to demonstrate how these concepts translate to code +- An opinionated, fully functional and deployable application built to demonstrate how these concepts translate to code ## Audience @@ -26,25 +26,25 @@ The purpose of this repository is to showcase the best practices around integrat ## What it covers -The topics below cover the best practices to achieve the required outcome and are vetted and updated continuously. They cover implementation details, edge cases to handle, testing scenarios, etc. To bridge the gap between documentation and implementation, each of these topics are actually implemented in code. The [pointer](../pointer/README.md) app is our demo application built with an opinionated tech stack with all the required pieces to spin up a working SaaS platform. +The topics below cover the best practices to achieve the required outcome and are vetted and updated continuously. They cover implementation details, edge cases to handle, testing scenarios, etc. To bridge the gap between documentation and implementation, each of these topics is actually implemented in code. The [Pointer](./pointer/README.md) app is our demo application built with an opinionated tech stack with all the required pieces to spin up a working SaaS platform. Regardless of your tech stack or hosting platform, each how-to covers the functional/non-functional requirements and a production go-live checklist to help you along the journey. -Each how-to is broken down into self-contained topics which addresses a specific scenario. It can include: +Each how-to is broken down into self-contained topics that addresses a specific scenario. It can include: - Architecture or flow diagrams -- Pseudocode showing how certain cases maybe structured in code +- Pseudocode showing how certain cases may be structured in code - Best practices to keep in mind, and testing scenarios which are useful in validating your implementation -- Do's and Dont's +- Do's and Don'ts - An overview of how that topic is implemented in the demo app along with the relevant source files to look at -- A go-live checklist of must do steps to pay attention to before your app gets deployed to production environments +- A go-live checklist of must-do steps to pay attention to before your app gets deployed to production environments -These topics are written in plain markdown with mermaid diagrams wherever possible. It is written and edited by humans, but can be consumed by agents as well. +These topics are written in plain Markdown with mermaid diagrams wherever possible. They are written and edited by humans, but can be consumed by agents as well. ## Where it fits in @@ -70,16 +70,16 @@ While the Chargebee [docs](https://chargebee.com/docs) and [apidocs](https://api

    -[Pointer](https://pointer.chargebee-labs.com) is our reference app which mimics a LLM/AI API provider that is PLG (product led growth) driven. A developer signs up, uses their daily token allowance, and upgrades to Pro to keep going. Chargebee handles the subscription, entitlements, usage tracking and alerts. +[Pointer](https://pointer.chargebee-labs.com) is our reference app which mimics an LLM/AI API provider that is PLG (product-led growth) driven. A developer signs up, uses their daily token allowance, and upgrades to Pro to keep going. Chargebee handles the subscription, entitlements, usage tracking and alerts. It was built from the ground up with the help of coding agents and Chargebee's official libraries and SDK. Although a considerable amount of code was generated via LLMs, the architecture and design of the app, and the how-to topics themselves are completely written and reviewed by humans. -The purpose of building pointer along with the how-to topics is to ensure that they cover practical aspects of integrating Chargebee with a monetizable product. -We aim to continuously evolve and expand the reference architecture as we build out Pointer with additional features - this mimics how an actual product team would go about integrating billing/subscription/entitlement/usage with their app. +The purpose of building Pointer along with the how-to topics is to ensure that they cover practical aspects of integrating Chargebee with a monetizable product. +We aim to continuously evolve and expand the reference architecture as we build out Pointer with additional features - this mimics how an actual product team would go about integrating billing, subscription, entitlement, and usage management into their app. -A high level overview of the pointer architecture is shown below. More details on the tech stack, components, infrastructure are detailed in [ARCHITECTURE.md](./pointer/ARCHITECTURE.md) +A high-level overview of the Pointer architecture is shown below. Further details on the tech stack, components, and infrastructure are provided in [ARCHITECTURE.md](./pointer/ARCHITECTURE.md). -![pointer architecture](./pointer/public/pointer-components.svg) +![Pointer architecture](./pointer/public/pointer-components.svg) ## Links diff --git a/pointer/architecture.html b/pointer/docs/flow_visualization.html similarity index 100% rename from pointer/architecture.html rename to pointer/docs/flow_visualization.html diff --git a/pointer/infra/README.md b/pointer/infra/README.md index fe6200c..380b57f 100644 --- a/pointer/infra/README.md +++ b/pointer/infra/README.md @@ -11,7 +11,6 @@ pointer/infra/ versions.tf providers.tf variables.tf vpc.tf ecr.tf acm.tf alb.tf ecs.tf rds.tf sqs.tf route53.tf outputs.tf backend.hcl.example terraform.tfvars.example - writability-test/ <- separate stack to verify AWS write access ``` ## What gets created @@ -331,7 +330,3 @@ The full DB credential bundle (username, password, host, port, dbname, database_ - RDS `skip_final_snapshot = true`, `deletion_protection = false`. - ECR `force_delete = true`. - No WAF, no VPC Flow Logs, no ALB access logs, no Performance Insights / Enhanced Monitoring. - -## Related stacks - -- [`writability-test/`](writability-test/) — separate, disposable stack used to verify the AWS profile can write into the account. From d02f2e5c8095880442f1958a965d79dd4ffead3b Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Mon, 28 Sep 2026 21:38:06 +1000 Subject: [PATCH 03/12] remove authoring skill --- .cursor/skills/authoring-how-to/SKILL.md | 102 ---- .../assets/how-to-template.md | 139 ----- .../references/chargebee-sources.md | 98 ---- .../authoring-how-to/references/diagrams.md | 180 ------ .../authoring-how-to/references/language.md | 136 ----- .../authoring-how-to/scripts/check_how_to.py | 531 ------------------ 6 files changed, 1186 deletions(-) delete mode 100644 .cursor/skills/authoring-how-to/SKILL.md delete mode 100644 .cursor/skills/authoring-how-to/assets/how-to-template.md delete mode 100644 .cursor/skills/authoring-how-to/references/chargebee-sources.md delete mode 100644 .cursor/skills/authoring-how-to/references/diagrams.md delete mode 100644 .cursor/skills/authoring-how-to/references/language.md delete mode 100755 .cursor/skills/authoring-how-to/scripts/check_how_to.py diff --git a/.cursor/skills/authoring-how-to/SKILL.md b/.cursor/skills/authoring-how-to/SKILL.md deleted file mode 100644 index 4da96e4..0000000 --- a/.cursor/skills/authoring-how-to/SKILL.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -name: authoring-how-to -description: Authors and revises in-depth Chargebee integration how-to guides in the reference-architecture repository, following the structure, mermaid-only diagram rule, and writing voice of how-to/integrating-chargebee-webhooks.md. Use when adding or editing any file under how-to/, when asked to write or review a Chargebee how-to or reference-architecture guide, or when documenting a Chargebee integration topic such as webhooks, subscription lifecycle, checkout, invoices, payments, dunning, entitlements, usage metering, tax, or reconciliation. ---- - -# Authoring reference-architecture how-tos - -The `how-to/` directory holds vetted, in-depth guides on integrating Chargebee into a high-traffic SaaS platform. Each guide gives an architect or developer a design they can build against, plus the operational detail needed to run it. `pointer/` is the working implementation those guides point at. - -Read [`how-to/integrating-chargebee-webhooks.md`](../../../how-to/integrating-chargebee-webhooks.md) before writing. It is the structural and tonal reference. Match its shape and voice; do not copy its sentences or reproduce its typos. - -## Four rules that override everything else - -1. **Everything is code, including diagrams.** Every diagram is a fenced `mermaid` code block committed in the markdown. No images, no screenshots, no ASCII art, no links to Lucid, Miro, Excalidraw, or draw.io. If a design can't be drawn in mermaid, describe it in prose instead of reaching for a picture. -2. **Verify Chargebee behavior against the live docs.** Never write API names, field names, event types, limits, or product behavior from memory. See [references/chargebee-sources.md](references/chargebee-sources.md). -3. **Go deep.** These are not quickstarts. A guide that only documents the happy path has failed. Cover the failure modes, the ordering and idempotency and consistency concerns, and the trade-offs behind each recommendation. -4. **Write like the reference guide, not like a model.** The language rules in [references/language.md](references/language.md) are enforceable, not advisory. Run the validator before finishing. - -## Workflow - -1. **Fix the scope.** Name the outcome the reader wants and the boundary of the system covered. One guide, one outcome. If the topic is really two outcomes, propose splitting it. -2. **Research.** Pull the current behavior from Chargebee's docs and API reference, then read the relevant `pointer/` code. Follow [references/chargebee-sources.md](references/chargebee-sources.md). Note the exact URLs you relied on; they become the guide's outbound links. -3. **Map the design.** Before writing prose, work out: the synchronous path, what moves to asynchronous work, the system of record for each piece of state, every failure path, who retries and what gets acknowledged, and how state is reconciled when things drift. -4. **Draft.** Copy [assets/how-to-template.md](assets/how-to-template.md) to `how-to/-.md` and fill it in. Keep the section order. -5. **Draw the diagrams.** Follow [references/diagrams.md](references/diagrams.md) for the sequence and flowchart conventions used across the repo. -6. **Ground the implementation notes.** Open every `pointer/` file you cite and confirm it still does what you say. Label opinionated choices (SQS, Better Auth, Next.js, Postgres) as `pointer`'s choices, not Chargebee requirements. -7. **Validate.** Run the checker, fix every error, and for each warning either fix it or be able to say why it is a false positive. - - ```bash - python3 .cursor/skills/author-ref-arch-how-to/scripts/check_how_to.py how-to/.md - ``` - -8. **Register the guide.** Add it to the numbered topic list in [`how-to/README.md`](../../../how-to/README.md). -9. **Self-review.** Walk the final checklist at the bottom of this file. - -## Required structure - -Keep this order. Drop a section only when the topic genuinely has nothing to put in it. - -| Section | Heading | Contains | -| --- | --- | --- | -| Title | `# How to ` | The outcome, phrased as a task | -| Intro | (no heading) | Why this matters and what the reader will build, in 2–4 sentences, optionally followed by a short list of concrete reasons | -| Architecture | `## High level architecture` | One mermaid diagram covering happy path and the failure paths that matter | -| Guidance | `## Best Practices` | Recommendations organized by responsibility, with `###` subsections for the hard correctness topics | -| Code | `## Implementation notes` | How `pointer` does it, with links to specific files and what each one does | -| Launch | `## Go-live checklist` | `- [ ]` questions that can be answered yes or no before deploying | - -`Best Practices` keeps its title-case capitalization to match the reference guide. Every other heading is sentence case. - -### Intro - -Open on the reader's situation, not on the document. The reference guide starts with `One of the first steps when integrating Chargebee using the SDK/API is to setup webhooks. You will need webhooks to:` and then lists three reasons. That is the pattern: a sentence that places the topic in a real integration sequence, then the concrete payoff. - -### High level architecture - -Lead with the diagram, then explain it in prose dense enough that a reader who skips the diagram still understands the design. Show where trust boundaries sit, where a hand-off becomes durable, what runs asynchronously, and what happens on error. - -### Best Practices - -Group by responsibility or design decision. For each recommendation, say what to do, why it matters, and what breaks if it is skipped. Where the topic involves retries, duplicates, ordering, concurrency, or partial failure, spell out the semantics rather than gesturing at them. - -Promote a genuinely hard correctness problem into its own `###` subsection with its own mermaid sequence. The reference guide does this for out-of-order and dependent events, and that subsection is the most useful part of the document. Drop to `####` for a finer breakdown inside it, as the reference guide does for retry semantics. - -### Implementation notes - -Open with a short paragraph on how `pointer` wires the topic together, then a bulleted list of files. Each bullet is a link to the file followed by its role and the runtime behavior that matters, for example what acknowledges a message or what happens on a thrown error. - -State trade-offs plainly when `pointer` diverges from the recommended production design. - -### Go-live checklist - -Write each item as a question with a testable answer: - -```markdown -- [ ] Does the worker retry transient failures without acknowledging the message? -``` - -Cover configuration, authentication, correctness, failure recovery, observability, security, scale, reconciliation, and ownership as the topic warrants. Reject anything as soft as "Is the integration production-ready?". - -## Reference files - -- [references/language.md](references/language.md) — the voice, sentence patterns, formatting conventions, and the banned-phrase list. Read this before drafting prose. -- [references/diagrams.md](references/diagrams.md) — mermaid conventions: participant naming, `alt` blocks for failure paths, when to use `flowchart` over `sequenceDiagram`. -- [references/chargebee-sources.md](references/chargebee-sources.md) — which documentation site to trust for what, verified entry-point URLs, product-version traps, and the facts that must always be verified rather than recalled. -- [assets/how-to-template.md](assets/how-to-template.md) — the skeleton to copy into `how-to/`. -- [scripts/check_how_to.py](scripts/check_how_to.py) — structural and language validator. Run with `--strict` to make warnings fail too. - -## Final checklist - -- [ ] The title names a concrete outcome, not a subject area. -- [ ] The intro states the reader's problem in its first sentence and contains no meta-commentary about the document. -- [ ] Every diagram is mermaid, and the diagram and the prose describe the same system. -- [ ] Failure paths appear in the architecture diagram, not just in the prose. -- [ ] Each recommendation carries its rationale and its failure mode. -- [ ] Delivery and consistency semantics are stated precisely, and "exactly once" is not claimed unless the design proves it. -- [ ] Chargebee behavior, event types, and field names were checked against the live documentation, and the guide links to the pages a reader would use to verify them. -- [ ] `pointer`'s technology choices are labelled as choices. -- [ ] Every repository link resolves and every cited file still plays the stated role. -- [ ] Checklist items are answerable yes or no before launch. -- [ ] The draft has no filler transitions, no bolded label on every bullet, no closing summary, and nothing that reads like a template. -- [ ] `check_how_to.py` reports no errors. diff --git a/.cursor/skills/authoring-how-to/assets/how-to-template.md b/.cursor/skills/authoring-how-to/assets/how-to-template.md deleted file mode 100644 index 14fa7ed..0000000 --- a/.cursor/skills/authoring-how-to/assets/how-to-template.md +++ /dev/null @@ -1,139 +0,0 @@ -# How to with Chargebee - - - - You will need to: - -* -* -* - - -## High level architecture - - - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant queue as Durable
    Queue/Store - participant worker as Worker - participant db as Database - - cb->>app: - activate app - note over app: - app->>queue: Store message - alt Error - app->>cb: HTTP 5xx (Retry) - cb-->>app: Scheduled retry - else Success - app->>cb: HTTP 2xx (Success) - end - deactivate app - - queue-)worker: Receive messages - activate worker - worker-->>db: - alt All OK - worker-->>queue: Ack - else Retryable error - worker->>queue: Error (Retry) - else Non-retryable error - note over queue: Push to Dead Letter
    queue/store - end - deactivate worker -``` - - - - -## Best Practices - - - -* It should: - - - - - - - - -* Bold a **new term** on first use, then drop the bold. - -* - - -### - - - - - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant worker as Worker - - note over cb,worker: - - cb->>app: - app->>cb: HTTP 2xx -``` - - - - -## Implementation notes - -The [`pointer`](../pointer/) app implements using . - -- [`pointer/`](../pointer/) — - -- [`pointer/`](../pointer/) — - - - - -## Go-live checklist - - - -- [ ] - -- [ ] - -- [ ] - -- [ ] - -- [ ] diff --git a/.cursor/skills/authoring-how-to/references/chargebee-sources.md b/.cursor/skills/authoring-how-to/references/chargebee-sources.md deleted file mode 100644 index d8ad9db..0000000 --- a/.cursor/skills/authoring-how-to/references/chargebee-sources.md +++ /dev/null @@ -1,98 +0,0 @@ -# Chargebee sources - -These guides are read by people who will build against them, so a wrong field name or a stale limit is worse than no guide. Verify every Chargebee-specific claim against the live documentation while drafting. - -## Contents - -- [Which site for which question](#which-site-for-which-question) -- [Research procedure](#research-procedure) -- [Verified entry points](#verified-entry-points) -- [Facts that must never be written from memory](#facts-that-must-never-be-written-from-memory) -- [Product version traps](#product-version-traps) -- [Tooling that speeds up research](#tooling-that-speeds-up-research) - -## Which site for which question - -| Question | Site | -| --- | --- | -| What does this endpoint accept and return? What are the resource attributes, enums, and event types? | [apidocs.chargebee.com](https://apidocs.chargebee.com/docs/api) | -| How does the product behave? What does this setting do in the dashboard? How do these objects relate? | [chargebee.com/docs](https://www.chargebee.com/docs/billing/2.0/getting-started/object-relationship) | - -When product behavior and API reference appear to disagree, cite the API reference for the contract and the product docs for the behavior, and say which is which. - -## Research procedure - -1. Search the relevant site for the topic. The docs URL scheme is `https://www.chargebee.com/docs/billing/2.0/
    /`; the API reference is `https://apidocs.chargebee.com/docs/api/`. -2. Read the page rather than the search snippet. Snippets routinely drop the qualifier that matters. -3. **Confirm the URL resolves before citing it.** Documentation pages get reorganized, and a plausible-looking path is often a 404: - -```bash -curl -s -o /dev/null -w "%{http_code}\n" -L "https://apidocs.chargebee.com/docs/api/" -``` - -The validator's `--external` flag checks every outbound link in a finished guide. - -4. Link to the specific section or anchor that supports the claim, not to the section landing page. The webhook guide links straight to the event object's out-of-order delivery anchor, which is what makes that claim checkable. -5. Cross-check against `pointer/`. If the code contradicts the docs, the code is probably behind — investigate before documenting either. - -## Verified entry points - -Confirmed reachable at the time of writing. Re-check before citing. - -**API reference** - -- Getting started, authentication, pagination, error model — `https://apidocs.chargebee.com/docs/api` -- Events and event types — `https://apidocs.chargebee.com/docs/api/events` -- Event object, including out-of-order delivery — `https://apidocs.chargebee.com/docs/api/events/event-object` -- Webhooks — `https://apidocs.chargebee.com/docs/api/webhooks` -- Idempotency — `https://apidocs.chargebee.com/docs/api/idempotency` -- Error handling and `api_error_code` — `https://apidocs.chargebee.com/docs/api/error-handling` -- Customers — `https://apidocs.chargebee.com/docs/api/customers` -- Subscriptions — `https://apidocs.chargebee.com/docs/api/subscriptions` -- Invoices — `https://apidocs.chargebee.com/docs/api/invoices` -- Hosted pages — `https://apidocs.chargebee.com/docs/api/hosted_pages` -- Payment intents — `https://apidocs.chargebee.com/docs/api/payment_intents` -- Usages — `https://apidocs.chargebee.com/docs/api/usages` -- Entitlements — `https://apidocs.chargebee.com/docs/api/entitlements` - -**Product documentation** - -- Object relationship model — `https://www.chargebee.com/docs/billing/2.0/getting-started/object-relationship` -- Product catalog — `https://www.chargebee.com/docs/billing/2.0/product-catalog/product-catalog` -- Subscriptions — `https://www.chargebee.com/docs/billing/2.0/subscriptions/subscriptions` -- Invoices and credit notes — `https://www.chargebee.com/docs/billing/2.0/invoices-credit-notes-and-quotes/invoice-overview` -- Entitlements — `https://www.chargebee.com/docs/billing/2.0/entitlements/entitlements` -- Tax — `https://www.chargebee.com/docs/billing/2.0/taxes/tax-overview` - -For anything not listed, search the site and verify the URL. Do not extrapolate a path from the pattern. - -## Facts that must never be written from memory - -- Event type names and the exact set of resources inside `content` for a given event. -- Attribute names, nesting, and enum values on any resource. -- Which parameters an endpoint accepts and which are required. -- Retry schedules, rate limits, page sizes, expiry windows, and any other number. -- Whether a feature is generally available, gated, or plan-dependent. -- Anything about a specific payment gateway's behavior. - -Stable enough to rely on, and still worth linking: - -- The API is REST over HTTPS at `https://{site}.chargebee.com/api/v2/`, authenticated with HTTP Basic using the API key as the username and an empty password. -- Timestamps are Unix epoch seconds. -- Single resources come back enveloped (`{"customer": {...}}`); lists come back as `{"list": [...]}` with `next_offset` for pagination. -- API keys are per-environment: a test-site key is not a live-site key. -- Webhook events are delivered at-least-once with no ordering guarantee, and `resource_version` is the tiebreaker for staleness. - -## Product version traps - -- Documentation paths carry the product version (`/docs/billing/2.0/`). Confirm you are reading 2.0 and not an older page surfaced by a search engine. -- Product Catalog exists in two versions. The newer model uses items and item prices; the older uses plans and addons. Which one applies changes the API calls, the event payloads, and the local schema. State which version a guide assumes. -- A Chargebee site can run in test or live mode with separate configuration, keys, and webhook endpoints. Guides should say which mode a step applies to. -- Chargebee sells more than Billing. If a topic touches Retention, Revenue Recognition, or Receivables, name the product explicitly instead of writing "Chargebee". - -## Tooling that speeds up research - -- API reference pages carry a **Copy for LLM** control that yields the page as clean markdown. -- The full API is published as an OpenAPI 3.0 document, which is the fastest way to confirm a parameter list exhaustively. -- Chargebee publishes an MCP server and official agent skills (`npx skills add chargebee/ai`). If either is available in the session, prefer it over scraping pages. -- **Time Machine** on a test site simulates time-based billing events. Mention it when a guide's testing scenarios depend on renewals, trial expiry, or dunning cycles. diff --git a/.cursor/skills/authoring-how-to/references/diagrams.md b/.cursor/skills/authoring-how-to/references/diagrams.md deleted file mode 100644 index 8148d05..0000000 --- a/.cursor/skills/authoring-how-to/references/diagrams.md +++ /dev/null @@ -1,180 +0,0 @@ -# Diagrams - -Everything is code, diagrams included. Every diagram in `how-to/` is a fenced `mermaid` block in the markdown file. No images, no screenshots, no exported PNGs, no ASCII art, no links to Lucid, Miro, Excalidraw, or draw.io. GitHub renders mermaid natively, so the diagram stays reviewable in a pull request diff. - -## Contents - -- [Choosing a diagram type](#choosing-a-diagram-type) -- [Sequence diagram conventions](#sequence-diagram-conventions) -- [Arrow vocabulary](#arrow-vocabulary) -- [Showing failure paths](#showing-failure-paths) -- [Scenario diagrams](#scenario-diagrams) -- [Flowchart conventions](#flowchart-conventions) -- [Syntax pitfalls](#syntax-pitfalls) -- [What the diagram must not do](#what-the-diagram-must-not-do) - -## Choosing a diagram type - -| Use | When | -| --- | --- | -| `sequenceDiagram` | Timing, ordering, acknowledgements, retries, or who-waits-for-whom is the point. This is the default for the `High level architecture` section. | -| `flowchart LR` / `flowchart TD` | Component boundaries, routing, and deployment topology matter more than timing. | -| `stateDiagram-v2` | The topic is a lifecycle with named states, such as subscription or invoice status transitions. | -| `erDiagram` | The topic is about how Chargebee resources map onto local tables. | - -One diagram in `High level architecture`. Additional diagrams belong inside the `Best Practices` subsection that needs them, next to the prose they clarify. - -## Sequence diagram conventions - -Declare every participant up front with a short lowercase alias and a readable display name. Use `
    ` to wrap long names. - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant queue as Durable
    Queue/Store - participant worker as Worker - participant db as Database -``` - -Established aliases, reused across guides so diagrams read consistently: `cb` (Chargebee), `app` (your application), `web` (browser or client), `queue`, `worker`, `db`, `dlq`. - -Wrap the span where a component is doing work in `activate` / `deactivate`. It makes the synchronous portion of the flow visually obvious, which is usually the whole argument of the diagram. - -Use `note over` for the things that are not messages: validation steps, decisions, and state checks. - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant queue as Durable
    Queue/Store - - cb->>app: Webhook Event - activate app - note over app: Validate basic auth - app->>queue: Store message - note over app: Message stored
    successfully? - app->>cb: HTTP 2xx (Success) - deactivate app -``` - -Label messages with the real thing being sent. `payment_succeeded` beats `event`. `HTTP 2xx (Success)` beats `response`. - -## Arrow vocabulary - -Keep these consistent, because readers learn them across guides: - -| Arrow | Meaning | -| --- | --- | -| `->>` | A request or a call the sender waits on | -| `-->>` | A response, or an internal write the caller does not block a remote system on | -| `-)` | Asynchronous delivery where the sender does not wait, such as a queue handing a message to a worker | - -Annotate a push-or-poll ambiguity rather than picking one silently: `note over queue,worker: Poll or push messages`. - -## Showing failure paths - -The failure path is the reason the diagram exists. Put it in the diagram with `alt` / `else`, and name each branch by its outcome. - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant queue as Durable
    Queue/Store - - app->>queue: Store message - alt Error - app->>cb: HTTP 5xx (Retry) - cb-->>app: Scheduled retry - else Success - app->>cb: HTTP 2xx (Success) - end -``` - -Where an error is not uniform, split the branches by how they are handled rather than by what threw: - -```mermaid -sequenceDiagram - participant queue as Durable
    Queue/Store - participant worker as Worker - - alt All OK - worker-->>queue: Ack - else Retryable error - worker->>queue: Error (Retry) - queue-->>worker: Scheduled retry - else Non-retryable error - worker->>queue: Error - note over queue: Push to Dead Letter
    queue/store for
    reporting - end -``` - -## Scenario diagrams - -When a subsection explains one specific hard case, open the diagram with a note spanning the full participant range that states the scenario, then walk it in order. This is what makes the out-of-order subsection in the webhook guide legible: - -```mermaid -sequenceDiagram - participant cb as Chargebee - participant app as App - participant queue as Durable
    Queue/Store - participant worker as Worker - participant db as Database - - note over cb,db: payment_succeeded arrives BEFORE customer_created - - cb->>app: payment_succeeded - app->>queue: Store message - app->>cb: HTTP 2xx - - queue-)worker: payment_succeeded - activate worker - note over worker: customer not in DB yet - worker->>queue: Retryable error (dependency missing) - note over queue: Redeliver after
    a backoff delay. - deactivate worker - - note over queue: retry delay elapses - queue-)worker: payment_succeeded (redelivery) - activate worker - note over worker: customer now exists - worker-->>db: Apply payment - worker-->>queue: Ack - deactivate worker -``` - -Use `note over` to mark the passage of time (`retry delay elapses`) rather than trying to draw it. - -## Flowchart conventions - -Use `flowchart LR` for a left-to-right pipeline, `flowchart TD` for a topology with layers. Give every node a stable id and quote any label containing punctuation. - -```mermaid -flowchart LR - web[Browser] -->|Hosted Checkout| cb[Chargebee] - cb -->|Webhook| app["App (/api/webhook)"] - app --> queue[(Durable queue)] - queue --> worker[Worker] - worker --> db[(Database)] - worker -.->|Exhausted retries| dlq[(Dead letter queue)] -``` - -Use `subgraph` to mark a trust or deployment boundary, and label the edge that crosses it. - -## Syntax pitfalls - -- Quote any label containing `(`, `)`, `:`, `,`, `#`, or `-`: `app["App (sync path)"]`. Unquoted parentheses break flowchart parsing. -- Use `
    ` for line breaks inside labels and notes. It works in both sequence diagrams and flowcharts. -- In a sequence diagram the first `:` after an arrow starts the message text; later colons are literal. -- Every `alt`, `loop`, `opt`, `par`, and `subgraph` needs a matching `end`. -- `note over a,b:` spans participants; `note over a:` sits on one. There is no `note between`. -- `%%` starts a comment line. -- Declare participants explicitly instead of letting them be inferred, so the left-to-right order is deliberate. -- Render the diagram before committing. A mermaid block that fails to parse shows as a raw error on GitHub, and the validator only checks that the block exists. - -## What the diagram must not do - -- Duplicate the prose. The diagram carries the flow; the prose carries the reasoning. -- Show only the happy path. -- Include so many participants that the failure branches stop fitting. Split into a topology flowchart plus a focused sequence instead. -- Encode `pointer`-specific technology in the `High level architecture` diagram. Say `Durable Queue/Store`, not `SQS`. SQS belongs in `Implementation notes`. diff --git a/.cursor/skills/authoring-how-to/references/language.md b/.cursor/skills/authoring-how-to/references/language.md deleted file mode 100644 index 9591f68..0000000 --- a/.cursor/skills/authoring-how-to/references/language.md +++ /dev/null @@ -1,136 +0,0 @@ -# Language patterns - -Every rule here is derived from [`how-to/integrating-chargebee-webhooks.md`](../../../../how-to/integrating-chargebee-webhooks.md). When in doubt, open that file and match it. - -## Contents - -- [Who you are writing for](#who-you-are-writing-for) -- [Voice and person](#voice-and-person) -- [Sentence patterns that match the reference guide](#sentence-patterns-that-match-the-reference-guide) -- [Paragraphs versus lists](#paragraphs-versus-lists) -- [Emphasis and code formatting](#emphasis-and-code-formatting) -- [Linking](#linking) -- [Precision about distributed-systems behavior](#precision-about-distributed-systems-behavior) -- [Banned phrases](#banned-phrases) -- [Structural tells to avoid](#structural-tells-to-avoid) -- [Before and after](#before-and-after) - -## Who you are writing for - -An architect or senior developer who has decided to integrate Chargebee and now has to design something that survives production. They know what a queue is. They do not know how Chargebee behaves at the edges. Write for that gap. - -Do not explain general computer science. Link to it. The reference guide links "dead letter queue" to Wikipedia rather than spending a paragraph defining it. - -## Voice and person - -Use **you** for guidance aimed at the reader: - -> Since webhook events can be delivered out of order, store and compare the `resource_version` returned in the webhook `content`. - -Name the **component** when describing runtime behavior, not "you": - -> The worker treats "dependency not ready" as a *retryable* error: it does not acknowledge the message, so the queue redelivers it after a delay. - -Use the imperative for instructions. `Store the event before returning 2xx.` Not `It is important to ensure that the event is stored.` - -Write in present tense and active voice. `Chargebee retries the webhook`, not `the webhook will be retried by Chargebee`. - -Never use "we" or "one". Never address a team. Never invent a scenario involving a fictional company. - -## Sentence patterns that match the reference guide - -**Lead with the action, then the reason.** The recommendation comes first; the justification follows in the same sentence or the next one. - -> Store the event payload in a **durable queue** or database with the event `id` as the unique identifier. This ensures events are not lost, and duplicate events can be ignored. - -**State the mechanism, not the vibe.** Prefer a sentence that a reader could implement from. - -> On each unacknowledged delivery the message becomes available again after a *backoff delay*, and its delivery/receive count is incremented. - -**Name the failure case directly.** The reference guide writes `The classic case:` and then describes an actual event pair. - -> The classic case: a `payment_succeeded` (or `subscription_created`) arrives and references a customer whose `customer_created` event **hasn't been processed yet**. - -**Use short sentences for constraints.** One clause, one rule. - -> Don't drop the event. The dependency will likely arrive moments later. - -**Keep transitions specific.** Tie the transition to the thing that just happened: `Once the event is durable, the endpoint can return 2xx.` Never open a sentence with `Additionally`, `Furthermore`, `Moreover`, or `It is worth noting that`. - -**Qualify honestly.** The reference guide writes `Most of the time events arrive in roughly the order they occurred` and `Out-of-order gaps are usually seconds`. Hedge where reality hedges; do not hedge to sound cautious. - -## Paragraphs versus lists - -Reasoning goes in paragraphs. Parallel items go in lists. A list of three items that are not actually parallel should have been a paragraph. Number a list only when the order is part of the instruction. - -Delete any sentence that can be removed without losing meaning. - -Top-level bullets in `Best Practices` carry real weight — often two or three sentences. Nested bullets carry the mechanics of the parent bullet, such as the ordered steps a handler performs. That nesting is how the reference guide handles the synchronous webhook path, and it works because the sub-bullets are genuinely a sequence. - -Contractions are fine where they read naturally (`doesn't redeliver it`, `hasn't been processed yet`). Do not force them. - -## Emphasis and code formatting - -Bold a term the first time you introduce it as a concept, then drop the bold: **durable queue**, **idempotent operations**, **at-least-once**. Bold a rule that a reader must not miss: **Don't drop the event.** - -Do not bold a two-word label at the front of every bullet. The reference guide does it in one place — the retry-semantics subsection, where `Retry with backoff` and `Retention` are genuine named concepts being contrasted. Mechanical bolding across a whole list is the single clearest sign that a model wrote the page. - -Use backticks for event types (`payment_succeeded`), field paths (`content.subscription.resource_version`), HTTP status families (`5xx`), config keys (`maxReceiveCount`), commands, and file paths. Write HTTP statuses as the reference guide does: `HTTP 2xx`, `HTTP 5xx`. - -Use italics sparingly, for a word being used in a precise technical sense: a *retryable* error, a *backoff delay*. - -No emoji. No decorative horizontal rules. - -## Linking - -- Repository files use relative links with the path as the link text: `` [`pointer/lib/webhooks.ts`](../pointer/lib/webhooks.ts) ``. -- Chargebee behavior links to the specific page and anchor that proves it, such as the event object's out-of-order delivery section. -- Standard concepts link out rather than being re-explained. -- Link text describes the destination. Never `click here` or a bare URL. - -## Precision about distributed-systems behavior - -These guides live or die on this section. - -- Name the delivery semantics: at-least-once, at-most-once. Do not claim exactly-once unless the whole design proves it. -- Separate **durable acceptance** from **successful processing**. The endpoint accepting an event is not the same as the work being done. -- Say who retries, what is acknowledged, when, and where a message goes when it runs out of attempts. -- Distinguish a transient failure worth retrying patiently from a permanently broken payload that should fail fast. -- Give concrete numbers when the implementation has them, and label them as the implementation's settings: `maxReceiveCount = 5`, `Main queue retention is 4 days; DLQ retention is 14 days`. -- Say which side is authoritative for each piece of state and how local state gets reconciled when it drifts. - -## Banned phrases - -The validator flags these. They are the vocabulary of generated marketing prose, and none of them survive in a technical guide. - -`seamless`, `seamlessly`, `robust`, `powerful`, `comprehensive`, `leverage` (use "use"), `utilize` (use "use"), `facilitate`, `delve`, `dive into`, `unpack`, `unlock`, `supercharge`, `elevate`, `empower`, `streamline`, `holistic`, `synergy`, `myriad`, `plethora`, `underscore(s)`, `paramount`, `harness` (as a verb), `foster`, `embark`, `bespoke`, `meticulous(ly)`, `boasts`, `game-changer`, `cutting-edge`, `best-in-class`, `state-of-the-art`, `world-class`, `tapestry`, `realm`, `landscape`, `testament to`, `navigate the complexities`, `in today's fast-paced`, `in the world of`, `when it comes to`, `ever-evolving`, `it is worth noting`, `it's worth noting`, `it is important to note`, `keep in mind that`, `needless to say`, `at the end of the day`, `in conclusion`, `in summary`, `to sum up`, `this guide will explore`, `this article will`, `let's dive in`, `we'll explore`, `crucial`, `vital role`, `pivotal role`, `key takeaway`, `rest assured`, `look no further`, `and more!`, `Certainly!`, `Great question`. - -Also cut vague intensifiers — `very`, `really`, `extremely`, `incredibly`. If a thing is slow, say how slow. - -Three notes on judgement: - -- `crucial`, `robust`, `powerful`, and `comprehensive` are banned as adjectives of praise. If a word has a precise technical meaning in context, rewrite to say the precise thing instead of arguing for the adjective. -- `enable` and `explore` are fine in their literal senses (enable a setting, explore the API reference) and banned as filler. -- Em dashes are fine, and the reference guide uses them for exactly the kind of aside they exist for. Do not ban punctuation to sound human, and do not lean on them either. - -## Structural tells to avoid - -- **No document meta-commentary.** No "This guide covers", no "In this section we will", no restating the heading in the first sentence beneath it. -- **No rhetorical questions in prose.** The go-live checklist is the one place a question belongs. Replace "So what happens when the customer doesn't exist yet?" with the answer. -- **No closing summary.** The reference guide ends on the last checklist item. Yours does too. -- **No rule of three everywhere.** If there are four practices, list four. If there are two, list two. -- **No symmetrical padding.** Do not add a sentence to a bullet just because the bullet above it is longer. -- **No hedged non-recommendations.** "Consider whether you might want to potentially validate" is not guidance. Pick a position and defend it. -- **No placeholder examples** where a real Chargebee name exists. Write `subscription_renewal_reminder`, not `some_event_type`. - -## Before and after - -| Rewrite this | To this | -| --- | --- | -| It is crucial to ensure that robust error handling is implemented. | If the handler throws before the event is stored, return `HTTP 5xx` so Chargebee redelivers it. | -| Additionally, it is worth noting that events may arrive out of order. | Chargebee delivers events with no ordering guarantee, so a `payment_succeeded` can arrive before the `customer_created` it depends on. | -| This guide will explore how to leverage Chargebee webhooks to build a seamless integration. | One of the first steps when integrating Chargebee is setting up webhooks. | -| **Idempotency:** Ensure idempotency. **Retries:** Ensure retries. **Monitoring:** Ensure monitoring. | Process every message as if it may arrive twice: key the audit table on the event `id` and let inserts fall through with `ON CONFLICT DO NOTHING`. | -| You might want to consider utilizing a durable queue. | Store the event in a durable queue. | -| This powerful feature enables seamless synchronization. | This keeps your app data in sync with your Chargebee site. | -| In conclusion, following these best practices will help you build a production-ready integration. | (delete it) | diff --git a/.cursor/skills/authoring-how-to/scripts/check_how_to.py b/.cursor/skills/authoring-how-to/scripts/check_how_to.py deleted file mode 100755 index 1ed018f..0000000 --- a/.cursor/skills/authoring-how-to/scripts/check_how_to.py +++ /dev/null @@ -1,531 +0,0 @@ -#!/usr/bin/env python3 -"""Validate a reference-architecture how-to guide. - -Checks structure, mermaid-only diagrams, link integrity, and the language rules -described in references/language.md. - - python3 check_how_to.py how-to/integrating-chargebee-webhooks.md - python3 check_how_to.py how-to/*.md --strict --external -""" - -from __future__ import annotations - -import argparse -import os -import re -import sys -import urllib.error -import urllib.request - -REQUIRED_SECTIONS = [ - "High level architecture", - "Best Practices", - "Implementation notes", - "Go-live checklist", -] - -# Unambiguous marketing or generated-prose tells. -def inflect(verb: str) -> list[str]: - """Every form of a banned verb, so 'utilizes' is caught alongside 'utilize'.""" - if verb.endswith("e"): - return [verb, verb + "s", verb + "d", verb[:-1] + "ing"] - return [verb, verb + "s", verb + "ed", verb + "ing"] - - -BANNED_VERBS_ERROR = [ - "utilize", "facilitate", "delve", "elevate", "empower", "streamline", - "unlock", "supercharge", "unpack", -] - -BANNED_ERROR = [ - "seamless", "seamlessly", "robust", "powerful", "comprehensive", - "dive into", "holistic", - "synergy", "myriad", "plethora", "paramount", "bespoke", "meticulous", - "meticulously", "boasts", "game-changer", "game changer", "cutting-edge", - "best-in-class", "state-of-the-art", "world-class", "tapestry", - "testament to", "navigate the complexities", "in today's fast-paced", - "in the world of", "when it comes to", "ever-evolving", - "it is worth noting", "it's worth noting", "it is important to note", - "keep in mind that", "needless to say", "at the end of the day", - "in conclusion", "in summary", "to sum up", "this guide will explore", - "this guide will cover", "this article will", "let's dive in", - "we'll explore", "we will explore", "crucial", "vital role", - "pivotal role", "key takeaway", "rest assured", "look no further", - "and more!", "as an ai", "certainly!", "great question", - "might want to consider", -] + [form for verb in BANNED_VERBS_ERROR for form in inflect(verb)] - -# Legitimate in the right technical context, but usually a tell. -BANNED_WARN = [ - "harness", "foster", "embark", "underscore", "underscores", "realm", - "landscape", "very", "really", "extremely", "incredibly", -] + inflect("leverage") + inflect("explore") - -# Filler only when they open a sentence; fine mid-sentence. -BANNED_OPENERS = ["additionally", "furthermore", "moreover", "overall", "notably"] - -IMAGE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp", ".pdf") - -DIAGRAM_HOSTS = ( - "lucid.app", "lucidchart", "miro.com", "excalidraw", "draw.io", - "diagrams.net", "figma.com", "whimsical.com", "mermaid.live", -) - -MERMAID_TYPES = ( - "sequenceDiagram", "flowchart", "graph", "stateDiagram-v2", "stateDiagram", - "erDiagram", "classDiagram", "gantt", "journey", "timeline", "block-beta", - "C4Context", -) - -MERMAID_BLOCK_OPENERS = ("alt", "loop", "opt", "par", "critical", "rect", "box", "subgraph") - -# pointer's technology choices, which do not belong in a neutral architecture diagram. -VENDOR_TERMS = ( - "SQS", "SNS", "Kinesis", "RabbitMQ", "Kafka", "Lambda", "DynamoDB", - "Postgres", "PostgreSQL", "MySQL", "Redis", "Next.js", "NextJS", - "Better Auth", "BetterAuth", "Vercel", "Cloudflare", "Drizzle", "Prisma", -) - -EMOJI = re.compile( - "[\U0001F000-\U0001FAFF\U00002600-\U000027BF\U0001F1E6-\U0001F1FF\U00002B00-\U00002BFF]" -) - -LINK = re.compile(r"(!?)\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+\"[^\"]*\")?\s*\)") -HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$") -BULLET = re.compile(r"^(\s*)([-*+])\s+(.*)$") -CHECKBOX = re.compile(r"^\s*[-*+]\s+\[( |x|X)\]\s+(.*)$") - - -class Report: - def __init__(self, path: str) -> None: - self.path = path - self.errors: list[tuple[int, str]] = [] - self.warnings: list[tuple[int, str]] = [] - - def error(self, line: int, message: str) -> None: - self.errors.append((line, message)) - - def warn(self, line: int, message: str) -> None: - self.warnings.append((line, message)) - - def render(self) -> None: - print(f"\n{self.path}") - if not self.errors and not self.warnings: - print(" ok") - return - for line, message in sorted(self.errors): - print(f" ERROR {self.path}:{line}: {message}") - for line, message in sorted(self.warnings): - print(f" WARN {self.path}:{line}: {message}") - print(f" {len(self.errors)} error(s), {len(self.warnings)} warning(s)") - - -def normalize(text: str) -> str: - """Fold smart punctuation so phrase matching is not defeated by typography.""" - return ( - text.replace("\u2019", "'").replace("\u2018", "'") - .replace("\u201c", '"').replace("\u201d", '"') - ) - - -def find_fences(lines: list[str]) -> list[dict]: - """Return fenced code blocks as {lang, start, end, body} with 0-based indices.""" - fences: list[dict] = [] - open_fence: dict | None = None - for index, line in enumerate(lines): - match = re.match(r"^(\s*)(`{3,})(.*)$", line) - if not match: - continue - marker, info = match.group(2), match.group(3).strip() - if open_fence is None: - open_fence = {"lang": info.split()[0] if info else "", "start": index, - "marker": marker, "body": []} - elif marker[: len(open_fence["marker"])] == open_fence["marker"] and not info: - open_fence["end"] = index - fences.append(open_fence) - open_fence = None - if open_fence is not None: - open_fence["end"] = len(lines) - 1 - open_fence["unclosed"] = True - fences.append(open_fence) - for fence in fences: - fence["body"] = lines[fence["start"] + 1: fence["end"]] - return fences - - -def mask(lines: list[str], fences: list[dict]) -> list[str]: - """Blank out code so prose checks never fire on code samples.""" - masked = list(lines) - for fence in fences: - for index in range(fence["start"], min(fence["end"] + 1, len(masked))): - masked[index] = "" - for index, line in enumerate(masked): - masked[index] = re.sub(r"`[^`]*`", lambda m: " " * len(m.group(0)), line) - return masked - - -def slugify(title: str) -> str: - text = re.sub(r"`", "", title.lower()) - text = re.sub(r"[^\w\- ]", "", text) - return text.strip().replace(" ", "-") - - -def phrase_pattern(phrase: str) -> re.Pattern: - body = re.escape(phrase).replace(r"\ ", r"\s+").replace("'", "['\u2019]") - lead = r"\b" if phrase[0].isalnum() else "" - tail = r"\b" if phrase[-1].isalnum() else "" - return re.compile(lead + body + tail, re.IGNORECASE) - - -def opener_pattern(word: str) -> re.Pattern: - return re.compile(r"(?:^|[.!?]\s+)[\s\-*+>]*(?:\*\*|__)?" + re.escape(word) + r"\b", - re.IGNORECASE) - - -BANNED_ERROR_PATTERNS = [(p, phrase_pattern(p)) for p in BANNED_ERROR] -BANNED_WARN_PATTERNS = [(p, phrase_pattern(p)) for p in BANNED_WARN] -BANNED_OPENER_PATTERNS = [(p, opener_pattern(p)) for p in BANNED_OPENERS] - - -def check_headings(lines: list[str], masked: list[str], report: Report) -> list[dict]: - headings = [] - for index, line in enumerate(masked): - match = HEADING.match(line) - if match: - headings.append({"level": len(match.group(1)), "title": match.group(2), - "line": index}) - - h1s = [h for h in headings if h["level"] == 1] - if not h1s: - report.error(1, "no H1 title found") - elif len(h1s) > 1: - report.error(h1s[1]["line"] + 1, f"{len(h1s)} H1 headings; a guide has exactly one") - if h1s and not re.match(r"^How to \S", h1s[0]["title"]): - report.error(h1s[0]["line"] + 1, - f"title must start with 'How to ' and name an outcome: {h1s[0]['title']!r}") - - h2_titles = [h["title"] for h in headings if h["level"] == 2] - positions = [] - for required in REQUIRED_SECTIONS: - if required not in h2_titles: - report.error(1, f"missing required section '## {required}'") - else: - positions.append(h2_titles.index(required)) - if len(positions) == len(REQUIRED_SECTIONS) and positions != sorted(positions): - report.error(1, "required sections are out of order; expected " - + " then ".join(REQUIRED_SECTIONS)) - - for heading in headings: - if heading["level"] >= 2 and heading["title"] not in ("Best Practices",): - words = [w for w in heading["title"].split() if w.isalpha() and len(w) > 3] - capitalized = [w for w in words[1:] if w[0].isupper()] - if words and len(capitalized) >= max(2, len(words) // 2): - report.warn(heading["line"] + 1, - f"heading looks title-cased; use sentence case: {heading['title']!r}") - return headings - - -def section_bounds(headings: list[dict], title: str, total: int) -> tuple[int, int] | None: - for position, heading in enumerate(headings): - if heading["title"] == title: - start = heading["line"] - end = total - for later in headings[position + 1:]: - if later["level"] <= heading["level"]: - end = later["line"] - break - return start, end - return None - - -def check_diagrams(lines: list[str], fences: list[dict], headings: list[dict], - report: Report) -> None: - for fence in fences: - if fence.get("unclosed"): - report.error(fence["start"] + 1, "unclosed code fence") - - mermaid = [f for f in fences if f["lang"].lower() == "mermaid"] - if not mermaid: - report.error(1, "no mermaid diagram found; every diagram must be a mermaid code block") - - bounds = section_bounds(headings, "High level architecture", len(lines)) - if bounds: - start, end = bounds - in_section = [f for f in mermaid if start < f["start"] < end] - if not in_section: - report.error(start + 1, "'High level architecture' has no mermaid diagram") - else: - architecture = in_section[0] - for offset, line in enumerate(architecture["body"]): - for term in VENDOR_TERMS: - if re.search(rf"\b{re.escape(term)}\b", line): - report.warn(architecture["start"] + 2 + offset, - f"'{term}' is a pointer implementation choice; keep the " - "architecture diagram technology-neutral and move it to " - "'Implementation notes'") - prose = " ".join( - l.strip() for l in lines[architecture["end"] + 1: end] - if l.strip() and not l.strip().startswith("#") - ) - if len(prose) < 200: - report.warn(architecture["end"] + 1, - "the architecture diagram is not followed by enough prose to stand " - "on its own") - - for fence in mermaid: - body = [l for l in fence["body"] if l.strip() and not l.strip().startswith("%%")] - if not body: - report.error(fence["start"] + 1, "empty mermaid block") - continue - first = body[0].strip() - if not any(first.startswith(kind) for kind in MERMAID_TYPES): - report.warn(fence["start"] + 2, f"unrecognized mermaid diagram type: {first!r}") - - openers = 0 - ends = 0 - for line in body: - token = line.strip().split()[0] if line.strip().split() else "" - if token in MERMAID_BLOCK_OPENERS: - openers += 1 - elif token == "end": - ends += 1 - if openers != ends: - report.error(fence["start"] + 1, - f"mermaid block has {openers} block opener(s) but {ends} 'end'(s)") - - if first.startswith("sequenceDiagram"): - declared = set() - for line in body: - match = re.match(r"^\s*(?:participant|actor)\s+(\w+)", line) - if match: - declared.add(match.group(1)) - used: set[str] = set() - for line in body: - arrow = re.match(r"^\s*(\w+)\s*(?:-{1,2}>>?|-{1,2}\)|-{1,2}x|<<-->>)\s*\+?(\w+)", - line) - if arrow: - used.update(arrow.groups()) - for keyword in ("activate", "deactivate"): - single = re.match(rf"^\s*{keyword}\s+(\w+)", line) - if single: - used.add(single.group(1)) - undeclared = sorted(used - declared) - if undeclared: - report.warn(fence["start"] + 1, - "participant(s) used but never declared: " + ", ".join(undeclared)) - - -def check_images_and_links(lines: list[str], masked: list[str], headings: list[dict], - report: Report, doc_path: str, check_external: bool) -> None: - anchors = {slugify(h["title"]) for h in headings} - doc_dir = os.path.dirname(os.path.abspath(doc_path)) - external: list[tuple[int, str]] = [] - - for index, line in enumerate(masked): - line_number = index + 1 - if re.search(r" tag; diagrams must be mermaid code blocks") - if ">Queue: Ack (no-op) ``` - Rules for version tracking: +Rules for version tracking: - • Track version per resource type and ID (subscription:sub_123, customer:cust_456). Versions advance independently across different entities. - • Do not rely on event occurred_at for deduplication; occurred_at represents when the event triggered, while resource_version represents the entity mutation state. +- Track versions per resource type and ID (`subscription:sub_123`, `customer:cust_456`). Versions advance independently across different entities. +- Do not rely on event `occurred_at` for deduplication. `occurred_at` marks when the event fired, while `resource_version` reflects the actual entity mutation state. ```typescript // Check if incoming version is older than stored version @@ -163,13 +161,13 @@ SET version = EXCLUDED.version, updated_at = NOW() WHERE EXCLUDED.version > chargebee_resource_version.version; ``` -✅ **Do**: Compare resource_version for every individual entity in event.content +✅ Do: Compare `resource_version` for every individual entity in `event.content`. -⚠️ **Don't**: Maintaining a single global version cursor for the entire site +⚠️ Don't: Maintain a single global version cursor for the entire site. Different entities update independently and will falsely reject valid events if tied to a single counter. ## 4. Handling Subscription Upgrades (Preventing Split-Brain) -When a customer upgrades in the app, two paths execute: the synchronous API mutation (or checkout redirect) and the asynchronous subscription_changed webhook. If the app writes locally before the webhook arrives, writes can race and corrupt state. +When a customer upgrades in the app, two paths touch subscription state: the synchronous API mutation (or checkout redirect) and the asynchronous `subscription_changed` webhook. If the app updates state incorrectly or waits solely on the webhook, the user either experiences upgrade lag or writes race. ```mermaid sequenceDiagram @@ -190,11 +188,11 @@ sequenceDiagram Worker-->>Worker: Incoming <= stored -> No-op (Ack) ``` -To avoid split-brain and provide immediate UI feedback: +To prevent race conditions and provide instant UI feedback: -1. Mutate via Chargebee first: Call Chargebee's subscription update API. -2. Apply immediate response: Upsert the local database row using the response body and record its resource_version. -3. Allow webhook to no-op: When the corresponding webhook arrives, the worker sees incoming.resource_version <= stored.resource_version and cleanly discards it. +1. Mutate in Chargebee first: Call Chargebee's subscription update API. +2. Update the local mirror from the response: Upsert the local database row using the response body and record its `resource_version`. +3. Let the webhook no-op: When the corresponding webhook arrives, the worker sees `incoming.resource_version <= stored.resource_version` and cleanly discards it. ```typescript // Synchronous upgrade mutation @@ -205,12 +203,11 @@ const { subscription } = await chargebee.subscription.updateForItems(subId, { await syncSubscriptionMirror(subscription); ``` -✅ **Do**: Update local mirror state using the Chargebee API response immediately after checkout/upgrade mutations - -⚠️ **Don't**: Updating local subscription status optimistically before Chargebee responds +✅ Do: Update local mirror state using the Chargebee API response immediately after checkout or upgrade mutations. -⚠️ **Don't**: Waiting purely for the webhook after checkout without refreshing locally, which causes visible upgrade lag for the user +⚠️ Don't: Update local subscription records optimistically before Chargebee confirms the change. If the Chargebee request fails, local state will be out of sync. +⚠️ Don't: Wait purely for the background webhook after checkout without updating locally. Webhook delivery delays leave paying customers waiting for upgraded access. ## 5. Reconciling Gaps After Outages @@ -236,7 +233,7 @@ Use two reconciliation strategies depending on the gap window: ### Option 1: Event Replay (Gaps < 90 Days) -Chargebee retains events for 90 days. Walk the List events API (https://apidocs.chargebee.com/docs/api/events/list-events) and pass each event directly into the worker's processing logic. +Chargebee retains events for 90 days. Query the [list events API](https://apidocs.chargebee.com/docs/api/events/list-events) and pass each event directly into the worker's processing logic. ```typescript // Replay events from last known cursor @@ -252,30 +249,29 @@ for (const entry of events.list) { ### Option 2: Full Entity Backfill (Gaps > 90 Days or Cold Starts) -If event retention has lapsed, paginate the resource list endpoints directly using next_offset. +If event retention has lapsed, paginate the resource list endpoints directly using `next_offset`. ```typescript // Paginating current entity snapshots let offset: string | undefined; do { - const result = await chargebee.subscription.list({ limit: 100, offset }); - for (const entry of result.list) { - await syncSubscriptionMirror(entry.subscription); - } - offset = result.next_offset; + const result = await chargebee.subscription.list({ limit: 100, offset }); + for (const entry of result.list) { + await syncSubscriptionMirror(entry.subscription); + } + offset = result.next_offset; } while (offset); ``` -✅ **Do**: Run a daily reconciliation cron to catch any drifted rows - -✅ **Do**: Reuse the same idempotent worker logic for event replay and webhooks +✅ Do: Run a scheduled reconciliation job to catch drifted records. -⚠️ **Don't**: Deleting local rows during reconciliation if an event is missing; verify current status with retrieve first +✅ Do: Reuse the same idempotent worker logic for event replay and live webhooks. +⚠️ Don't: Delete local records during reconciliation if an expected event is missing. Query Chargebee's retrieve endpoint first to verify current status. ## 6. Cold Start And Backfill -When standing up a fresh environment or recovering an empty database: +When provisioning a fresh environment or restoring an empty database, backfill catalog and customer state before enabling live traffic: ```mermaid sequenceDiagram @@ -294,37 +290,38 @@ When standing up a fresh environment or recovering an empty database: Bootstrap sequence: -1. Pull Catalog: Paginate items and item prices from Chargebee and populate catalog tables. -2. Pull Active Subscriptions: Paginate customers and subscriptions. Link Chargebee customers to internal users via metadata or email. -3. Seed Version Cursors: Populate the chargebee_resource_version table with current timestamps from the API response so incoming webhooks do not replay older state. -4. Enable Webhook Ingress: Enable worker processing. Steady-state updates take over seamlessly. +1. Pull the catalog: Paginate items and item prices from Chargebee and populate catalog tables. +2. Pull active subscriptions: Paginate customer and subscription records, linking Chargebee customers to internal users via metadata or email. +3. Seed version cursors: Populate `chargebee_resource_version` with the current timestamps from the API response so incoming webhooks do not replay older state. +4. Enable webhook workers: Start worker processing. Steady-state updates take over without reprocessing older records. -✅ **Do**: Seed version cursors during backfill to avoid unnecessary worker replay +✅ Do: Seed version cursors during the backfill so incoming webhooks do not replay already-applied state. -⚠️ **Don't**: Relying on webhooks alone to populate state on an empty database +⚠️ Don't: Rely on webhooks alone to populate an empty database. Webhooks only transmit changes, so existing customers and catalog items will be missing. ## See It Running In The Demo App -* [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts) — SQS message consumer pipeline: runs stale checks, enforces parent dependencies, applies upserts, and commits version cursors. -* [`pointer/lib/webhooks/webhook-guards.ts`](../pointer/lib/webhooks/webhook-guards.ts) — Implements isEventStale, versionedResources, and commitVersions backed by the chargebee_resource_version table. -* [`pointer/lib/subscriptions.ts`](../pointer/lib/subscriptions.ts) — Queries active subscription status and item price limits from PostgreSQL for incoming AI requests. -* [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts) — Defines and deploys the Product Catalog 2.0 structure (items, prices, and entitlements) into Chargebee. +Implementation files to review: +* [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts): SQS message consumer pipeline that checks for stale events, enforces parent dependencies, applies upserts, and commits version cursors. +* [`pointer/lib/webhooks/webhook-guards.ts`](../pointer/lib/webhooks/webhook-guards.ts): Implements `isEventStale`, `versionedResources`, and `commitVersions` backed by the `chargebee_resource_version` table. +* [`pointer/lib/subscriptions.ts`](../pointer/lib/subscriptions.ts): Queries active subscription status and item price limits from PostgreSQL for incoming AI requests. +* [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts): Defines and deploys the Product Catalog 2.0 structure (items, prices, and entitlements) into Chargebee. ## Go-Live Checklist - [ ] Does the app treat Chargebee as the single source of truth, routing mutations to Chargebee before updating local state? -- [ ] Is every entity upsert guarded by a check against that resource's stored resource_version? +- [ ] Is every entity upsert guarded by a check against that resource's stored `resource_version`? -- [ ] Does the worker commit the entity changes and the version high-water mark inside the same database transaction? +- [ ] Does the worker commit entity changes and the version high-water mark inside the same database transaction? - [ ] Does the worker acknowledge queue messages only after successful persistence? - [ ] Are subscription upgrades applied to the local mirror immediately using the API response to eliminate UI lag? -- [ ] Does a reconciliation job exist to detect and heal gaps via the List events API (for gaps < 90 days) or resource list APIs? +- [ ] Does a reconciliation job exist to detect and heal gaps via the list events API (for gaps < 90 days) or resource list APIs? -- [ ] Are all catalog entities mapped to Product Catalog 2.0 (items and item_prices) rather than legacy plans? +- [ ] Are all catalog entities mapped to Product Catalog 2.0 (`items` and `item_prices`) rather than legacy plans? -- [ ] Does the cold-start backfill seed resource_version tracking records so initial webhooks do not process as duplicate writes? +- [ ] Does the cold-start backfill seed `resource_version` tracking records so initial webhooks do not process as duplicate writes? diff --git a/how-to/usage-alerts.md b/how-to/usage-alerts.md new file mode 100644 index 0000000..b167588 --- /dev/null +++ b/how-to/usage-alerts.md @@ -0,0 +1,2 @@ +# Usage Alerts + diff --git a/how-to/usage-based-billing.md b/how-to/usage-based-billing.md index cf681d8..9666d51 100644 --- a/how-to/usage-based-billing.md +++ b/how-to/usage-based-billing.md @@ -1,82 +1,87 @@ # How To Implement Usage-Based Billing -Turning raw product activity (API hits, LLM tokens, compute hours) into accurate Chargebee invoices requires decoupling your high-volume product traffic from billing ingestion. This topic covers: +Charging for metered usage (API requests, LLM tokens, or compute hours) requires keeping billing ingestion out of your core application path. Calling external billing APIs during a request adds latency, risks customer-facing failures, and burns through rate limits. -* Recording usage on the hot path without adding latency or risking data loss -* Batching and synchronizing usage events to Chargebee's Advanced Usage-Based Billing (UBB) API -* Enforcing real-time quotas locally while letting Chargebee handle period-end rating and invoicing -* Reconciling invoice lines back to your local raw event history +This guide covers how to: -**Important**: Chargebee is the system of record for billing aggregation and invoicing, but your app is the authority for real-time enforcement. Never call Chargebee's API inside customer-facing request paths. +* Record usage events locally on the hot path with minimal overhead +* Batch and synchronize events to Chargebee's Advanced Usage-Based Billing (UBB) API +* Enforce real-time quotas locally while letting Chargebee handle cycle-end rating and invoicing +* Reconcile invoice line items against your raw event logs + +Chargebee is the system of record for billing aggregation and invoices, but your app is the authority for real-time quota enforcement. Never call Chargebee's API inside user-facing request paths. ## Setup - Product Catalog 2.0 with [metered features](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/defining-metered-features) and linked pricing configured -- A fast local buffer (Redis Stream or durable message queue) on the request path -- A local event store (PostgreSQL) for audit trails and billing reconciliation +- A fast local buffer (such as a Redis Stream or durable message queue) on the request path +- A local event store (such as PostgreSQL) for audit trails and billing reconciliation - A background worker to flush event batches to Chargebee's [Usage Events API](https://apidocs.chargebee.com/docs/api/usage_events) ## 1. How Usage Ingestion Works -When a customer performs a billable action, record the event locally and return immediately. Ingesting synchronously to Chargebee adds latency and fails customer requests if the billing API is slow. + +When a customer performs a billable action, record the event locally and return immediately. Synchronous billing calls add latency and cause user requests to fail whenever the billing API slows down. ```mermaid sequenceDiagram - participant Client - participant App - participant Buffer as Redis Stream / Queue - - Client->>App: API request (tokens / compute) - App->>App: Execute action - App->>Buffer: Buffer usage event (deduplication_id) - App-->>Client: 200 OK (immediate response) + participant Client + participant App + participant Buffer as Redis Stream / Queue + + Client->>App: API request (tokens / compute) + App->>App: Execute action + App->>Buffer: Buffer usage event (deduplication_id) + App-->>Client: 200 OK (immediate response) ``` ### Flow -1. Customer executes an action (e.g. LLM completion, file upload). -2. The application computes usage and assigns a stable `deduplication_id` (such as a UUIDv7 trace ID). -3. The event is written to a fast local buffer (sub-millisecond). -4. The API response returns to the customer without waiting on third-party networks. + +1. A customer triggers a billable action (such as an LLM completion or file export). +2. The application records the usage numbers and generates a stable `deduplication_id` (such as a UUIDv7 trace ID). +3. The app appends the event to a fast local buffer (sub-millisecond). +4. The request returns immediately without waiting on external network calls. ```typescript // Fast, non-blocking usage recording on the request path await stream.xadd("usage_events", "*", "event", JSON.stringify({ - subscription_id: subscriptionId, - usage_timestamp: Date.now(), - deduplication_id: requestId, - properties: { input_tokens: 150, output_tokens: 420 } + subscription_id: subscriptionId, + usage_timestamp: Date.now(), + deduplication_id: requestId, + properties: { input_tokens: 150, output_tokens: 420 } })); - ``` -✅ **Do**: Buffer usage events in-memory or in an append-only stream (sub-millisecond) before returning to the caller. +``` -⚠️ **Don't**: Calling Chargebee's ingest endpoint inline during a user request. External latency degrades user experience and creates cascading timeouts. +✅ Do: Buffer usage events in an in-memory stream or queue before returning the HTTP response. +⚠️ Don't: Call Chargebee's ingest endpoint inline during a user request. External latency degrades user experience and creates cascading timeouts. ## 2. What The Worker Does + The background worker drains buffered events, writes them to your local database for reconciliation, and flushes batches to Chargebee. ```mermaid sequenceDiagram - participant Buffer as Stream / Queue - participant Worker - participant DB as Local Store - box rgba(0,0,0,0.1) Third-party service - participant Chargebee - end - - Buffer->>Worker: Pull batch (up to 500) - Worker->>DB: Archive batch locally - Worker->>Chargebee: POST /usage_events/batch - Chargebee-->>Worker: 200 Accepted - Worker->>Buffer: Ack / trim processed entries + participant Buffer as Stream / Queue + participant Worker + participant DB as Local Store + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + + Buffer->>Worker: Pull batch (up to 500) + Worker->>DB: Archive batch locally + Worker->>Chargebee: POST /usage_events/batch + Chargebee-->>Worker: 200 Accepted + Worker->>Buffer: Ack / trim processed entries ``` ### Flow 1. Read a batch of up to 500 events from the buffer. -2. Persist the raw events to PostgreSQL (the reconciliation source of truth). -3. Check the 12-hour timestamp cutoff; dead-letter expired events. -4. Call Chargebee's `batchIngest` endpoint. -5. Acknowledge and delete entries from the buffer only after Chargebee accepts the batch. +2. Persist the raw events to PostgreSQL as the local source of truth for reconciliation. +3. Check the 12-hour timestamp cutoff and route expired events to a dead-letter queue. +4. Send the batch to Chargebee's `batchIngest` endpoint. +5. Acknowledge and trim entries from the buffer only after Chargebee confirms receipt. ```typescript // Flush up to 500 events per batch and mark synced @@ -85,43 +90,43 @@ await db.usageEvents.markSynced(batch.map((e) => e.deduplicationId)); await stream.xack("usage_events", "worker_group", batch.map((e) => e.streamId)); ``` -**Key points**: +Batching rules: -* Chargebee's batch endpoint accepts a maximum of **500 events** per request. -* Acknowledge messages only after Chargebee returns `200`. -* If Chargebee is temporarily unavailable, leave messages on the buffer and retry with exponential backoff. +* Chargebee accepts up to 500 events per batch request. +* Acknowledge buffer messages only after Chargebee returns HTTP 200. +* If Chargebee is temporarily unavailable, leave messages in the buffer and retry with exponential backoff. -✅ **Do**: Keep batches large (up to 500) to minimize HTTP round-trips and preserve API quota. - -⚠️ **Don't**: Acknowledging the queue before Chargebee returns `200`. A worker crash mid-flight permanently loses billable revenue. +✅ Do: Send large batches (up to 500 events) to minimize HTTP overhead and stay within API rate limits. +⚠️ Don't: Acknowledge buffer messages before Chargebee returns HTTP 200. If the worker crashes mid-flight, unacknowledged events can be safely reprocessed. ## 3. Enforcing Limits: App vs Chargebee + Chargebee calculates usage asynchronously for periodic billing. It cannot evaluate real-time rate limits or quota boundaries at millisecond request speeds. ```mermaid sequenceDiagram - participant Client - participant Gateway as App / Gate - participant Cache as Redis (Local Quota) - - Client->>Gateway: API Request - Gateway->>Cache: Increment & check current quota - alt Quota available - Cache-->>Gateway: OK (within allowance) - Gateway-->>Client: 200 OK (process request) - else Quota exceeded - Cache-->>Gateway: Limit exceeded - Gateway-->>Client: 429 Too Many Requests - end + participant Client + participant Gateway as App / Gate + participant Cache as Redis (Local Quota) + + Client->>Gateway: API Request + Gateway->>Cache: Increment & check current quota + alt Quota available + Cache-->>Gateway: OK (within allowance) + Gateway-->>Client: 200 OK (process request) + else Quota exceeded + Cache-->>Gateway: Limit exceeded + Gateway-->>Client: 429 Too Many Requests + end ``` ### Flow 1. Store allowance thresholds in your local cache (synced via plan entitlements). -2. Increment and check customer counters in Redis on every request. -3. Reject or throttle immediately with `429` or `402` when the limit is breached. -4. Let Chargebee rate overages or aggregate totals at the end of the billing cycle. +2. Increment and check customer counters in Redis on every incoming request. +3. Reject or throttle requests immediately with HTTP 429 or 402 if the quota is exceeded. +4. Let Chargebee rate overages and calculate aggregate totals at cycle close. ```typescript // Real-time quota check in local cache; zero external API latency @@ -131,35 +136,37 @@ if (current > allowance) { } ``` -✅ **Do**: Enforce hard limits and concurrency caps against local counters in Redis. +✅ Do: Enforce hard limits and concurrency caps against local counters in Redis. -⚠️ **Don't**: Polling Chargebee's `usage_charges` endpoint on each request to determine whether to allow user actions. +⚠️ Don't: Poll Chargebee's endpoints on each request to check remaining allowance. Use locally cached limits instead. ## 4. Handling Upgrades and Overages + When a customer exhausts their included allowance, they either purchase credit top-ups, upgrade their tier, or spill over into metered pay-as-you-go pricing. ```mermaid sequenceDiagram - participant Customer - participant App - box rgba(0,0,0,0.1) Third-party service - participant Chargebee - end - - Customer->>Chargebee: Complete checkout (Upgrade / Top-up) - Chargebee-->>Customer: Redirect with checkout success - Customer->>App: Return to app - App->>Chargebee: Fetch updated entitlements - Chargebee-->>App: New limits - App->>App: Reset local Redis counters - App-->>Customer: Feature unlocked immediately + participant Customer + participant App + box rgba(0,0,0,0.1) Third-party service + participant Chargebee + end + + Customer->>Chargebee: Complete checkout (Upgrade / Top-up) + Chargebee-->>Customer: Redirect with checkout success + Customer->>App: Return to app + App->>Chargebee: Fetch updated entitlements + Chargebee-->>App: New limits + App->>App: Reset local Redis counters + App-->>Customer: Feature unlocked immediately ``` ### Flow -1. Customer reaches quota limit; UI displays an upgrade or credit pack prompt. -2. Customer completes checkout via Chargebee Checkout or customer portal. -3. On redirect back to the app, the app immediately fetches updated entitlements from Chargebee. -4. Reset the local usage counters in Redis so the customer can resume work without waiting for webhooks. + +1. The customer reaches their quota limit, and the UI prompts them to upgrade or buy credit packs. +2. The customer completes checkout through Chargebee Checkout or the customer portal. +3. Upon returning to the application, the app immediately fetches the updated entitlements from Chargebee. +4. The app resets the local Redis counters so the customer can resume work without waiting for background webhooks. ```typescript // Optimistic entitlement refresh on checkout completion @@ -167,11 +174,12 @@ await chargebee.subscriptionEntitlement.subscriptionEntitlementsForSubscription( await redis.del(`quota:${subId}:tokens`); ``` -✅ **Do**: Reset local enforcement counters immediately on the checkout return handler. +✅ Do: Reset local enforcement counters immediately in the checkout return handler. -⚠️ **Don't**: Waiting for the `subscription_entitlements_updated` webhook to unlock limits. Webhook delivery delays make users wait minutes after paying. +⚠️ Don't: Wait for the background `subscription_entitlements_updated` webhook before unlocking features. Webhook delivery delays leave paying customers stuck waiting. ## 5. Handling Duplicate and Late Events + Networks drop and workers retry. Chargebee deduplicates using a composite key: `(subscription_id, usage_timestamp, deduplication_id)`. ```sql @@ -184,11 +192,12 @@ ON CONFLICT (deduplication_id) DO NOTHING; ### The 12-Hour Backdating Window Chargebee's Usage Events API enforces a strict constraint: `usage_timestamp` must be within the **last 12 hours**. + | Scenario | Behavior | Resolution | |:---|:---|:---| -| **Worker retry (< 12 hours)** | Chargebee deduplicates using `deduplication_id`. | Safe to retry entire batch. | -| **Worker backlog (> 12 hours)** | Chargebee rejects the event with an error. | Split expired events, move to dead-letter storage, and ingest via Chargebee S3/file bulk import. | -| **Duplicate delivery** | Chargebee ignores the replayed event. | Send stable idempotency key on all requests. | +| **Worker retry (< 12 hours)** | Chargebee deduplicates using `deduplication_id`. | Safe to retry the entire batch. | +| **Worker backlog (> 12 hours)** | Chargebee rejects the event with an error. | Separate expired events, send them to dead-letter storage, and ingest via Chargebee bulk file import. | +| **Duplicate delivery** | Chargebee ignores the replayed event. | Send a stable idempotency key on all requests. | ```typescript // Drop or route expired events before sending batch to Chargebee @@ -196,12 +205,12 @@ const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000; const isExpired = (event: UsageEvent) => (Date.now() - event.usage_timestamp) > TWELVE_HOURS_MS; ``` -✅ **Do**: Use a deterministic UUID or request trace ID as your `deduplication_id`. - -⚠️ **Don't**: Infinitely retrying batches containing events older than 12 hours. The entire batch will fail repeatedly. +✅ Do: Use a deterministic UUID or request trace ID as your `deduplication_id`. +⚠️ Don't: Repeatedly retry batches containing events older than 12 hours. Chargebee will reject the entire batch until the expired events are removed. ## 6. Invoicing and Period-Close Reconciliation + At billing period close, Chargebee aggregates ingested events according to your metered feature SQL definitions and generates an invoice line item. ```mermaid @@ -225,9 +234,9 @@ sequenceDiagram ### Flow 1. Receive the `invoice_generated` webhook event. -2. Query Chargebee's `usage_summaries` API for the invoiced subscription and period dates. -3. Compute the sum of raw events stored in PostgreSQL for the same period. -4. Compare the quantities and trigger an alert if drift exceeds a safety threshold. +2. Query Chargebee's `usage_summaries` API for the invoiced subscription and billing period. +3. Sum the matching raw events stored in PostgreSQL for that same period. +4. Compare the quantities and trigger an alert if the discrepancy exceeds your tolerance threshold. ```typescript // Verify billed invoice quantity matches local event totals @@ -235,49 +244,48 @@ const localSum = await db.getEventSum(subscriptionId, featureId, periodStart, pe const drift = Math.abs(invoiceItem.quantity - localSum); if (drift > TOLERANCE) { - await alertOnCall(Usage drift on ${subscriptionId}: CB=${invoiceItem.quantity}, Local=${localSum}); + await alertOnCall(`Usage drift on ${subscriptionId}: CB=${invoiceItem.quantity}, Local=${localSum}`); } ``` -✅ **Do**: Compare local raw-event rollups against Chargebee's `usage_summaries` automatically on every invoice. - -⚠️ **Don't**: Overwriting local usage data with Chargebee's invoice values without auditing discrepancies. +✅ Do: Compare local raw-event rollups against Chargebee's `usage_summaries` automatically for every generated invoice. +⚠️ Don't: Overwrite local event logs with Chargebee invoice figures without investigating discrepancies first. ## See It Running In The Demo App +Implementation files to review: -- [`pointer/lib/usage/events.ts`](../pointer/lib/usage/events.ts) — The non-blocking domain write API (`recordUsageEvent`). Enqueues generation usage without delaying the AI response. - -- [`pointer/lib/usage/stream.ts`](../pointer/lib/usage/stream.ts) — Redis Streams buffer implementing consumer groups, pending-entry reclaiming, and batch reads. +- [`pointer/lib/usage/events.ts`](../pointer/lib/usage/events.ts): Non-blocking write API (`recordUsageEvent`). Buffers usage data without slowing down user responses. -- [`pointer/lib/usage/flush.ts`](../pointer/lib/usage/flush.ts) — The batch pump that archives entries to PostgreSQL, partitions expired events (> 12 hours), and ingests batches to Chargebee. +- [`pointer/lib/usage/stream.ts`](../pointer/lib/usage/stream.ts): Redis Streams buffer implementing consumer groups, pending-entry reclaiming, and batch reads. -- [`pointer/lib/usage/ingest.ts`](../pointer/lib/usage/ingest.ts) — Wrapper for Chargebee's `usageEvent.batchIngest` API handling batch limits and retry counts. +- [`pointer/lib/usage/flush.ts`](../pointer/lib/usage/flush.ts): Batch worker that archives events to PostgreSQL, isolates events older than 12 hours, and sends batches to Chargebee. -- [`pointer/workers/usage-flush-loop.ts`](../pointer/workers/usage-flush-loop.ts) — Long-running background loop in the worker container driving periodic flushes. +- [`pointer/lib/usage/ingest.ts`](../pointer/lib/usage/ingest.ts): Client wrapper for Chargebee's `usageEvent.batchIngest` API handling batch size limits and retry logic. -- [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts) — Declares metered features (`Input tokens`, `Output tokens`, `Credits consumed`, `Generations`) and maps them to Chargebee event properties. +- [`pointer/workers/usage-flush-loop.ts`](../pointer/workers/usage-flush-loop.ts): Background loop in the worker container that runs periodic flushes. +- [`pointer/scripts/catalog.ts`](../pointer/scripts/catalog.ts): Defines metered features (`Input tokens`, `Output tokens`, `Credits consumed`, `Generations`) and links them to Chargebee event properties. ## Go-Live Checklist - [ ] Is Product Catalog 2.0 active with metered features configured in Chargebee? -- [ ] Is the usage recording call on the request path non-blocking (buffered via Redis or queue)? +- [ ] Is usage recording non-blocking on the request path (buffered via Redis or a queue)? - [ ] Does every usage event carry a unique `deduplication_id`, `subscription_id`, and millisecond `usage_timestamp`? -- [ ] Are events batched up to a maximum of 500 records before calling Chargebee's ingest endpoint? +- [ ] Are events grouped into batches of up to 500 before sending to Chargebee? -- [ ] Does the worker retain events locally in PostgreSQL before calling third-party endpoints? +- [ ] Does the worker persist events in PostgreSQL before calling external billing endpoints? -- [ ] Are messages acknowledged on the buffer only after Chargebee returns `200`? +- [ ] Are buffer messages acknowledged only after Chargebee returns HTTP 200? -- [ ] Does the worker partition out events older than 12 hours so stale payloads don't block fresh batches? +- [ ] Does the worker filter out events older than 12 hours so expired records do not block the queue? -- [ ] Are quotas and rate limits enforced locally against cache, without synchronous Chargebee API calls? +- [ ] Are quotas and rate limits enforced locally against Redis without synchronous Chargebee API calls? -- [ ] Does the checkout success handler refresh entitlements and reset cache counters immediately? +- [ ] Does the checkout return handler refresh entitlements and reset cache counters immediately? -- [ ] Is there an automated reconciliation job comparing `invoice_generated` quantities against local event sums? +- [ ] Is there an automated reconciliation job that compares `invoice_generated` quantities against local event sums? diff --git a/how-to/webhooks.md b/how-to/webhooks.md index f98547f..5ad35a2 100644 --- a/how-to/webhooks.md +++ b/how-to/webhooks.md @@ -1,20 +1,22 @@ # How To Integrate Chargebee Webhooks -Webhooks are usually the first thing you build after the Chargebee SDK is wired up. This topic covers using them to: +Webhooks notify your application when billing events happen in Chargebee: subscriptions are created, payments succeed, or plans change. -* Keep your app's copy of customers, subscriptions and invoices in sync with your Chargebee site -* Trigger your own jobs when a payment, renewal or plan change happens -* Recover from a partial or failed user checkout +This guide covers how to: -**Important**: Chargebee is the source of truth for billing state. Your app keeps a local mirror of it. The webhook endpoint's only job is to accept the event and make it durable. Everything else happens in a background worker. +* Keep your local copy of customers, subscriptions, and invoices in sync with Chargebee +* Trigger custom workflows when payments, renewals, or plan updates occur +* Recover cleanly from interrupted or failed user checkouts + +Chargebee is the single source of truth for billing state. Your app maintains a local read-only mirror. The webhook endpoint's only job is to accept the event and store it durably. Everything else happens asynchronously in a background worker. ## Setup - A webhook configured in Chargebee under **Settings > Configure Chargebee > API Keys and Webhooks** - An HTTPS endpoint protected with [basic authentication](https://www.chargebee.com/docs/billing/2.0/site-configuration/webhook_settings) -- A durable queue (or a table) the endpoint can write to -- A background worker that consumes from that queue -- A table to record which event `id` and `resource_version` you have already applied +- A durable queue (or database table) where the endpoint writes incoming payloads +- A background worker consuming messages from that queue +- A tracking table to record which event `id` and `resource_version` records have already been applied ## 1. How Webhook Delivery Works @@ -38,17 +40,17 @@ sequenceDiagram ### Flow -1. Something changes in Chargebee (a subscription is created, a payment succeeds) -2. Chargebee POSTs the event as JSON to your endpoint -3. The endpoint validates basic auth and writes the event to the queue -4. The endpoint returns `200`, which tells Chargebee the event was delivered -5. The worker picks the event up later and updates the database +1. An event occurs in Chargebee (such as a subscription created or a payment succeeding). +2. Chargebee POSTs the event payload as JSON to your endpoint. +3. The endpoint verifies basic authentication and writes the event to a durable queue. +4. The endpoint returns HTTP 200 to acknowledge receipt. +5. The background worker pulls the event from the queue and updates the local database. -Delivery is **at-least-once** and **unordered**. The same event can arrive twice, and a later event can arrive before an earlier one. Once you answer `200`, Chargebee stops tracking the event, so finishing the work is entirely your app's problem. +Chargebee's webhook delivery is **at-least-once** and **unordered**. The same event can arrive more than once, and an older event can arrive after a newer one. Once you return HTTP 200, Chargebee considers the event delivered, so safe execution rests entirely with your background pipeline. ## 2. What The Endpoint Does -The endpoint has one responsibility: make the event durable, fast. Chargebee's execution timeout on a live site is 60 seconds, and a timeout counts as a failure, so any real work belongs in the worker. +The endpoint has one job: store the event in a durable queue and return quickly. Chargebee enforces a 60-second execution timeout on live sites, and a timeout counts as a delivery failure. Keep all business logic and database mutations inside the worker. ```typescript // Validate, enqueue, answer. No DB writes on this path. @@ -56,19 +58,19 @@ await queue.send({ MessageBody: JSON.stringify(event), MessageDeduplicationId: e return new Response(null, { status: 200 }); ``` -If the enqueue fails, return a `5xx`. Chargebee then [retries the delivery](https://www.chargebee.com/docs/billing/2.0/site-configuration/webhook_settings), so nothing is lost. +If the queue write fails, return a 5xx status code. Chargebee will [retry delivery](https://www.chargebee.com/docs/billing/2.0/site-configuration/webhook_settings) according to its backoff schedule so no event is lost. -✅ **Do**: Return `200` as soon as the event is durably stored +✅ Do: Return HTTP 200 as soon as the event is safely queued. -✅ **Do**: Return `5xx` when you could not store it, and let Chargebee redeliver +✅ Do: Return a 5xx status code if enqueuing fails, prompting Chargebee to retry delivery. -⚠️ **Don't**: Updating your database, calling the Chargebee API, or sending email inside the endpoint. A slow dependency turns into a webhook timeout and a retry storm. +⚠️ Don't: Update your database, call the Chargebee API, or send emails inside the webhook endpoint. Slow external dependencies cause webhook timeouts and retry storms. -⚠️ **Don't**: Returning `200` on an error to "keep Chargebee quiet". The event is then gone for good. +⚠️ Don't: Return HTTP 200 on an ingestion error to silence webhook retries. If the event is dropped, Chargebee will not resend it automatically. ## 3. What The Worker Does -The worker is where the actual sync happens. It is a separate process from your web app, so a burst of webhooks never competes with customer traffic. It runs one pipeline per message and its exit path decides the message's fate. +The background worker handles all entity synchronization. Running it in a process separate from your web application prevents webhook surges from competing with user traffic. ```mermaid sequenceDiagram @@ -91,13 +93,13 @@ sequenceDiagram end ``` -The pipeline, in order: +The worker executes this sequence for each message: -1. Parse the body. A body that will never parse is a poison message (section 7) -2. Skip the event if you have already applied an equal or newer `resource_version` (section 5) -3. Requeue the event if a parent record it references is missing (section 6) -4. Apply the change to the database -5. Record the applied `resource_version`, then acknowledge the message +1. Parse the payload. Malformed bodies that cannot be parsed are poison messages and should be routed to a dead letter queue (Section 7). +2. Check the stored `resource_version` and skip the event if a newer or identical version is already applied (Section 5). +3. Check for parent records and requeue the message if a referenced entity does not exist yet (Section 6). +4. Apply the change to the local database. +5. Commit the updated `resource_version` and acknowledge the queue message. ```typescript // Ack only after the version is committed, so a crash mid-apply replays the event @@ -105,51 +107,51 @@ await applyEvent(event); await commitVersions(event); ``` -**Key points**: +Worker guidelines: -* Acknowledging is the worker's way of saying "done", not "received" -* Throwing (not acknowledging) puts the message back on the queue for another attempt -* Scaling out means running more worker processes — the queue's visibility timeout stops two workers from applying the same event +* Acknowledging a message signals completion, not receipt. +* Throwing an error or leaving a message unacknowledged causes the queue to redeliver it after a timeout. +* Scale worker capacity by running additional consumer processes; queue visibility timeouts prevent concurrent workers from processing the same message. -✅ **Do**: One worker per queue, scaled horizontally, with the queue's retry and dead-letter policy doing the error handling +✅ Do: Scale workers horizontally using queue-driven retries and dead letter policies to handle failures. -⚠️ **Don't**: Doing the sync in a `setTimeout` or fire-and-forget promise in the web process. A deploy or a crash drops the event silently. +⚠️ Don't: Process webhooks inside fire-and-forget promises or timeouts within the web process. Restarts and deployments will drop in-flight events silently. ## 4. Handling Duplicate Deliveries -Retries mean the same event can arrive more than once. The event `id` is unique per event, so use it as the deduplication key. +Because retries happen automatically, the same event can arrive more than once. Use Chargebee's event `id` as an idempotency key. ```sql -- A replay of an already-processed event falls straight through INSERT INTO webhook_event (id) VALUES ($1) ON CONFLICT (id) DO NOTHING; ``` -Chargebee's [last retry lands about 3 days and 7 hours](https://apidocs.chargebee.com/docs/api/events) after the original event, so keep event IDs for at least that long before purging them. +Chargebee retries failed deliveries for up to [3 days and 7 hours](https://apidocs.chargebee.com/docs/api/events). Retain processed event IDs for at least that long before purging them. -✅ **Do**: Writing every applied event `id` to a table, and making your upserts idempotent so a duplicate is a no-op +✅ Do: Record applied event IDs in a dedicated table and make database upserts idempotent. -⚠️ **Don't**: Deduplicating on `(event_type, subscription_id)`. Two legitimate changes to the same subscription share that key. +⚠️ Don't: Deduplicate based on composite keys like `(event_type, subscription_id)`. Legitimate back-to-back updates to the same subscription share that key. ## 5. Handling Out-Of-Order Events -Network delays and retries mean a newer event can arrive before an older one. Every Chargebee resource carries a `resource_version` that increases on each change, so compare it against what you have stored before writing. +Network delays and retries mean an event with a newer state can arrive before an older one. Every Chargebee entity includes a `resource_version` that increments on every mutation. Compare this version against your local record before applying updates. ```typescript // The incoming snapshot is older than what's already applied — ignore it if (event.content.subscription.resource_version <= stored.resourceVersion) return; ``` -`resource_version` is per resource, not per event. An event's `content` can hold a customer, a subscription and an invoice, each with its own version, so check each one you intend to write. +The `resource_version` applies to each individual resource, not the event wrapper. An event's `content` payload may contain a customer, a subscription, and an invoice, each carrying its own independent `resource_version`. Verify each entity individually before updating local tables. -The payload is a point-in-time snapshot taken when the change happened, and it does not change on retries. When you need the current state instead of the snapshot, call the resource's retrieve endpoint. +Webhook payloads reflect the entity snapshot at the time the event fired and do not change during redeliveries. If your application needs current state rather than a historical snapshot, call Chargebee's retrieve endpoint directly. -✅ **Do**: Storing the applied `resource_version` per resource, and only advancing it forward +✅ Do: Track `resource_version` per entity and only advance versions forward. -⚠️ **Don't**: Ordering events by `occurred_at`. It tells you when the change happened, not whether your row is newer. +⚠️ Don't: Rely on `occurred_at` to determine event ordering. `occurred_at` records when the event was generated, not the sequence of mutations applied to the entity. ## 6. Handling Dependent Events That Arrive Early -The classic case: `payment_succeeded` arrives referencing a customer whose `customer_created` has not been processed yet. There is no row to attach the payment to. +A common race condition occurs when `payment_succeeded` arrives before the corresponding `customer_created` event has finished processing. The worker has no existing customer row to attach the payment to. ```mermaid sequenceDiagram @@ -168,7 +170,7 @@ sequenceDiagram Worker->>DB: Apply payment ``` -Treat "dependency not ready" as a *retryable* error. The worker does not acknowledge the message, the queue redelivers it after a delay, and other messages keep flowing past it in the meantime. +Treat a missing parent dependency as a retryable condition. Leave the message unacknowledged or throw a retryable error so the queue redelivers it after a short delay. ```typescript // Not an error to alert on — the parent event is usually seconds behind @@ -177,96 +179,98 @@ if (!(await customerExists(event.content.subscription.customer_id))) { } ``` -Exempt the resource the event itself creates. A `customer_created` event must not wait for the customer to already exist. +Exempt the entity that the event itself creates: a `customer_created` handler must not check for an existing customer record before running. -✅ **Do**: Retrying the dependent event with increasing backoff until its parent lands +✅ Do: Requeue dependent events with progressive backoff until the parent record is created. -⚠️ **Don't**: Dropping the event, or returning `5xx` to Chargebee. You already stored it, so the retry belongs to your queue. +⚠️ Don't: Drop the event or return a 5xx status code to Chargebee. Once an event is enqueued, all retries belong to your internal queue. -⚠️ **Don't**: Fetching the missing parent from the Chargebee API inline. It works, but it hides genuine ordering problems and burns API quota on every gap. +⚠️ Don't: Fetch missing parent entities inline from the Chargebee API. Doing so masks ordering issues and wastes API quota during routine bursts. ## 7. Handling Poison Messages -Some messages will never succeed: an unparseable body, a missing event `id`, a payload your code cannot interpret. Retrying these wastes the retry budget that dependent events need. +Some payloads will never succeed: malformed JSON, missing event IDs, or schemas unsupported by your application code. Continuously retrying these wastes queue capacity and blocks healthy messages. ```typescript // Skip the retries and send it to the dead letter queue immediately if (!event.id) throw new PoisonWebhookError("webhook body is missing an event id"); ``` -Send them to a [dead letter queue](https://en.wikipedia.org/wiki/Dead_letter_queue), alert when it is non-empty, and keep a way to replay messages back onto the main queue once the bug is fixed. Chargebee can also resend a webhook manually from **Logs > Events**, which is the fallback when a message is lost entirely. +Route non-retryable payloads directly to a [dead letter queue](https://en.wikipedia.org/wiki/Dead_letter_queue), set up alerts for when the queue receives messages, and provide a tool to replay messages once a fix is deployed. You can also retrigger failed webhooks from the Chargebee console under **Logs > Events**. -✅ **Do**: Separating "retry patiently" from "fail fast", so a malformed payload and a late parent event get different treatment +✅ Do: Distinguish between temporary dependency delays and permanent payload failures, routing unrecoverable messages straight to a dead letter queue. -⚠️ **Don't**: A single `catch` that retries everything. Poison messages then retry for days and bury the real failures. +⚠️ Don't: Catch all errors indiscriminately and retry every failure. Poison messages will cycle indefinitely and consume your worker retry budget. ## 8. Retry And Retention Settings -Two independent retry loops are in play. Chargebee's loop gets the event to your endpoint; your queue's loop gets it through your worker. +Webhook ingestion involves two independent retry lifecycles: Chargebee retries delivering to your endpoint, and your queue retries delivering to your worker. | | Chargebee to endpoint | Queue to worker | |---|---|---| -| Retries on | Non-`2xx` response or timeout | Unacknowledged message | -| Attempts | 7 | Your `maxReceiveCount` | -| Schedule | 2m, 6m, 30m, 1h, 5h, 1d, 2d | Your backoff policy | -| Total window | ~3 days 7 hours | Your retention period | -| Gives up by | Failure email, manual resend | Dead letter queue | +| Retries on | Non-2xx response or timeout | Unacknowledged message or worker error | +| Attempts | 7 attempts | Configured via `maxReceiveCount` | +| Schedule | 2m, 6m, 30m, 1h, 5h, 1d, 2d | Configured backoff policy | +| Total window | ~3 days 7 hours | Configured queue retention period | +| On exhaustion | Sends failure email, requires manual resend | Routes to dead letter queue | -Back off progressively using the delivery count, rather than retrying at a fixed interval. +Apply exponential backoff based on delivery count rather than retrying at fixed intervals. ```typescript // 30s -> 1m -> 2m -> ... capped at 15m const backoff = Math.min(30 * 2 ** (receiveCount - 1), 900); ``` -Set the queue's retention period well above the largest out-of-order gap you expect. Gaps are normally seconds, so hours-to-days of retention is ample headroom. +Set the queue retention period well above the largest expected processing gap. Webhook processing delays are typically seconds, so several days of queue retention provides ample safety margin. ## 9. Choosing Events And Endpoints -A Chargebee site allows up to five webhooks. Subscribe each one to only the events it acts on, instead of leaving **All Events** selected — fewer deliveries means less noise and less retry load. +Chargebee allows configuring up to five webhook endpoints per site. Subscribe each endpoint only to the event types it needs to handle rather than selecting **All Events**. Filtering reduces network traffic, lowers endpoint load, and avoids processing irrelevant noise. -If you configure more than one endpoint, give each a distinct job. One for the app's billing sync, one for auditing or analytics. Two endpoints both writing the same tables produces duplicate work that your deduplication has to absorb. +When configuring multiple endpoints, divide them by function: for example, one endpoint for core billing synchronization and another for analytics or auditing. Having multiple endpoints write to the same database tables creates redundant work that your idempotency checks must filter. -Check the event's `api_version` against the API version your client library targets. A mismatch means the `content` is structured differently from what your code expects. +Check the `api_version` field on incoming events against the API version supported by your SDK. A version mismatch means the payload structure may differ from what your application expects. -If you already run on AWS and your event volume is high, [Event Streaming via AWS EventBridge](https://www.chargebee.com/docs/billing/2.0/site-configuration/webhook_settings) delivers the same events without an HTTP endpoint to operate. The endpoint in section 2 disappears; everything from section 3 onwards still applies. +If your infrastructure runs on AWS and your event volume is high, [Event Streaming via AWS EventBridge](https://www.chargebee.com/docs/billing/2.0/site-configuration/webhook_settings) delivers webhook events directly to your cloud resources without managing a public HTTP endpoint. -✅ **Do**: One endpoint owning the billing mirror, subscribed to the specific event types your app handles +✅ Do: Dedicate a single endpoint to the billing database mirror and subscribe only to relevant event types. -⚠️ **Don't**: Pointing several services at the same endpoint and fanning out from there without a queue. One slow consumer then times out the webhook for everyone. +⚠️ Don't: Fan out webhooks directly to multiple internal services without a queue. A single slow consumer will cause the webhook endpoint to time out for all services. ## See It Running In The Demo App -- [`pointer/lib/webhooks.ts`](../pointer/lib/webhooks.ts) — the endpoint's publish step. Sends the validated event to SQS using the event `id` as the dedupe key, then returns. +Implementation files to review: + +- [`pointer/lib/webhooks.ts`](../pointer/lib/webhooks.ts): Webhook ingestion endpoint. Validates basic auth, enqueues the event to SQS using the event `id` as the deduplication key, and returns HTTP 200. -- [`pointer/workers/chargebee-webhook-worker.ts`](../pointer/workers/chargebee-webhook-worker.ts) — the consumer loop. Long-polls the queue; returning from the handler acknowledges the message, throwing leaves it for redelivery. +- [`pointer/workers/chargebee-webhook-worker.ts`](../pointer/workers/chargebee-webhook-worker.ts): SQS consumer loop. Long-polls for messages, acks on successful completion, and allows unhandled errors to trigger queue redelivery. -- [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts) — the per-message pipeline: stale check, dependency check, apply, commit versions, ack. +- [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts): Message processing pipeline executing freshness checks, parent dependency validation, database updates, and version commits. -- [`pointer/lib/webhooks/webhook-guards.ts`](../pointer/lib/webhooks/webhook-guards.ts) — the `resource_version` and dependency guards, backed by the `chargebee_resource_version` table. +- [`pointer/lib/webhooks/webhook-guards.ts`](../pointer/lib/webhooks/webhook-guards.ts): `resource_version` guards and parent relationship checks backed by the `chargebee_resource_version` table. -- [`pointer/infra/sqs.tf`](../pointer/infra/sqs.tf) — the queue and DLQ. `maxReceiveCount = 5`, main queue retention 4 days, DLQ retention 14 days, with a CloudWatch alarm when the DLQ is non-empty. +- [`pointer/infra/sqs.tf`](../pointer/infra/sqs.tf): Terraform configuration for the SQS queue and DLQ. Configured with `maxReceiveCount = 5`, 4-day queue retention, 14-day DLQ retention, and CloudWatch alarms for DLQ messages. ## Go-Live Checklist - [ ] Is the webhook URL served over HTTPS and protected with basic authentication? -- [ ] Does the endpoint return `2xx` only after the event is durably stored, and `5xx` when it is not? +- [ ] Does the endpoint return HTTP 200 only after the event is durably stored, and a 5xx code when storage fails? -- [ ] Does the endpoint stay clear of database writes and outbound API calls? +- [ ] Does the endpoint avoid database writes, external API calls, and email delivery? -- [ ] Is every applied event `id` recorded, and kept for at least 3 days and 7 hours? +- [ ] Is every applied event `id` recorded and retained for at least 3 days and 7 hours? -- [ ] Does the worker compare `resource_version` per resource before writing, and only advance it forward? +- [ ] Does the worker check `resource_version` per entity before writing and only advance versions forward? -- [ ] Does the worker retry a missing-dependency event instead of dropping or failing it? +- [ ] Does the worker requeue dependent events instead of dropping them when parent entities are missing? -- [ ] Do malformed payloads reach the dead letter queue without consuming the retry budget? +- [ ] Do malformed payloads route to a dead letter queue without consuming retry attempts? -- [ ] Is the dead letter queue alerted on, and can its messages be replayed onto the main queue? +- [ ] Is the dead letter queue monitored with alerts, and can messages be redriven onto the main queue? -- [ ] Is the queue's retention period longer than the largest out-of-order gap you expect? +- [ ] Is the queue retention period longer than the largest expected out-of-order delay? -- [ ] Is each webhook subscribed to only the event types it handles, rather than **All Events**? +- [ ] Is each webhook subscribed only to the event types it processes, rather than **All Events**? -- [ ] Does the event `api_version` match the API version your client library targets? +- [ ] Does the event `api_version` match the version expected by your application client library? diff --git a/pointer/ARCHITECTURE.md b/pointer/ARCHITECTURE.md index 11b8edc..1db8748 100644 --- a/pointer/ARCHITECTURE.md +++ b/pointer/ARCHITECTURE.md @@ -1,10 +1,12 @@ # Pointer — Architecture Overview -Pointer is a reference SaaS application which integrates with Chargebee for subscription billing and OpenRouter for AI text generation. +Pointer is a reference SaaS application that integrates with Chargebee for subscription billing and OpenRouter for AI text generation. -This document outlines the high level architecture of the app, including it's tech stack and infrastructure. +This document details the system architecture, component boundaries, core task flows, infrastructure design, and local development setup. -## Overview +## 1. System Context + +Pointer connects browser clients, background processing pipelines, and third-party APIs across well-defined network boundaries. ```mermaid flowchart TB @@ -12,19 +14,19 @@ flowchart TB Admin([Browser Admin]) subgraph Platform["Pointer Platform"] - App["pointer-app
    (Next.js 16 + Better Auth)"] - Worker["pointer-worker
    (SQS Consumer + Usage Loop)"] - Queue[["AWS SQS
    (Main Queue + DLQ)"]] - PG[("PostgreSQL 18
    (Mirror & Archive)")] - Redis[("Redis 8
    (Cache, Counters & Streams)")] + App["pointer-app
    (Next.js 16 + Better Auth)"] + Worker["pointer-worker
    (SQS Consumer + Usage Loop)"] + Queue[["AWS SQS
    (Main Queue + DLQ)"]] + PG[("PostgreSQL 18
    (Mirror & Archive)")] + Redis[("Redis 8
    (Cache, Counters & Streams)")] end subgraph ThirdParty["Third-Party Services"] - CB["Chargebee
    (Billing Engine)"] - OR["OpenRouter
    (Model Provider)"] + CB["Chargebee
    (Billing Engine)"] + OR["OpenRouter
    (Model Provider)"] end - style ThirdParty fill:#DDDDDD,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 + style ThirdParty fill:#fffbe6,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 style CB fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px style OR fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px @@ -42,148 +44,241 @@ flowchart TB Worker <-->|Drain buffer & emit events| Redis ``` - -## Tech Stack +## 2. Tech Stack | Layer | Technology | Role | | --- | --- | --- | -| Web + API | **Next.js 16** (App Router, standalone build), **React 19** | Renders the UI and hosts all HTTP endpoints | -| Auth | **Better Auth** | Email/password, 2FA, bearer tokens, organizations, admin | -| Billing | **Chargebee** + [@chargebee/better-auth](https://npmx.dev/@chargebee/better-auth) plugin | Customer/subscription lifecycle and webhook sync | -| Model Provider | **OpenRouter** + `@openrouter/ai-sdk-provider` | External streaming text generation provider | -| Primary DB | **PostgreSQL 18** (via `pg` + Kysely) | Users, sessions, orgs, subscription mirror, usage archive | -| Cache + streams | **Redis 8** (`ioredis`) | Idempotency helpers, usage counters, the usage-event buffer, and the live event stream | -| Async Queue | **AWS SQS** (+ DLQ), `sqs-consumer` | Buffers Chargebee webhooks for the worker | -| Background jobs | Standalone Node process (`tsx`) | Consumes SQS and applies DB sync | +| Web & API | **Next.js 16** (App Router), **React 19** | Hosts user interface, auth routes, and generation endpoints | +| Auth | **Better Auth** | Email/password, 2FA, session tokens, and organizations | +| Third-Party Billing | **Chargebee** + `@chargebee/better-auth` | Customer records, subscription lifecycle, and customer portal | +| Third-Party Models | **OpenRouter** + `@openrouter/ai-sdk-provider` | External streaming text generation provider | +| Primary Database | **PostgreSQL 18** (`pg` + Kysely) | Users, accounts, subscription mirror, and partitioned usage archive | +| Cache & Streams | **Redis 8** (`ioredis`) | Quota counters, usage-event buffer, and live event bus | +| Async Queue | **AWS SQS** (+ DLQ), `sqs-consumer` | Buffers incoming Chargebee webhooks and entitlement sync jobs | +| Background Worker | Standalone Node process (`tsx`) or Lambda | Consumes SQS webhooks and runs periodic usage flush passes | -## Components +## 3. Platform Components ### 3.1 `pointer-app` -The single web/API application. It is responsible for: +The web and API service is responsible for: -- **Authentication & accounts** — Better Auth with the organization plugin. Personal accounts - bill against the user; Team accounts bill against the organization. -- **Chargebee provisioning** — on user creation a Chargebee customer is created (or reused), - its ID is stored on the `user` row, and an idempotent free subscription is created. The - home page waits for the subscription webhook before opening, then uses free-tier defaults - while the queued entitlement snapshot finishes loading. -- **Webhook ingress** — the Chargebee webhook endpoint validates HTTP Basic Auth, parses the - payload, and enqueues it to SQS. It intentionally does **no** database work, so a webhook - storm can never degrade the web tier. -- **Live event stream** — publishes domain events to a Redis stream and exposes them over SSE - for the `/admin/flow` visualization. +- **Authentication and accounts**: Better Auth with the organization plugin. Personal accounts bill against the individual user; team accounts bill against the organization. +- **Chargebee provisioning**: On user creation, a Chargebee customer record is created (or reused), its ID is recorded on the `user` table, and an idempotent free subscription is provisioned with `billing_cycles: 0`. The home page waits for the initial subscription webhook before rendering, using free-tier defaults while the queued entitlement snapshot completes. +- **Webhook ingress**: The Chargebee webhook endpoint verifies HTTP Basic Auth, parses payloads, and immediately pushes them to AWS SQS. It executes no database operations, preventing webhook traffic spikes from exhausting connection pools or slowing HTTP requests. +- **Live event stream**: Publishes domain events to a Redis stream, exposed via Server-Sent Events (SSE) for the `/admin/flow` diagnostic visualization. ### 3.2 `pointer-worker` -The worker owns all webhook-driven database sync and can run as either an ECS service (the -default) or an SQS-triggered Lambda function, selected by Terraform's `worker_runtime` -variable. The runtimes are mutually exclusive and consume the same durable queue. Both call -the same per-message correctness pipeline that the plugin alone can't provide: +The worker owns all webhook-driven database synchronization and periodic background tasks. It can run as either an ECS Fargate service (the default) or an SQS-triggered Lambda function, configured by Terraform's `worker_runtime` variable. Both runtimes execute the same message pipeline: ``` -parse → stale/duplicate guard → dependency pre-check → process - → verify-after-process → commit versions → ack +parse -> stale/duplicate guard -> dependency pre-check -> process -> verify-after-process -> commit versions -> ack ``` -- **Idempotency without an inbox** — SQS is at-least-once, but the sync hooks are idempotent - and a `resource_version` guard turns a stale or redelivered event into a no-op. -- **Error handling** — transient/dependency-not-ready errors get an increasing backoff (via - `ChangeMessageVisibility`) and are left on the queue; after 5 attempts SQS auto-routes them - to the DLQ. Malformed "poison" messages are sent straight to the DLQ. -- **Scaling** — ECS scales tasks from queue-depth alarms. Lambda uses a bounded SQS event - source concurrency so its warm connection pools cannot overwhelm PostgreSQL or Redis. - SQS visibility prevents simultaneous processing; no leader election is required. -- **Lambda batches** — partial batch responses retry only failed records. A cold Lambda loads - the existing application and database secrets from Secrets Manager before constructing the - Better Auth processor; warm environments reuse the processor and connection pools. -- **Networking** — Lambda runs in private subnets for RDS/Redis and reaches Chargebee and AWS - APIs through a NAT gateway. The POC uses one NAT; production should use one per AZ. -- **Entitlement jobs** — after the subscription-created webhook writes the subscription and - item rows, the same queue receives a job for its entitlement snapshot. Jobs skip the webhook - pipeline, since there is no additional Chargebee event to order or verify, but reuse the - backoff and DLQ. -- **Usage flush** — a second loop drains the Redis usage buffer into Postgres and then - Chargebee, up to 500 events per pass (Chargebee's batch ceiling). Both sinks are fed from - one pass rather than a second consumer group, because settling an entry deletes it from the - stream: whichever group acknowledged first would take it away from the other. A failed - Postgres write abandons the pass without acknowledging anything, since history is the read - path and a silent gap would be visible to the subscriber. It rides this process rather than - a service of its own: the work is a periodic drain and this is already a long-running task in the VPC - with a Redis connection. Redis consumer groups spread entries across however many tasks - autoscaling creates, so no leader election is needed — the same property SQS provides for - webhooks. The loop requires the ECS runtime; `lib/usage/flush.ts` is runtime-agnostic so a - scheduled Lambda could drive it instead. - -See [`docs/plans/5-webhook-correctness.md`](docs/plans/5-webhook-correctness.md) for the full rationale. - -### 3.3 Data stores - -- **PostgreSQL** — the primary store. Better Auth manages the core schema (users, sessions, - organizations, members) plus the Chargebee plugin's tables and a small - `chargebee_resource_version` table used for out-of-order protection. Schema changes are - applied by Better Auth's migration CLI, run as a dedicated one-off task. - It also holds `usage_event`, the durable usage archive. That table is range-partitioned by - ISO week so a period's worth of history is a handful of contiguous partitions and ageing - data can be dropped by detaching one; `pg_cron` provisions the coming fortnight's - partitions nightly, with a `DEFAULT` partition catching anything it misses. A single - unique index on `("subscriptionId", "usageTimestamp", "deduplicationId")` does double duty - — it is the history query's range scan and the conflict target that makes the flush loop's - at-least-once replay a no-op — so the write path pays for exactly one index. The CLI cannot - express partitioning, so `lib/usage/partitions.ts` owns that DDL while - `plugins/usage-plugin.ts` keeps the columns under CLI management. -- **Redis** — hosts the Redis-Streams event bus for the live flow view, the quota and rate - counters, and the usage-event buffer awaiting its next Chargebee batch. The event bus is a - best-effort observation tap: if it is down, publishing fails silently and the caller is - unaffected. The usage buffer is held to a higher bar — it is read through a consumer group - so an unacknowledged batch survives a worker crash — but it is not as durable as SQS. A node - loss drops at most one flush interval of events. Production should enable AOF or add a - replica; the POC runs a single `cache.t4g.micro`. +- **Idempotency without an inbox**: SQS delivers messages with at-least-once guarantees. The worker uses a `chargebee_resource_version` table to guard against out-of-order or duplicate updates. Stale events turn into immediate no-ops. +- **Error handling**: Transient errors and missing dependencies trigger a visibility timeout increase (`ChangeMessageVisibility`) so the message remains in the queue for later redelivery. SQS moves the message to the DLQ after 5 failed attempts. Malformed poison messages route to the DLQ immediately. +- **Scaling**: Under ECS, tasks scale horizontally on queue depth alarms (`ApproximateNumberOfMessagesVisible`). Under Lambda, SQS event source concurrency is capped to protect database and Redis connection pools. SQS message visibility prevents concurrent processing of the same message without requiring leader election. +- **Lambda batching**: Lambda partial batch responses acknowledge successful records while retrying only failed entries. Warm instances reuse database connection pools and the initialized Better Auth processor. +- **Networking**: Worker tasks run in private subnets with direct access to RDS and Redis. Outbound traffic to Chargebee and AWS APIs routes through a NAT gateway. +- **Entitlement sync jobs**: After the subscription-created webhook updates the subscription mirror, an entitlement snapshot job is queued to the same SQS queue. These jobs bypass webhook version guards while sharing the same retry, backoff, and DLQ infrastructure. +- **Usage flush loop**: In the ECS runtime, a second loop drains usage records from the Redis buffer into PostgreSQL and flushes batches to Chargebee (up to 500 events per pass). Both sinks are populated in a single pass before acknowledging the Redis stream. If the PostgreSQL insert fails, the loop halts without acknowledging Redis, preventing gaps in audit history. + +### 3.3 Data Stores + +- **PostgreSQL 18**: Primary application datastore. Better Auth manages the core schema (users, sessions, organizations, members), Chargebee plugin tables, and `chargebee_resource_version`. Migrations run as a dedicated one-off task during deployments. + +The `usage_event` table stores the durable usage archive, range-partitioned by ISO week. Weekly partitioning allows dropping aged billing periods cleanly without vacuum overhead. The `pg_cron` extension provisions future partitions nightly, while a `DEFAULT` partition catches unexpected timestamps. A composite unique index on `("subscriptionId", "usageTimestamp", "deduplicationId")` serves both range scan queries and conflict targets for idempotent batch inserts. Partition DDL is managed in `lib/usage/partitions.ts`. + +- **Redis 8**: Hosts the event stream for live flow diagnostics, rate and quota counters, and the usage-event buffer awaiting batch ingestion. The diagnostic event bus is best-effort: connection drops fail silently without impacting caller requests. The usage buffer uses consumer groups so unacknowledged batches survive task crashes. ### 3.4 Chargebee -The external billing **system of record**. The app never treats its local Postgres data as -authoritative for billing state — it mirrors Chargebee via webhooks and reconciles using -resource versions. See [`docs/03-chargebee-source-of-truth.md`](docs/03-chargebee-source-of-truth.md). +Chargebee is the external billing system of record. Pointer never treats local PostgreSQL tables as authoritative for billing status. Local tables act as a read mirror updated via webhooks and validated against `resource_version`. +## 4. Core Task Flows -## 4. Infrastructure +### 4.1 User Sign-Up & Subscription Flow -Everything is defined as **Terraform** under [`infra/`](infra/) and runs on **AWS**. The stack -is intentionally lean for a proof-of-concept, with production trade-offs documented in the -infra README. +When a user registers: +1. Better Auth creates the user and session in PostgreSQL. +2. The Chargebee plugin calls the Chargebee API to create a customer record. +3. Pointer creates an idempotent free subscription with `billing_cycles: 0`. +4. PostgreSQL stores the Chargebee customer identifier on the user record. ```mermaid -flowchart TB - Route53["Route53
    pointer.chargebee-labs.com"] --> ALB["ALB
    HTTPS, TLS 1.2+"] - ALB --> AppSvc["ECS service: pointer-app
    Fargate (Next.js :3000)"] - subgraph ECS["ECS Fargate cluster (pointer-cluster)"] - AppSvc - WorkerSvc["ECS service: pointer-worker
    queue-depth autoscaling"] - Migrate["one-off task: pointer-app-migrate"] - end - AppSvc --> RDS[("RDS Postgres
    encrypted, TLS")] - WorkerSvc --> RDS - AppSvc --> SQS[["SQS queue + DLQ"]] - WorkerSvc --> SQS - Secrets["Secrets Manager
    db + app secrets"] -.-> AppSvc - Secrets -.-> WorkerSvc - ECR["ECR: pointer-app"] -.image.-> ECS +flowchart LR + User([User]) + subgraph App["Pointer App"] + Auth["Better Auth
    /api/auth/sign-up"] + PG[("PostgreSQL")] + end + subgraph ThirdParty["Third-Party Services"] + CB["Chargebee API"] + end + + style ThirdParty fill:#fffbe6,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 + style CB fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px + + User -->|1. Sign up| Auth + Auth -->|2. Insert user & session| PG + Auth -->|3. Create customer & free plan| CB + CB -->|4. Return customer & sub IDs| Auth + Auth -->|5. Update user with Chargebee ID| PG +``` + +### 4.2 Webhook Ingestion Flow + +Chargebee notifies Pointer of billing events via HTTP POST. The webhook endpoint performs zero database operations: +1. Endpoint verifies Basic Auth credentials and signatures. +2. Endpoint sends the raw event to AWS SQS. +3. App returns HTTP 200 immediately (~30ms). + +```mermaid +flowchart LR + subgraph ThirdParty["Third-Party Services"] + CB["Chargebee"] + end + subgraph App["Pointer App"] + Endpoint["/api/auth/[...all]
    Webhook Receiver"] + end + Queue[["AWS SQS
    chargebee-webhooks"]] + + style ThirdParty fill:#fffbe6,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 + style CB fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px + + CB -->|1. POST webhook event| Endpoint + Endpoint -->|2. Validate & enqueue payload| Queue + Endpoint -->|3. Return 200 OK (~30ms)| CB +``` + +### 4.3 Asynchronous Webhook Processing Flow + +The background worker consumes events from SQS and applies them to PostgreSQL: +1. Worker long-polls messages from SQS. +2. Stale guard checks `chargebee_resource_version`. Older or duplicate versions are skipped. +3. Worker applies changes to local PostgreSQL tables inside a transaction. +4. For new subscriptions, an entitlement refresh job is queued to the same SQS queue. +5. On success, the worker deletes the message from SQS. Poison messages route to the DLQ after 5 retries. + +```mermaid +flowchart LR + Queue[["AWS SQS
    chargebee-webhooks"]] + DLQ[["AWS SQS DLQ"]] + subgraph Worker["pointer-worker"] + Pipeline["Correctness Pipeline
    Guard -> Sync -> Verify"] + end + PG[("PostgreSQL
    Subscription Mirror")] + Redis[("Redis
    Domain Events")] + + Queue -->|1. Long-poll message| Pipeline + Pipeline -->|2. Check version & write mirror| PG + Pipeline -->|3. Emit domain event| Redis + Pipeline -->|4. Delete message| Queue + Pipeline -.->|Failed 5 times / poison| DLQ +``` + +### 4.4 Text Generation & Metering Flow + +When an authenticated user requests text generation: +1. The application resolves entitlement limits and checks quota counters in Redis. +2. The request streams directly from OpenRouter API using `@openrouter/ai-sdk-provider`. +3. Output tokens stream to the user. If quota runs out mid-stream, the connection closes cleanly. +4. Finished generation appends a usage record to the Redis `usage:events` stream. + +```mermaid +flowchart LR + User([User]) + subgraph App["Pointer App"] + Route["/api/generate"] + Gate["Entitlement Gate
    & Quota Checker"] + end + subgraph ThirdParty["Third-Party Services"] + OR["OpenRouter API
    Model Provider"] + end + Redis[("Redis
    usage:events Stream")] + + style ThirdParty fill:#fffbe6,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 + style OR fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px + + User -->|1. POST prompt| Route + Route -->|2. Verify quota| Gate + Gate <-->|Read & increment counters| Redis + Route -->|3. Stream request| OR + OR -->|4. Return token deltas| Route + Route -->|5. Stream response| User + Route -->|6. Append usage record| Redis +``` + +### 4.5 Asynchronous Usage Batch Flush Flow + +The worker periodically drains usage records from the Redis stream: +1. Reads up to 500 entries per batch using a Redis consumer group. +2. Inserts the batch into weekly-partitioned PostgreSQL table `usage_event` (`ON CONFLICT DO NOTHING`). +3. Sends the batch to Chargebee's `/usage_charges` endpoint using idempotency keys. +4. Acknowledges and removes processed entries from Redis. + +```mermaid +flowchart LR + Redis[("Redis
    usage:events Stream")] + subgraph Worker["pointer-worker (Flush Loop)"] + Batch["Batch Consumer
    (Max 500 events/tick)"] + end + PG[("PostgreSQL
    Partitioned usage_event")] + subgraph ThirdParty["Third-Party Services"] + CB["Chargebee
    /usage_charges API"] + end + + style ThirdParty fill:#fffbe6,stroke:#faad14,stroke-width:1.5px,stroke-dasharray: 4 4 + style CB fill:#fff7e6,stroke:#d46b08,stroke-width:1.5px + + Redis -->|1. Read batch| Batch + Batch -->|2. Bulk insert archive| PG + Batch -->|3. POST batch charges| CB + Batch -->|4. XACK & XDEL entries| Redis +``` + +### 4.6 Live Event Stream Flow (Admin Flow View) + +Administrators view events in real time at `/admin/flow`: +1. The app and worker publish non-blocking domain events to Redis. +2. The `/api/events/stream` route opens a Server-Sent Events (SSE) connection. +3. The browser listens to the SSE stream and highlights active nodes on the canvas. + +```mermaid +flowchart LR + subgraph Producers["Pointer Services"] + App["pointer-app"] + Worker["pointer-worker"] + end + Redis[("Redis
    events:domain Stream")] + SSE["/api/events/stream
    SSE Endpoint"] + Admin([Admin Browser]) + + App -->|Publish event| Redis + Worker -->|Publish event| Redis + Redis -->|Read new entries| SSE + SSE -->|Push event packets| Admin ``` -| Area | What runs | -| --- | --- | -| **Network** | `pointer-vpc` (10.20.0.0/16), Internet Gateway, 2 public subnets across 2 AZs | -| **Edge** | ALB (HTTPS only, HTTP→HTTPS redirect); Route53 alias to `pointer.chargebee-labs.com`; ACM cert looked up | -| **Compute** | ECS Fargate cluster with `pointer-app` (web), `pointer-worker` (webhook consumer), and a short-lived `pointer-app-migrate` task | -| **Images** | ECR repo `pointer-app`; a slim standalone `:latest` for the app, a `builder` image for worker + migrations | -| **Data** | RDS Postgres (`db.t4g.micro`, single-AZ, encrypted at rest, TLS enforced) | -| **Async** | SQS main queue + DLQ (SSE on both, `maxReceiveCount=5`) | -| **Secrets** | Secrets Manager holds DB credentials and app/Chargebee secrets, injected into tasks at start | -| **Logs** | CloudWatch log groups per service (14-day retention) | +## 5. Architecture Decisions & Anti-Patterns Avoided + +### 5.1 No Database Writes in Webhook Endpoint + +Handling database mutations directly inside webhook handlers risks exhausting database connection pools during traffic spikes and stalling concurrent user requests. Pointer's webhook route only verifies credentials and publishes the raw payload to AWS SQS in roughly 30ms, letting background workers process updates at a steady rate. + +### 5.2 Resource Versioning Instead of an Inbox Table + +Maintaining a dedicated inbox table of processed webhook IDs adds a synchronous database round trip to every message and grows indefinitely. Pointer relies on idempotent handlers guarded by the `chargebee_resource_version` table. If a duplicate or out-of-order event arrives with an older version, the worker drops it immediately as a no-op. + +### 5.3 Chargebee as Billing System of Record + +Treating local application tables as the primary billing authority leads to drift whenever webhooks fail, queue up, or arrive out of sequence. Chargebee remains the authoritative system of record. Local PostgreSQL tables serve as a read mirror that stays current through webhooks and periodic reconciliation. + +### 5.4 Partitioned Database for Usage History -**Deployment:** A new image is built and pushed to ECR, Better Auth migrations run as a one-off Fargate task against the new schema, then the `pointer-app` and `pointer-worker` services are rolled onto the new image. +Querying Chargebee's API whenever a customer opens a billing dashboard introduces latency and quickly burns through upstream rate limits. Instead, Redis handles sub-millisecond live quota checks, while PostgreSQL stores historical records in the `usage_event` table partitioned by ISO week. Account dashboards query this local table directly. -**Autoscaling:** The worker scales on SQS backlog(`ApproximateNumberOfMessagesVisible`): scale out when the queue is deep, scale in when it drains, within configurable min/max bounds — so webhook bursts are absorbed without touching the web tier. +### 5.5 Single-Pass Flush for Two Sinks -**Security** Encryption at rest (RDS/SQS/Secrets/ECR) and in transit (ALB TLS 1.2+, RDS `force_ssl`), security groups that reference each other rather than open CIDRs, RDS not publicly reachable, and IAM scoped to specific secret and queue ARNs. +Splitting the usage stream between two independent Redis consumer groups creates a race condition: acknowledging an entry deletes it from the stream (`XDEL`), starving whichever consumer runs slower. Pointer uses a single worker loop that writes to PostgreSQL first, flushes to Chargebee second, and acknowledges the Redis stream only after both operations succeed. diff --git a/pointer/INFRA.md b/pointer/INFRA.md new file mode 100644 index 0000000..90b6361 --- /dev/null +++ b/pointer/INFRA.md @@ -0,0 +1,86 @@ +# Pointer Infrastructure + +The production environment runs on **AWS** managed via Terraform under [`infra/`](infra/). + +```mermaid +flowchart TB + Route53["Route 53 DNS
    pointer.chargebee-labs.com"] --> ALB["Application Load Balancer
    HTTPS (TLS 1.2+)"] + ALB --> AppTask["ECS Task: pointer-app
    (Next.js Port 3000)"] + + subgraph VPC["AWS VPC (10.20.0.0/16)"] + subgraph PublicSubnet["Public Subnets"] + ALB + NAT["NAT Gateway"] + end + + subgraph Cluster["ECS Fargate (pointer-cluster)"] + AppTask + WorkerTask["ECS Task: pointer-worker
    (SQS Worker)"] + MigrateTask["Task: pointer-app-migrate
    (One-off Migrations)"] + end + + subgraph Backend["Private Subnets"] + RDS[("RDS PostgreSQL 18
    Multi-AZ Capable, TLS")] + SQS[["AWS SQS
    Queue + DLQ"]] + end + end + + AppTask --> RDS + WorkerTask --> RDS + AppTask --> SQS + WorkerTask --> SQS + WorkerTask -.->|Outbound API calls| NAT +``` + +| Area | Component | Configuration | +| --- | --- | --- | +| Edge | Route 53 + ALB | ACM SSL certificate, TLS 1.2+, HTTP to HTTPS redirect | +| Compute | AWS ECS Fargate | `pointer-app` (web), `pointer-worker` (worker), and migration task | +| Worker Scaling | ECS CloudWatch Alarm | Autoscales based on SQS `ApproximateNumberOfMessagesVisible` | +| Database | AWS RDS PostgreSQL 18 | `db.t4g.micro`, encrypted at rest, TLS enforced (`force_ssl`) | +| Queue | AWS SQS | Main queue + DLQ (`maxReceiveCount = 5`), server-side encryption | +| Secrets | AWS Secrets Manager + KMS | DB credentials, Chargebee keys, and application secrets | + +## Deployment Lifecycle + +Deployments build and push new container images to Amazon ECR. Better Auth schema migrations run as a one-off Fargate task (`pointer-app-migrate`) against the target database before services roll over. Once migrations complete, `pointer-app` and `pointer-worker` update to the new image via rolling deployments. + +## Worker Autoscaling + +The worker service scales on SQS queue backlog (`ApproximateNumberOfMessagesVisible`). When queue depth rises during webhook bursts, additional worker tasks launch to absorb the load. When the queue drains, tasks scale in down to the configured minimum bound, protecting the web tier from processing backlog. + +## Network and Data Security + +- **Encryption**: Data is encrypted at rest across RDS, SQS, Secrets Manager, and ECR. Transport encryption uses TLS 1.2+ on the ALB and SSL enforcement on RDS (`force_ssl`). +- **Network isolation**: Application and worker tasks run in private subnets. RDS and Redis instances are inaccessible from the public internet. Security groups reference each other directly rather than exposing open CIDR blocks. +- **Least privilege IAM**: Task roles are scoped to explicit Secrets Manager and SQS ARNs, preventing unauthorized access across services. + + +## Scaling + + + +## Deploying in other public clouds + +The following table shows the AWS equivalent services for GCP and Azure if the app is to be deployed in those clouds. Note that the Terraform definitions have to be rewritten as the provided `infra` is specific to AWS. + +| AWS | GCP | Azure | +| --- | --- | --- | +| Amazon EC2 | Compute Engine | Azure Virtual Machines | +| AWS Lambda | Cloud Run functions | Azure Functions | +| Amazon ECS with AWS Fargate | Cloud Run | Azure Container Apps | +| Amazon ECR | Artifact Registry | Azure Container Registry | +| Amazon VPC | Virtual Private Cloud | Azure Virtual Network | +| AWS NAT Gateway | Cloud NAT | Azure NAT Gateway | +| Elastic Load Balancing (Application Load Balancer) | Cloud Load Balancing | Azure Application Gateway | +| Amazon Route 53 | Cloud DNS | Azure DNS | +| AWS Certificate Manager | Certificate Manager | Key Vault Certificates | +| Amazon RDS for PostgreSQL | Cloud SQL for PostgreSQL | Azure Database for PostgreSQL | +| Amazon ElastiCache for Redis | Memorystore for Redis | Azure Managed Redis | +| Amazon SQS | Pub/Sub | Azure Service Bus queues | +| Amazon SNS | Pub/Sub | Azure Service Bus topics | +| AWS Secrets Manager | Secret Manager | Azure Key Vault | +| AWS KMS | Cloud Key Management Service | Azure Key Vault | +| Amazon CloudWatch | Cloud Monitoring and Cloud Logging | Azure Monitor | +| AWS IAM | Cloud IAM | Microsoft Entra ID and Azure RBAC | +| Amazon S3 | Cloud Storage | Azure Blob Storage | diff --git a/pointer/README.md b/pointer/README.md index 628f181..6c0325f 100644 --- a/pointer/README.md +++ b/pointer/README.md @@ -18,3 +18,10 @@ 4. `pnpm dev` to start the server Open [http://localhost:3000](http://localhost:3000) in your browser to play around! + +### Processing webhook events + +To receive and proces webhook events from Chargebee, use a tunneling service like [Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/) or [ngrok](https://ngrok.com/) to forward events to your local setup. Once you have a [tunnel setup](https://www.chargebee.com/docs/billing/2.0/kb/platform/how-to-test-webhooks-on-staging-which-requires-vpn), run `pnpm run worker:chargebee` to start the background worker. + +The incoming webhook events are pushed to the LocalStack SQS queue, which is polled and processed by the webhook worker. + From 811f4c2a6281f5c5ea4df7cba4d797dd2f0ee44c Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Tue, 29 Sep 2026 11:02:20 +1000 Subject: [PATCH 08/12] fix mermaid diagram --- pointer/ARCHITECTURE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pointer/ARCHITECTURE.md b/pointer/ARCHITECTURE.md index 1db8748..ee3d9ab 100644 --- a/pointer/ARCHITECTURE.md +++ b/pointer/ARCHITECTURE.md @@ -149,7 +149,7 @@ flowchart LR CB -->|1. POST webhook event| Endpoint Endpoint -->|2. Validate & enqueue payload| Queue - Endpoint -->|3. Return 200 OK (~30ms)| CB + Endpoint -->|3. Return 200 OK | CB ``` ### 4.3 Asynchronous Webhook Processing Flow From 987b0a590658a3a86b6067ce7ddaf13565f75cf8 Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Tue, 29 Sep 2026 14:28:48 +1000 Subject: [PATCH 09/12] track in-progress work --- README.md | 2 + how-to/checkout-experience.md | 2 + how-to/usage-alerts.md | 373 +++++++++++++++++++++++++++++++++- how-to/usage-based-billing.md | 1 + 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 how-to/checkout-experience.md diff --git a/README.md b/README.md index 7a868b9..af5e8d4 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ While the Chargebee [docs](https://chargebee.com/docs) and [apidocs](https://api | [Enforcing entitlement checks](./how-to/entitlement-checks.md) | In Review | | | [Sync Chargebee entities](./how-to/sync-chargebee-entities.md) | Draft | | | [Usage-based billing](./how-to/usage-based-billing.md) | Draft | | +| [Usage alerts](./how-to/usage-alerts.md) | In Progress | | +| [Checkout experience](./how-to/checkout-experience.md) | In Progress | | ## Reference App diff --git a/how-to/checkout-experience.md b/how-to/checkout-experience.md new file mode 100644 index 0000000..9b2a574 --- /dev/null +++ b/how-to/checkout-experience.md @@ -0,0 +1,2 @@ +# Checkout Experience + diff --git a/how-to/usage-alerts.md b/how-to/usage-alerts.md index b167588..c6425b0 100644 --- a/how-to/usage-alerts.md +++ b/how-to/usage-alerts.md @@ -1,2 +1,373 @@ -# Usage Alerts +# How To Configure Usage Alerts +When billing on consumption, customers need advance warning before unexpected overage charges appear on an invoice. Chargebee's usage alerts can be configured to notify the app that the aggregate of a metered feature is within a certain threshold. This ping is received by the app as a webhook event and can then be used appropriately to notify the end user. + +This guide covers how to: + +* Configure Chargebee usage alerts on metered features and process `alert_status_changed` webhooks +* Separate Chargebee billing alerts from real-time in-app quota enforcement on non-metered features +* Handle limit resets at billing cycle boundaries and term restarts +* Manage quota adjustments, prorations, and alert states during mid-term subscription changes + +Chargebee stores billing alerts and evaluates thresholds. Your application handles immediate customer notifications and real-time request enforcement. + +## Setup + +- Usage ingestion pipeline sending events to Chargebee (see [usage-based billing](./usage-based-billing.md)) +- Webhook endpoint configured to receive `alert_status_changed` events (see [webhook integration](./webhooks.md)) +- A local cache (such as Redis) to store alarm states for fast UI rendering + +## 1. How Usage Alerts Work + +Chargebee evaluates [usage alerts](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/usage-alerts) within seconds of receiving usage events. You define an alert rule for a metered feature with a specific threshold, such as 80% of included quota or an absolute quantity. + +When consumption crosses the threshold, Chargebee updates the subscription alert status from `within_limit` to `in_alarm` and posts an `alert_status_changed` webhook to your application. + +```mermaid +sequenceDiagram + participant App + participant Worker as Ingest Worker + box rgba(0,0,0,0.1) External + participant Chargebee + end + participant Webhook as Webhook Worker + participant Cache as Redis + participant User as Customer + + App->>Worker: Buffer usage events + Worker->>Chargebee: POST /usage_events/batch + Chargebee->>Chargebee: Evaluate alert threshold + Chargebee->>Webhook: POST alert_status_changed (in_alarm) + Webhook->>Cache: Set pointer:alarm:sub_id:alert_id + Webhook->>User: Send email / show in-app banner +``` + +### Flow + +1. You create an alert in Chargebee under **Usages > Alerts** or through the API, selecting a metered feature, a threshold percentage (such as 80% or 100%), and optional plan filters (see [setting up usage alerts](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/setting-up-usage-alerts)). +2. As customers use your product, your background worker sends usage events to Chargebee's `batchIngest` API. +3. Chargebee evaluates the aggregated events against active alert rules. +4. If a subscription crosses the threshold, Chargebee sets its alert status to `in_alarm` and delivers an `alert_status_changed` webhook. +5. Your webhook worker saves the alarm state to a fast local store (such as Redis) and triggers customer-facing notifications. + +### Alert vs Alert Status + +| Concept | What it represents | How you interact with it | +|:---|:---|:---| +| Alert | The configuration rule: feature, threshold, and target plans | Create, update, disable, or delete via UI or API | +| Alert Status | The live subscription state: `within_limit` or `in_alarm` | Read-only state computed automatically by Chargebee | + +### Scope: Global vs Subscription-Scoped + +| Dimension | Global Alert | Subscription-Scoped Alert | +|:---|:---|:---| +| Scope | All subscriptions using the metered feature | One specific subscription | +| Filter conditions | Filter by `plan_price_id` to target specific plans | Not supported; bound to `subscription_id` | +| Alert status | One status record per matching subscription | Single status record | +| Primary use case | Standard milestones (such as 80% warnings) across plans | Custom thresholds negotiated for enterprise accounts | + +When handling the `alert_status_changed` webhook, persist the alarm to Redis so user interfaces can render warning banners immediately. + +```typescript +// Process alert_status_changed webhooks from Chargebee +export async function processAlertWebhook(event: WebhookEvent): Promise { + if (event.event_type !== "alert_status_changed") return false; + + const { alert, alert_status: status } = event.content as { + alert?: { id: string; name: string; metered_feature_id: string }; + alert_status?: { subscription_id: string; alarm_status: string; alarm_triggered_at: number }; + }; + + if (!alert?.id || !status?.subscription_id) return true; + + const isAlarm = status.alarm_status === "in_alarm"; + + // Store in Redis with TTL so customer dashboards show banners instantly + const key = `pointer:alarm:${status.subscription_id}:${alert.id}`; + if (isAlarm) { + await redis.set(key, JSON.stringify({ + alertId: alert.id, + subscriptionId: status.subscription_id, + alarmStatus: "in_alarm", + meteredFeatureId: alert.metered_feature_id, + triggeredAt: status.alarm_triggered_at, + }), "EX", 25 * 60 * 60); + + await sendCustomerNotification(status.subscription_id, alert.name); + } else { + await redis.del(key); + } + + return true; +} +``` + +✅ Do: Store active alarm states in a local Redis cache so application dashboards display warnings without calling Chargebee. + +✅ Do: Use global alerts with plan filters to maintain different thresholds (such as 80% on Starter vs 95% on Enterprise) without creating separate rules per customer. + +⚠️ Don't: Poll Chargebee's alert status endpoints on user request paths. Use incoming webhooks to keep local state up to date. + +⚠️ Don't: Block user requests when an alert fires unless you deliberately configured that threshold as a hard service cutoff. Most alerts serve as advance warnings before overage fees accumulate. + +## 2. Metered vs Non-Metered Features + +Chargebee usage alerts evaluate only metered features backed by ingested usage events. Non-metered features (such as daily token limits, minute-level API request limits, or prepaid seat counts) do not send event streams to Chargebee. Aggregation and alerting for non-metered features have to be done in the app. + +| Capability | Metered Features | Non-Metered Features | +|:---|:---|:---| +| Ingestion path | Batched to Chargebee Usage Events API | Tracked locally in Redis or Postgres | +| Aggregation authority | Chargebee Billing engine | Application local counters | +| Alert trigger | Chargebee `alert_status_changed` webhook | Application event bus or notification queue | +| Evaluation latency | Near real time (seconds after batch ingestion) | Sub-millisecond on the request path | +| Common use case | Monthly usage overages, pooled billing | Daily rate limits, burst limits, seat caps | + +```mermaid +sequenceDiagram + participant Client + participant App + participant Redis as Local Counter + participant Queue as Notification Queue + + Client->>App: API request (tokens / calls) + App->>Redis: Atomic INCRBY (current_usage) + alt Usage >= 80% of quota and not alerted + App->>Redis: SETNX alert_lock (window TTL) + App->>Queue: Enqueue 80% warning notification + end + App-->>Client: 200 OK (response) +``` + +### Flow for Non-Metered Alerting + +1. The client makes a request consuming a non-metered entitlement (such as 500 LLM output tokens). +2. The application increments an atomic counter in Redis tied to the customer's reset window (such as `quota:sub_123:tokens:2026-09-29`). +3. If the counter crosses the alert threshold (such as 80% of the daily allowance), the app claims a distributed lock key using `SETNX` with a TTL matching the window. +4. If the lock was acquired, the app dispatches an alert notification to the queue. The lock prevents repeated notifications on subsequent requests in the same window. +5. The request completes without waiting for external billing APIs. + +```typescript +// Enforce and alert on non-metered daily quotas in local Redis +const windowKey = `quota:${subscriptionId}:tokens:${utcDayKey}`; +const alertLockKey = `alert:lock:${subscriptionId}:tokens:${utcDayKey}:80pct`; + +const currentUsage = await redis.incrby(windowKey, tokenDelta); + +if (currentUsage >= dailyAllowance * 0.8) { + // SETNX allows only the first request crossing the threshold to fire an alert + const acquired = await redis.set(alertLockKey, "1", "EX", 86400, "NX"); + if (acquired) { + await notificationQueue.send({ + subscriptionId, + feature: "tokens", + threshold: "80%", + usage: currentUsage, + }); + } +} +``` + +✅ Do: Use atomic Redis operations and idempotency locks (`SET ... NX`) so local threshold alerts fire exactly once per window. + +✅ Do: Handle non-metered quotas in the application so rate checks complete in sub-millisecond time. + +⚠️ Don't: Attempt to send usage events to Chargebee for sub-minute or daily rate-limit enforcement. Chargebee aggregates billing data for periodic invoicing. Real-time request throttling belongs in your application. + +## 3. When Are Usage Limits Reset + +Understanding reset timing prevents duplicate alerts and false overage warnings across billing boundaries. + +```mermaid +sequenceDiagram + box rgba(0,0,0,0.1) External + participant Chargebee + end + participant Webhook as Webhook Worker + participant Cache as Redis + participant App + + Note over Chargebee: Term rollover (e.g. 1st of month) + Chargebee->>Chargebee: Reset metered usage counters to zero + Chargebee->>Chargebee: Transition alert: in_alarm -> within_limit + Chargebee->>Webhook: POST alert_status_changed (within_limit) + Webhook->>Cache: Remove pointer:alarm:sub_id:alert_id + Webhook->>App: Emit alert_resolved event + App->>App: Dismiss user-facing warning banners +``` + +### Reset Scenarios + +| Reset Type | Trigger | Chargebee Action | Application Action | +|:---|:---|:---|:---| +| Billing cycle renewal | Subscription renewal date | Metered counters reset to zero; alert status switches to `within_limit` | Webhook worker removes local Redis alarm key; clears UI banners | +| Fixed window (non-metered) | UTC day or minute rollover | None (Chargebee does not track daily non-metered limits) | Redis key TTL expires; local alert lock key resets automatically | +| Mid-term reset (`force_term_reset`) | API subscription update | Terminates term, settles overages, starts new term at zero usage | Re-fetch entitlements, clear Redis alarms, reset local usage counters | + +### Chargebee Billing Cycle Reset + +When a subscription renews into a new billing cycle: + +1. Chargebee resets the subscription's metered usage counters to zero. +2. Any active alerts transition from `in_alarm` back to `within_limit`. +3. Chargebee sends an `alert_status_changed` webhook with `alarm_status: "within_limit"`. +4. Your application removes the local alarm record from Redis, clearing warning banners from the customer portal. + +```typescript +// Clear local alarm when Chargebee confirms status returned to within_limit +if (status.alarm_status === "within_limit") { + await redis.del(`pointer:alarm:${status.subscription_id}:${alert.id}`); + await emit("chargebee.alert_resolved", { + alertId: alert.id, + subscriptionId: status.subscription_id, + }); +} +``` + +### Non-Metered Window Reset + +Non-metered features do not reset on Chargebee's renewal dates unless configured for a monthly term. Daily or hourly limits should use UTC timestamp keys with a set TTL (such as 86,400 seconds for daily keys). When the TTL expires, both the counter and the alert lock disappear without requiring manual cleanup jobs. + +### Timezone Differences + +Chargebee billing cycles run in the time zone configured in your Chargebee site settings. If your application tracks non-metered quotas in UTC, the resets will occur at different times of day. State the reset time zone clearly on usage dashboards so users know when their allowances renew. + +✅ Do: Listen for `alert_status_changed` webhooks with `alarm_status: "within_limit"` to dismiss UI warnings automatically when a new cycle begins. + +✅ Do: Use TTLs on local quota keys so non-metered allowances and threshold locks expire cleanly without cron scripts. + +⚠️ Don't: Assume Chargebee resets non-metered features. If a plan includes 10,000 API calls per day, your application must track and reset that quota. + +## 4. Mid Term Subscription Change + +When a customer upgrades, increases quantity, or purchases a top-up mid-cycle, quotas and alert thresholds change before the billing term ends. The [Chargebee mid-term subscription changes guide](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/mid-term-subscription-changes-ubb) specifies how each change type calculates entitlements and overages. + +```mermaid +sequenceDiagram + participant Customer + participant App + box rgba(0,0,0,0.1) External + participant Chargebee + end + participant Webhook as Webhook Worker + participant Cache as Redis + + Customer->>App: Upgrade plan (mid-term) + App->>Chargebee: Update subscription + Chargebee->>Chargebee: Grant full new quota for remaining term + Chargebee->>Chargebee: Re-evaluate alerts against new quota + opt Alert resolves + Chargebee->>Webhook: POST alert_status_changed (within_limit) + Webhook->>Cache: Clear local alarm + end + App->>Cache: Invalidate cached entitlements + App-->>Customer: Upgrade applied immediately +``` + +### Subscription Change Scenarios + +| Scenario | Entitlement Timing | Invoicing Impact | Usage Alert Impact | +|:---|:---|:---|:---| +| Plan upgrade | New plan quota granted in full for remaining term; not prorated by time | Prorated credit for unused old plan; prorated charge for new plan | Total usage may fall below the new quota threshold; Chargebee switches alert from `in_alarm` to `within_limit` | +| Prepaid quantity increase | Quota scales linearly and applies retroactively from cycle start | Prorated charge for additional units | Prior usage is measured against the higher total; active alarms may clear | +| Entitlement override | Takes effect immediately; valid retroactively from cycle start | None (no invoice or credit note generated) | Recalculates overages and alert limits against overridden quota | +| Top-up addon | Quota granted for addon billing period (from attachment date forward) | Prorated charge for addon | Covers usage after attachment date; prior usage remains overage unless backdated | +| Immediate overage settlement (`invoice_usages`) | Current term closes; new term starts at zero usage | Unbilled overages invoiced immediately | Closes active alarms for prior term; starts new term `within_limit` | + +### Plan Upgrades + +When upgrading from a smaller plan to a larger plan mid-cycle: + +- The old plan's unused charge receives a prorated credit note. +- The new plan's included usage quota is granted in full for the remainder of the period. Entitlements are not time-prorated. +- Events with timestamps before the upgrade date map to the old plan's entitlement grant. Events after the upgrade date map to the new grant. +- If a customer was `in_alarm` under the old plan, the higher quota often brings usage back below the threshold percentage. Chargebee detects this on the next incoming usage event and emits an `alert_status_changed` webhook with `within_limit`. + +### Prepaid Quantity Increases + +When increasing the quantity of a prepaid plan item (such as doubling a 100,000-unit tier): + +- The new entitlement applies retroactively from the beginning of the billing term. +- Usage that previously counted as an overage is now absorbed by the expanded quota. +- If an alert was triggered at 80% of 100,000 units (80,000 units), increasing quantity to 2 raises the quota to 200,000. That 80,000 units now represents 40%, clearing the alarm. + +### Entitlement Overrides + +Direct entitlement overrides modify included usage without altering plans or attaching addons. Because overrides apply retroactively from the start of the billing period, overage math and alert thresholds adjust immediately without generating invoices or credit notes. + +### Top-Up Addons + +Attaching a non-metered top-up addon adds extra included units, but the entitlement grant period only covers usage from the addon attachment date forward. Usage recorded before that date remains billable overage unless the addon attachment is backdated to the start of the cycle. + +### Immediate Overage Invoicing + +If you want to invoice accumulated overages at the time of a mid-term change instead of waiting for term end, call the subscription update API with: + +```json +{ + "invoice_usages": true, + "force_term_reset": true +} +``` + +This closes the current billing term, creates an invoice for unbilled overages, and starts a new billing term at zero usage. Any active alerts for that subscription reset to `within_limit`. + +```typescript +// Refresh entitlements and evict alarms on checkout return +export async function handleSubscriptionUpgrade(subscriptionId: string) { + // Evict cached entitlements so the next check fetches the expanded quota + await redis.del(`entitlements:${subscriptionId}`); + + // Fetch updated entitlements from Chargebee + const response = await chargebee.subscriptionEntitlement + .subscriptionEntitlementsForSubscription(subscriptionId, { limit: 100 }); + + // Update local database snapshot + await db.upsertEntitlements(subscriptionId, response.list); + + // If new quota exceeds current consumption, remove active local alarm + const alarms = await getLocalAlarms(subscriptionId); + for (const alarm of alarms) { + if (alarm.alarm_status === "in_alarm") { + await redis.del(`pointer:alarm:${subscriptionId}:${alarm.alert_id}`); + } + } +} +``` + +✅ Do: Invalidate local entitlement caches and quota counters immediately when a customer upgrades so they receive expanded capacity right away. + +✅ Do: Use backdating when attaching top-up addons if you want the extra quota to cover usage that occurred earlier in the current billing cycle. + +⚠️ Don't: Rely on mid-term plan changes to clear past overages unless you backdate the change or choose an operation that applies retroactively (such as quantity increases or entitlement overrides). + +⚠️ Don't: Set `invoice_usages: true` without `force_term_reset: true`. Chargebee requires both parameters together to settle overages mid-term. + +## See It Running In The Demo App + +Implementation files to review: + +- [`pointer/lib/alerts/sync.ts`](../pointer/lib/alerts/sync.ts): Webhook processor for `alert_status_changed`. Updates local Redis alarm keys and emits domain events for the admin flow. + +- [`pointer/lib/alerts/provider.ts`](../pointer/lib/alerts/provider.ts): Queries active alarms from Chargebee and reconciles them with local Redis alarm keys (`pointer:alarm::`). + +- [`pointer/lib/usage/events.ts`](../pointer/lib/usage/events.ts): Non-blocking usage recording buffer feeding events to Chargebee. + +- [`pointer/lib/entitlements/gate.ts`](../pointer/lib/entitlements/gate.ts): Enforces local non-metered limits and quota boundaries in real time. + +## Go-Live Checklist + +- [ ] Are metered features configured in Chargebee Product Catalog 2.0 with linked usage alerts? + +- [ ] Does your webhook endpoint accept `alert_status_changed` events and return HTTP 200 quickly? + +- [ ] Does the webhook worker save active alarm states to a fast local store (such as Redis) for zero-latency dashboard rendering? + +- [ ] Does the application handle the `within_limit` status transition to dismiss warning banners when usage resets? + +- [ ] Are non-metered quotas (daily token limits, minute-level API limits) aggregated and alerted locally in the application? + +- [ ] Do local non-metered alert checks use atomic locks (`SETNX`) so customers receive exactly one notification per window? + +- [ ] Does the subscription change workflow invalidate local entitlement and alarm caches immediately after checkout? + +- [ ] Are top-up addons backdated to the cycle start date when intended to absorb prior unbilled overages? diff --git a/how-to/usage-based-billing.md b/how-to/usage-based-billing.md index 9666d51..d9f2e89 100644 --- a/how-to/usage-based-billing.md +++ b/how-to/usage-based-billing.md @@ -13,6 +13,7 @@ Chargebee is the system of record for billing aggregation and invoices, but your ## Setup +- Usage-based billing [configured](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/understanding-usages#test-site) - Product Catalog 2.0 with [metered features](https://www.chargebee.com/docs/billing/2.0/usage-based-billing/defining-metered-features) and linked pricing configured - A fast local buffer (such as a Redis Stream or durable message queue) on the request path - A local event store (such as PostgreSQL) for audit trails and billing reconciliation From 69004f587b16b5b9861352bd502fa75aba7d2911 Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Tue, 29 Sep 2026 14:35:53 +1000 Subject: [PATCH 10/12] fix snyk warning --- pointer/docs/flow_visualization.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pointer/docs/flow_visualization.html b/pointer/docs/flow_visualization.html index ee87eb3..631436a 100644 --- a/pointer/docs/flow_visualization.html +++ b/pointer/docs/flow_visualization.html @@ -873,7 +873,7 @@

    Pointer Architecture

    payload: `{ "customer": { - "id": "cust_KPjZ3wLm8NqRf", + "id": "cust_abc123", "email": "user@example.com", "first_name": "John", "last_name": "Doe", @@ -889,7 +889,7 @@

    Pointer Architecture

    desc:"Plugin updates user row with chargebeeCustomerId.", payload: `UPDATE user -SET chargebeeCustomerId = 'cust_KPjZ3wLm8NqRf' +SET chargebeeCustomerId = 'cust_abc123' WHERE id = 'usr_abc123';`, chips:["~15ms"] }, @@ -901,7 +901,7 @@

    Pointer Architecture

    payload: `POST /api/v2/subscriptions { - "customer_id": "cust_KPjZ3wLm8NqRf", + "customer_id": "cust_abc123", "plan_id": "free-plan", "billing_cycles": 0 }`, @@ -916,7 +916,7 @@

    Pointer Architecture

    `{ "subscription": { "id": "sub_xyz123", - "customer_id": "cust_KPjZ3wLm8NqRf", + "customer_id": "cust_abc123", "plan_id": "free-plan", "status": "active" } @@ -961,7 +961,7 @@

    Pointer Architecture

    "content": { "subscription": { "id": "sub_xyz123", - "customer_id": "cust_KPjZ3wLm8NqRf", + "customer_id": "cust_abc123", "plan_id": "free-plan", "status": "active", "resource_version": 1725428450 @@ -1046,7 +1046,7 @@

    Pointer Architecture

    `BEGIN; INSERT INTO subscription (id, customerId, planId, status, resourceVersion) -VALUES ('sub_xyz123', 'cust_KPjZ3wLm8NqRf', 'free-plan', 'active', 1725428450) +VALUES ('sub_xyz123', 'cust_abc123', 'free-plan', 'active', 1725428450) ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, resourceVersion = EXCLUDED.resourceVersion; From 6541ebde3aac89d000803d7abe68402dcbaa026d Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Tue, 29 Sep 2026 18:26:49 +1000 Subject: [PATCH 11/12] checkout experience --- how-to/checkout-experience.md | 387 +++++++++++++++++++++++++++++++++- 1 file changed, 386 insertions(+), 1 deletion(-) diff --git a/how-to/checkout-experience.md b/how-to/checkout-experience.md index 9b2a574..f297699 100644 --- a/how-to/checkout-experience.md +++ b/how-to/checkout-experience.md @@ -1,2 +1,387 @@ -# Checkout Experience +# How To Build A Checkout Experience +Integrating checkout requires balancing customer convenience with billing security. The frontend must guide users through plan selection and payment smoothly, while your server retains strict control over pricing, plan eligibility, and order fulfillment. + +This guide covers how to: + +* Choose between Hosted Checkout, Chargebee.js Payment Components, and native app store flows +* Launch new subscriptions and plan upgrades using Product Catalog 2.0 +* Build a custom checkout form without exposing card data to your application +* Record native Apple and Google in-app purchases in Chargebee Omnichannel Subscriptions +* Grant access safely after checkout and recover from dropped connections or browser refreshes + +Chargebee is the system of record for subscriptions, customer balances, and invoices. Your application owns authentication, plan eligibility, and the local billing mirror. + +## Setup + +- Product Catalog 2.0 with items, item prices, currencies, and tax rules configured +- At least one payment gateway and required payment methods active in Chargebee +- Checkout branding, fields, and return URLs published under **Settings > Configure Chargebee > Checkout & Self-Serve Portal** +- Publishable API key when using Chargebee.js Payment Components +- HTTPS webhook endpoint and background queue worker (see [webhook integration](./webhooks.md)) +- For mobile app purchases, an Apple or Google app connected to [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview) + +## 1. Choose The Checkout Experience + +Chargebee supports three main checkout models depending on your platform and user interface needs: + +| Model | Best for | UI owner | PCI compliance | +|:---|:---|:---|:---| +| Hosted Checkout | Standard web apps, fast setup, maximum payment method support | Chargebee (modal or full-page) | Handled by Chargebee (SAQ A) | +| Payment Components | Web apps requiring an embedded, fully custom design | Your application, with Chargebee iframes | Handled by Chargebee iframes (SAQ A) | +| Native In-App Purchases | iOS and Android apps selling digital products | Native store UI (StoreKit / Play Billing) | Handled by Apple / Google | + +```mermaid +flowchart TD + A[Where does checkout run?] -->|Web application| B{Need complete control
    over form markup?} + B -->|No| C[Chargebee Hosted Checkout] + B -->|Yes| D[Chargebee.js Payment Components] + A -->|Native mobile app| E{Selling digital goods?} + E -->|Yes| F[StoreKit / Play Billing
    + Omnichannel Subscriptions] + E -->|No| G[Hosted Checkout in mobile browser] +``` + +Most web applications should use [Hosted Checkout](https://www.chargebee.com/docs/billing/2.0/hosted-capabilities/hosted-checkout). It manages order previews, tax calculation, 3D Secure challenges, mobile wallets, and regional direct debits without custom billing UI. + +Use [Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) when the hosted layouts cannot fit your design system. Your backend must then coordinate pricing estimates, Payment Intents, and asynchronous order creation. + +For native mobile apps selling digital features, store policies require using Apple In-App Purchase or Google Play Billing. Complete the transaction in the mobile app, then record the receipt in Chargebee. + +✅ Do: Validate customer identity, plan eligibility, and discounts on your backend before initiating checkout. + +⚠️ Don't: Collect or transmit raw card details through your server. Doing so triggers full PCI DSS (SAQ D) compliance audits. + +## 2. Implement Hosted Checkout + +Hosted Checkout provides a prebuilt payment flow that collects account, address, and payment information, authorizes the charge, and updates the subscription. + +### Choose A Layout + +Chargebee provides two hosted layouts: + +* **In-App layout:** Opens as an overlay modal inside your web page. It supports all Chargebee payment methods and maintains your application context. Chargebee.js creates and manages the modal iframe automatically. +* **Full-Page layout:** Opens as a standalone page on your Chargebee subdomain or custom domain. It supports embedded checkout in a container, product images, and rich HTML descriptions, with a curated set of payment methods. + +Do not wrap an in-app checkout URL inside your own iframe tag. The modal is already an iframe, and nesting it breaks mobile viewports and payment redirects. If you want embedded checkout inside your page layout, use the full-page layout mounted via Chargebee.js. + +Redirect-based payment methods (including PayPal, GoCardless, and Plaid) require `embed: false` because bank authorization pages cannot run inside an embedded iframe. Check the [layout comparison](https://www.chargebee.com/docs/billing/2.0/hosted-capabilities/in-app-vs-full-page-checkout) before enabling payment options. + +```mermaid +sequenceDiagram + participant Customer + participant Browser + participant App + box rgba(0,0,0,0.1) External + participant Chargebee + end + + Customer->>Browser: Select plan + Browser->>App: POST /api/checkout + App->>App: Verify customer & plan + App->>Chargebee: checkoutNewForItems + Chargebee-->>App: Hosted page object + App-->>Browser: Return hosted page + Browser->>Chargebee: Chargebee.openCheckout() + Customer->>Chargebee: Submit payment & 3DS + Chargebee-->>Browser: Success callback (hostedPageId) + Browser->>App: POST /api/checkout/verify + App->>Chargebee: Retrieve hosted page + Chargebee-->>App: Return subscription & invoice + App-->>Browser: Redirect to dashboard +``` + +### Flow + +1. The customer selects a plan on your pricing page. +2. The client requests a checkout session from your backend. +3. The backend resolves the customer ID and calls `chargebee.hostedPage.checkoutNewForItems` (or `checkoutExistingForItems` for upgrades). +4. The backend returns the hosted page object to the browser. +5. The browser opens the modal using `Chargebee.openCheckout`. +6. After payment, the client sends the returned `hosted_page.id` to the backend for server verification. + +```typescript +// Server: create hosted page for an authenticated user +const { hosted_page: hostedPage } = + await chargebee.hostedPage.checkoutNewForItems( + { + layout: "in_app", + customer: { + id: chargebeeCustomerId, + email: session.user.email, + }, + subscription_items: [ + { item_price_id: allowedItemPriceId, quantity: 1 }, + ], + pass_thru_content: JSON.stringify({ checkoutAttemptId }), + }, + { "chargebee-idempotency-key": checkoutAttemptId }, + ); + +return Response.json(hostedPage); +``` + +For upgrades on existing subscriptions, call `checkoutExistingForItems`. Pass `replace_items_list: true` when switching plans, because the default behavior appends new items to the subscription. + +```typescript +// Browser: launch hosted checkout with Chargebee.js +const chargebee = Chargebee.init({ site: process.env.NEXT_PUBLIC_CHARGEBEE_SITE }); + +chargebee.openCheckout({ + hostedPage: async () => { + const res = await fetch("/api/checkout", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ planId: "pro-monthly" }), + }); + if (!res.ok) throw new Error("Checkout creation failed"); + return res.json(); + }, + success: async (hostedPageId) => { + await fetch("/api/checkout/verify", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ hostedPageId }), + }); + window.location.assign("/dashboard"); + }, +}); +``` + +Always trigger `openCheckout` directly from a user gesture (such as clicking a button). Mobile Safari and Chrome can block popups that launch asynchronously without a direct user interaction. + +✅ Do: Pass a unique, stable idempotency key in the `chargebee-idempotency-key` header when creating hosted pages. + +⚠️ Don't: Rely on client return parameters alone to unlock accounts. Always verify the hosted page status on your backend. + +## 3. Build A Custom Checkout With Payment Components + +[Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) render secure payment fields inside an iframe hosted on Chargebee's domain. Your application owns the surrounding checkout UI, while card details bypass your servers completely. + +Payment Components replace the deprecated Card Components and Payment Method Helpers. New custom checkouts should use Payment Components. Verify that your payment gateway supports Payment Intents for the methods you plan to offer. + +```mermaid +sequenceDiagram + participant Browser + participant App as Backend + box rgba(0,0,0,0.1) External + participant Chargebee + end + + Browser->>App: Request checkout intent + App->>Chargebee: Create subscription estimate + Chargebee-->>App: Return total & tax + App->>Chargebee: Create Payment Intent + Chargebee-->>App: Return payment_intent (inited) + App-->>Browser: Return client token & intent ID + Browser->>Chargebee: Mount Payment Component + Browser->>Chargebee: confirm() (handles 3DS) + Chargebee-->>Browser: onSuccess callback + Browser->>App: Poll order status + Chargebee->>App: Webhook: payment_intent_updated (authorized) + App->>Chargebee: subscription.createWithItems() + Chargebee-->>App: Subscription active +``` + +### Flow + +1. The customer reviews their cart and enters their billing address. +2. Your backend calls Chargebee's estimate API to compute taxes, discounts, and the final total. +3. Your backend creates a `payment_intent` for that amount and stores the checkout attempt in your database. +4. The browser loads Chargebee.js with your publishable key, creates the payment component with the Payment Intent ID, and mounts it into a DOM container. +5. The customer enters their details and submits the form. +6. The frontend calls `paymentComponent.validate()`, followed by `paymentComponent.confirm()`. The component manages card tokenization and 3D Secure challenges inside its iframe. +7. When authorization succeeds, the component fires `onSuccess`. The UI switches to a processing state. +8. Chargebee delivers a `payment_intent_updated` webhook with status `authorized`. Your backend worker verifies the intent and calls `subscription.createWithItems` to activate the subscription. + +```typescript +// Server: estimate total and create Payment Intent +const { estimate } = + await chargebee.estimate.createSubItemForCustomerEstimate(customerId, { + subscription_items: [{ item_price_id: itemPriceId, quantity: 1 }], + billing_address: address, + }); + +const invoice = estimate.invoice_estimate; +if (!invoice || invoice.amount_due === undefined) { + throw new Error("No immediate invoice generated for checkout"); +} + +const { payment_intent: intent } = await chargebee.paymentIntent.create( + { + customer_id: customerId, + amount: invoice.amount_due, + currency_code: invoice.currency_code, + defer_payment_method_type: true, + }, + { "chargebee-idempotency-key": `${attemptId}:intent` }, +); +``` + +```typescript +// Browser: mount and confirm payment +const chargebee = Chargebee.init({ + site: process.env.NEXT_PUBLIC_CHARGEBEE_SITE, + publishableKey: process.env.NEXT_PUBLIC_CHARGEBEE_PK, +}); + +const components = chargebee.components({ locale: "en" }); +const payment = components.create("payment", { + paymentIntent: { id: paymentIntentId }, +}, { + onSuccess: () => showProcessingScreen(), + onError: (err) => showErrorMessage(err.message), +}); + +await payment.mount("#payment-container"); + +async function handlePayClick() { + const isValid = await payment.validate(); + if (!isValid) return; + await payment.confirm(); +} +``` + +```typescript +// Webhook Worker: fulfill subscription on payment authorization +if (event.event_type !== "payment_intent_updated") return; + +const intent = event.content.payment_intent; +if (intent.status !== "authorized") return; + +const attempt = await db.checkoutAttempts.findByIntentId(intent.id); +if (!attempt || attempt.status === "completed") return; + +const result = await chargebee.subscription.createWithItems( + attempt.customerId, + { + subscription_items: attempt.items, + payment_intent: { id: intent.id }, + }, + { "chargebee-idempotency-key": `${attempt.id}:subscription` }, +); + +await db.checkoutAttempts.markCompleted(attempt.id, result.subscription.id); +``` + +Use separate idempotency keys for creating the Payment Intent and creating the subscription. Reusing the same key across different endpoints causes Chargebee to reject the second call with an error. + +✅ Do: Compute prices and taxes through the Estimate API before creating a Payment Intent. + +⚠️ Don't: Call `subscription.createWithItems` from the browser's `onSuccess` callback. If the user closes the tab before the API call finishes, the payment is authorized but the subscription is never created. + +## 4. Record Native In-App Purchases + +Web modals are not allowed for digital goods inside iOS or Android apps. App Store and Google Play policies require native in-app purchases. Chargebee handles these through [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview). + +The app store manages billing renewals, cancellations, and payment retries. Chargebee maintains a mirrored subscription record so your backend can evaluate entitlements consistently across web and mobile users. + +```mermaid +sequenceDiagram + participant App as Mobile App + participant Store as App Store / Google Play + participant Server as Backend + box rgba(0,0,0,0.1) External + participant Chargebee + end + + App->>Store: In-app purchase (StoreKit 2 / Billing Library) + Store-->>App: Purchase receipt / token + App->>Server: POST /api/mobile/purchase + Server->>Chargebee: recordedPurchase.create() + Chargebee-->>Server: recorded_purchase (in_process) + Chargebee->>Store: Verify receipt asynchronously + Chargebee-->>Server: Webhook: omnichannel_subscription_created + Server->>Server: Update local user access +``` + +### Flow + +1. The customer completes a purchase using StoreKit 2 or Google Play Billing. +2. The mobile app sends the transaction ID (Apple) or order ID and purchase token (Google) to your backend. +3. Your backend verifies the user's session and links the purchase to their Chargebee customer ID. +4. Your backend calls `chargebee.recordedPurchase.create`. +5. Chargebee returns a `recorded_purchase` object with `status: "in_process"`. +6. Chargebee validates the purchase directly with Apple or Google and delivers an `omnichannel_subscription_created` webhook. +7. Your webhook processor updates the local database mirror and unlocks the purchased features. + +```typescript +// Server: record native mobile purchase +const { recorded_purchase: purchase } = await chargebee.recordedPurchase.create( + { + app_id: process.env.CHARGEBEE_APPLE_APP_ID, + customer: { id: customerId }, + apple_app_store: { transaction_id: appleTransactionId }, + }, + { "chargebee-idempotency-key": purchaseAttemptId }, +); + +return Response.json({ status: purchase.status, id: purchase.id }); +``` + +For Google Play, pass `google_play_store.order_id` (format: `GPA.xxxx-xxxx-xxxx-xxxxx`) and `google_play_store.purchase_token`. + +Configure Apple App Store Server Notifications and Google Cloud Pub/Sub in Chargebee. When a user cancels, renews, or pauses in their app store settings, Chargebee updates the omnichannel subscription automatically. + +Chargebee's older mobile SDKs and wrappers belong to Mobile Subscriptions (Legacy) and no longer receive feature updates. New native applications should use StoreKit 2 and Google Play Billing directly, with receipts recorded via the Omnichannel API. + +✅ Do: Configure store server-to-server notifications so renewals, refunds, and cancellations sync to Chargebee. + +⚠️ Don't: Create regular Chargebee Billing subscriptions for mobile purchases. Omnichannel subscriptions manage their own lifecycle and do not produce Chargebee invoices. + +## 5. Verify Purchases And Recover From Failures + +Network timeouts, closed tabs, and expired sessions happen during checkout. A dependable integration treats the client callback as an optimistic hint, using server retrieval and webhooks to guarantee fulfillment. + +### Verify Completed Sessions + +For Hosted Checkout, pass the returned `hosted_page.id` to your backend and call `chargebee.hostedPage.retrieve`: + +1. Confirm `hosted_page.state === "succeeded"`. +2. Confirm the customer ID matches the authenticated user session. +3. Confirm the subscription items match the user's selected plan. +4. Mark the local checkout attempt completed to prevent replay attacks. + +After verification, update your local mirror and bust the user's cached entitlements immediately. This gives the customer instant access without waiting for background webhook delivery. + +### Recovery Scenarios + +| Scenario | System behavior | Recovery action | +|:---|:---|:---| +| Customer closes modal before paying | Hosted page remains in `created` state | Leave attempt open; generate a fresh hosted page if they resume later | +| Hosted page session expires (3-hour TTL) | Chargebee rejects checkout with expired error | Prompt the user to restart checkout and generate a new page | +| Customer clicks Pay multiple times | Frontend sends duplicate requests | Return the existing intent or hosted page using a deterministic idempotency key | +| User closes browser during 3DS redirect | Payment completes, but redirect handler never runs | Background `subscription_created` or `payment_intent_updated` webhook fulfills the order | +| Webhook arrives before client redirect | Worker updates local mirror first | Client verification sees the updated record and redirects cleanly | +| Receipt recording returns `status: "ignored"` | Store transaction was already processed | Confirm active access for the user without throwing an error | + +✅ Do: Update local entitlements immediately upon verified checkout return for instant access. + +⚠️ Don't: Trust URL query strings like `?state=succeeded` without calling `hostedPage.retrieve` on your backend. + +## See It Running In The Demo App + +Pointer implements the Hosted Checkout model using `@chargebee/better-auth`. It provisions a free subscription at sign-up, opens an upgrade checkout when switching tiers, and refreshes entitlements immediately on return: + +- [`pointer/app/choose-plan/_components/plan-picker.tsx`](../pointer/app/choose-plan/_components/plan-picker.tsx): Renders self-service plans and invokes `authClient.subscription.create` or `update`. +- [`pointer/app/_components/account-provisioning.tsx`](../pointer/app/_components/account-provisioning.tsx): Waits for the initial customer record before opening an upgrade checkout. +- [`pointer/lib/checkout-urls.ts`](../pointer/lib/checkout-urls.ts): Generates absolute success and cancel return URLs for hosted pages. +- [`pointer/app/api/entitlements/checkout-complete/route.ts`](../pointer/app/api/entitlements/checkout-complete/route.ts): Handles the checkout return, verifies the authenticated user, and triggers an optimistic entitlement refresh. +- [`pointer/plugins/chargebee-plugin.ts`](../pointer/plugins/chargebee-plugin.ts): Configures plan limits, organization billing permissions, and the webhook event bus. +- [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts): Asynchronously updates the PostgreSQL subscription mirror and version records. + +## Go-Live Checklist + +- [ ] Is Product Catalog 2.0 active with plan item prices, addon prices, and currencies configured? +- [ ] Does your checkout model match your platform (Hosted Checkout, Payment Components, or Omnichannel)? +- [ ] Are Chargebee secret API keys restricted to backend environments, with only the publishable key exposed in browser bundles? +- [ ] Is Chargebee.js loaded directly from `https://js.chargebee.com/v2/chargebee.js`? +- [ ] Are hosted pages created via the API on your backend rather than unauthenticated client buttons? +- [ ] Are redirect URLs for PayPal, GoCardless, and Plaid tested with `embed: false`? +- [ ] Do backend mutation calls include a unique, deterministic `chargebee-idempotency-key` header? +- [ ] Does the custom checkout path compute estimates before generating Payment Intents? +- [ ] Is subscription fulfillment in custom checkouts triggered by the authorized webhook rather than the browser callback? +- [ ] Does your redirect return handler verify `hostedPage.retrieve` before granting access? +- [ ] Does the return handler refresh local entitlements immediately so users see upgraded features without delay? +- [ ] Is an asynchronous webhook worker running to fulfill purchases when users close the tab? +- [ ] For native mobile purchases, are Apple and Google store notifications connected to Omnichannel Subscriptions? +- [ ] Has your team confirmed PCI compliance obligations (SAQ A for Hosted Pages and Payment Components)? From 36281cfd8437552bf71283486c94d8ee56d66842 Mon Sep 17 00:00:00 2001 From: Srinath Sankar Date: Tue, 29 Sep 2026 18:50:27 +1000 Subject: [PATCH 12/12] updates to checkout experience --- how-to/checkout-experience.md | 99 +++++++++++++++++------------------ 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/how-to/checkout-experience.md b/how-to/checkout-experience.md index f297699..0e981da 100644 --- a/how-to/checkout-experience.md +++ b/how-to/checkout-experience.md @@ -1,6 +1,6 @@ # How To Build A Checkout Experience -Integrating checkout requires balancing customer convenience with billing security. The frontend must guide users through plan selection and payment smoothly, while your server retains strict control over pricing, plan eligibility, and order fulfillment. +Integrating checkout requires balancing customer convenience with billing security. The frontend guides users through plan selection and payment, while your server retains control over pricing, plan eligibility, and order fulfillment. This guide covers how to: @@ -14,20 +14,20 @@ Chargebee is the system of record for subscriptions, customer balances, and invo ## Setup -- Product Catalog 2.0 with items, item prices, currencies, and tax rules configured -- At least one payment gateway and required payment methods active in Chargebee -- Checkout branding, fields, and return URLs published under **Settings > Configure Chargebee > Checkout & Self-Serve Portal** -- Publishable API key when using Chargebee.js Payment Components -- HTTPS webhook endpoint and background queue worker (see [webhook integration](./webhooks.md)) -- For mobile app purchases, an Apple or Google app connected to [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview) +* Product Catalog 2.0 with items, item prices, currencies, and tax rules configured +* At least one payment gateway and required payment methods active in Chargebee +* Checkout branding, fields, and return URLs published under **Settings > Configure Chargebee > Checkout & Self-Serve Portal** +* Publishable API key when using Chargebee.js Payment Components +* HTTPS webhook endpoint and background queue worker (see [webhook integration](./webhooks.md)) +* For mobile app purchases, an Apple or Google app connected to [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview) ## 1. Choose The Checkout Experience -Chargebee supports three main checkout models depending on your platform and user interface needs: +Chargebee supports three checkout models depending on your platform and user interface needs: | Model | Best for | UI owner | PCI compliance | |:---|:---|:---|:---| -| Hosted Checkout | Standard web apps, fast setup, maximum payment method support | Chargebee (modal or full-page) | Handled by Chargebee (SAQ A) | +| Hosted Checkout | Standard web apps, fast setup, broad payment method support | Chargebee (modal or full-page) | Handled by Chargebee (SAQ A) | | Payment Components | Web apps requiring an embedded, fully custom design | Your application, with Chargebee iframes | Handled by Chargebee iframes (SAQ A) | | Native In-App Purchases | iOS and Android apps selling digital products | Native store UI (StoreKit / Play Billing) | Handled by Apple / Google | @@ -41,11 +41,11 @@ flowchart TD E -->|No| G[Hosted Checkout in mobile browser] ``` -Most web applications should use [Hosted Checkout](https://www.chargebee.com/docs/billing/2.0/hosted-capabilities/hosted-checkout). It manages order previews, tax calculation, 3D Secure challenges, mobile wallets, and regional direct debits without custom billing UI. +Most web applications should use [Hosted Checkout](https://www.chargebee.com/docs/billing/2.0/hosted-capabilities/hosted-checkout). It manages order previews, tax calculation, 3D Secure challenges, mobile wallets, and regional payment methods without custom billing UI. -Use [Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) when the hosted layouts cannot fit your design system. Your backend must then coordinate pricing estimates, Payment Intents, and asynchronous order creation. +Use [Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) when hosted layouts cannot fit your design system. Your backend must then coordinate pricing estimates, Payment Intents, and asynchronous order creation. -For native mobile apps selling digital features, store policies require using Apple In-App Purchase or Google Play Billing. Complete the transaction in the mobile app, then record the receipt in Chargebee. +For native mobile apps selling digital goods, store policies require using Apple In-App Purchase or Google Play Billing. Complete the transaction in the mobile app, then record the receipt in Chargebee. ✅ Do: Validate customer identity, plan eligibility, and discounts on your backend before initiating checkout. @@ -53,18 +53,17 @@ For native mobile apps selling digital features, store policies require using Ap ## 2. Implement Hosted Checkout -Hosted Checkout provides a prebuilt payment flow that collects account, address, and payment information, authorizes the charge, and updates the subscription. +Hosted Checkout provides a prebuilt payment flow that collects customer details, authorizes the charge, and updates the subscription. -### Choose A Layout +### Layout Options And Constraints -Chargebee provides two hosted layouts: +* **In-app modal:** Opens as an overlay modal inside your web page. It supports all Chargebee payment methods and maintains application context. Chargebee.js manages the modal iframe automatically. +* **Full-page redirect:** Opens as a standalone page on your Chargebee domain. It supports embedded containers, product images, and rich HTML descriptions. -* **In-App layout:** Opens as an overlay modal inside your web page. It supports all Chargebee payment methods and maintains your application context. Chargebee.js creates and manages the modal iframe automatically. -* **Full-Page layout:** Opens as a standalone page on your Chargebee subdomain or custom domain. It supports embedded checkout in a container, product images, and rich HTML descriptions, with a curated set of payment methods. +Two operational constraints govern these layouts: -Do not wrap an in-app checkout URL inside your own iframe tag. The modal is already an iframe, and nesting it breaks mobile viewports and payment redirects. If you want embedded checkout inside your page layout, use the full-page layout mounted via Chargebee.js. - -Redirect-based payment methods (including PayPal, GoCardless, and Plaid) require `embed: false` because bank authorization pages cannot run inside an embedded iframe. Check the [layout comparison](https://www.chargebee.com/docs/billing/2.0/hosted-capabilities/in-app-vs-full-page-checkout) before enabling payment options. +1. **Nested iframe restriction:** Do not wrap an in-app checkout URL inside your own iframe tag. The modal is already an iframe, and nesting it breaks mobile viewports, sandbox permissions, and payment redirects. +2. **Redirect payment methods:** Payment methods that require top-level redirects (such as PayPal, GoCardless, and Plaid) fail inside embedded iframes. They require `embed: false` or a full-page layout. ```mermaid sequenceDiagram @@ -120,7 +119,7 @@ const { hosted_page: hostedPage } = return Response.json(hostedPage); ``` -For upgrades on existing subscriptions, call `checkoutExistingForItems`. Pass `replace_items_list: true` when switching plans, because the default behavior appends new items to the subscription. +For upgrades on existing subscriptions, call `checkoutExistingForItems`. In Product Catalog 2.0, `replace_items_list` defaults to `false`. You must pass `replace_items_list: true` when switching plans, or Chargebee appends the new item to the subscription alongside the old one. ```typescript // Browser: launch hosted checkout with Chargebee.js @@ -147,15 +146,15 @@ chargebee.openCheckout({ }); ``` -Always trigger `openCheckout` directly from a user gesture (such as clicking a button). Mobile Safari and Chrome can block popups that launch asynchronously without a direct user interaction. +To prevent mobile Safari and Chrome from blocking checkout popups, trigger `openCheckout` synchronously from a direct user tap or click. Passing a promise callback directly to `hostedPage` lets Chargebee open the modal window immediately while the network request resolves. -✅ Do: Pass a unique, stable idempotency key in the `chargebee-idempotency-key` header when creating hosted pages. +✅ Do: Pass `replace_items_list: true` on `checkoutExistingForItems` when swapping plans. -⚠️ Don't: Rely on client return parameters alone to unlock accounts. Always verify the hosted page status on your backend. +⚠️ Don't: Rely on client return query parameters alone to grant paid access. Always verify the hosted page status on your backend. ## 3. Build A Custom Checkout With Payment Components -[Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) render secure payment fields inside an iframe hosted on Chargebee's domain. Your application owns the surrounding checkout UI, while card details bypass your servers completely. +[Payment Components](https://www.chargebee.com/docs/payments/2.0/payment-components/overview) render secure payment fields inside an iframe hosted on Chargebee's domain. Your application owns the surrounding checkout layout, while card details bypass your servers. Payment Components replace the deprecated Card Components and Payment Method Helpers. New custom checkouts should use Payment Components. Verify that your payment gateway supports Payment Intents for the methods you plan to offer. @@ -184,13 +183,13 @@ sequenceDiagram ### Flow -1. The customer reviews their cart and enters their billing address. +1. The customer selects a plan and enters their billing address. 2. Your backend calls Chargebee's estimate API to compute taxes, discounts, and the final total. -3. Your backend creates a `payment_intent` for that amount and stores the checkout attempt in your database. +3. Your backend creates a `payment_intent` for that amount and stores the checkout attempt locally. 4. The browser loads Chargebee.js with your publishable key, creates the payment component with the Payment Intent ID, and mounts it into a DOM container. -5. The customer enters their details and submits the form. +5. The customer submits payment details. 6. The frontend calls `paymentComponent.validate()`, followed by `paymentComponent.confirm()`. The component manages card tokenization and 3D Secure challenges inside its iframe. -7. When authorization succeeds, the component fires `onSuccess`. The UI switches to a processing state. +7. When authorization succeeds, the component fires `onSuccess`. The UI switches to a processing screen. 8. Chargebee delivers a `payment_intent_updated` webhook with status `authorized`. Your backend worker verifies the intent and calls `subscription.createWithItems` to activate the subscription. ```typescript @@ -263,17 +262,17 @@ const result = await chargebee.subscription.createWithItems( await db.checkoutAttempts.markCompleted(attempt.id, result.subscription.id); ``` -Use separate idempotency keys for creating the Payment Intent and creating the subscription. Reusing the same key across different endpoints causes Chargebee to reject the second call with an error. +Use distinct idempotency keys for creating the Payment Intent and creating the subscription. Reusing the same key across different endpoints causes Chargebee to reject the second call with an HTTP 422 error. Estimate APIs do not create resources and do not accept idempotency keys. ✅ Do: Compute prices and taxes through the Estimate API before creating a Payment Intent. -⚠️ Don't: Call `subscription.createWithItems` from the browser's `onSuccess` callback. If the user closes the tab before the API call finishes, the payment is authorized but the subscription is never created. +⚠️ Don't: Call `subscription.createWithItems` from the browser's `onSuccess` callback. If the customer closes the tab during authorization, the payment succeeds but the subscription is never created. Fulfill via webhooks instead. ## 4. Record Native In-App Purchases -Web modals are not allowed for digital goods inside iOS or Android apps. App Store and Google Play policies require native in-app purchases. Chargebee handles these through [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview). +Web checkouts are prohibited for digital products inside iOS and Android apps. App Store and Google Play policies require native in-app purchases. Chargebee handles these through [Omnichannel Subscriptions](https://www.chargebee.com/docs/billing/2.0/mobile-subscriptions/omnichannel-subscription-overview). -The app store manages billing renewals, cancellations, and payment retries. Chargebee maintains a mirrored subscription record so your backend can evaluate entitlements consistently across web and mobile users. +Apple and Google manage renewals, cancellations, and payment collection. Chargebee maintains a mirrored subscription record so your backend can evaluate entitlements consistently across web and mobile platforms. ```mermaid sequenceDiagram @@ -298,10 +297,10 @@ sequenceDiagram 1. The customer completes a purchase using StoreKit 2 or Google Play Billing. 2. The mobile app sends the transaction ID (Apple) or order ID and purchase token (Google) to your backend. -3. Your backend verifies the user's session and links the purchase to their Chargebee customer ID. +3. Your backend validates the user session and links the purchase to their Chargebee customer ID. 4. Your backend calls `chargebee.recordedPurchase.create`. 5. Chargebee returns a `recorded_purchase` object with `status: "in_process"`. -6. Chargebee validates the purchase directly with Apple or Google and delivers an `omnichannel_subscription_created` webhook. +6. Chargebee validates the receipt directly with Apple or Google and delivers an `omnichannel_subscription_created` webhook. 7. Your webhook processor updates the local database mirror and unlocks the purchased features. ```typescript @@ -322,15 +321,15 @@ For Google Play, pass `google_play_store.order_id` (format: `GPA.xxxx-xxxx-xxxx- Configure Apple App Store Server Notifications and Google Cloud Pub/Sub in Chargebee. When a user cancels, renews, or pauses in their app store settings, Chargebee updates the omnichannel subscription automatically. -Chargebee's older mobile SDKs and wrappers belong to Mobile Subscriptions (Legacy) and no longer receive feature updates. New native applications should use StoreKit 2 and Google Play Billing directly, with receipts recorded via the Omnichannel API. +Chargebee's older mobile SDKs belong to Mobile Subscriptions (Legacy) and no longer receive feature updates. New native applications should use StoreKit 2 and Google Play Billing directly, with receipts recorded via the Omnichannel API. -✅ Do: Configure store server-to-server notifications so renewals, refunds, and cancellations sync to Chargebee. +✅ Do: Configure store server-to-server notifications so renewals and cancellations sync to Chargebee. ⚠️ Don't: Create regular Chargebee Billing subscriptions for mobile purchases. Omnichannel subscriptions manage their own lifecycle and do not produce Chargebee invoices. ## 5. Verify Purchases And Recover From Failures -Network timeouts, closed tabs, and expired sessions happen during checkout. A dependable integration treats the client callback as an optimistic hint, using server retrieval and webhooks to guarantee fulfillment. +Network timeouts, closed tabs, and expired sessions occur during checkout. A dependable integration treats the client callback as an optimistic hint, using server retrieval and webhooks to guarantee fulfillment. ### Verify Completed Sessions @@ -341,14 +340,14 @@ For Hosted Checkout, pass the returned `hosted_page.id` to your backend and call 3. Confirm the subscription items match the user's selected plan. 4. Mark the local checkout attempt completed to prevent replay attacks. -After verification, update your local mirror and bust the user's cached entitlements immediately. This gives the customer instant access without waiting for background webhook delivery. +After verification, update your local mirror and clear cached entitlements immediately. This gives the customer instant access without waiting for background webhook delivery. ### Recovery Scenarios | Scenario | System behavior | Recovery action | |:---|:---|:---| | Customer closes modal before paying | Hosted page remains in `created` state | Leave attempt open; generate a fresh hosted page if they resume later | -| Hosted page session expires (3-hour TTL) | Chargebee rejects checkout with expired error | Prompt the user to restart checkout and generate a new page | +| Hosted page session expires (3-hour TTL) | Chargebee rejects checkout with expired error | Prompt user to restart checkout and generate a new page | | Customer clicks Pay multiple times | Frontend sends duplicate requests | Return the existing intent or hosted page using a deterministic idempotency key | | User closes browser during 3DS redirect | Payment completes, but redirect handler never runs | Background `subscription_created` or `payment_intent_updated` webhook fulfills the order | | Webhook arrives before client redirect | Worker updates local mirror first | Client verification sees the updated record and redirects cleanly | @@ -358,24 +357,24 @@ After verification, update your local mirror and bust the user's cached entitlem ⚠️ Don't: Trust URL query strings like `?state=succeeded` without calling `hostedPage.retrieve` on your backend. -## See It Running In The Demo App +## See It Running In The Pointer Demo App -Pointer implements the Hosted Checkout model using `@chargebee/better-auth`. It provisions a free subscription at sign-up, opens an upgrade checkout when switching tiers, and refreshes entitlements immediately on return: +The Pointer demo app uses Hosted Checkout via `@chargebee/better-auth`. It provisions a free subscription at sign-up, opens an upgrade checkout when switching tiers, and refreshes entitlements immediately on return: -- [`pointer/app/choose-plan/_components/plan-picker.tsx`](../pointer/app/choose-plan/_components/plan-picker.tsx): Renders self-service plans and invokes `authClient.subscription.create` or `update`. -- [`pointer/app/_components/account-provisioning.tsx`](../pointer/app/_components/account-provisioning.tsx): Waits for the initial customer record before opening an upgrade checkout. -- [`pointer/lib/checkout-urls.ts`](../pointer/lib/checkout-urls.ts): Generates absolute success and cancel return URLs for hosted pages. -- [`pointer/app/api/entitlements/checkout-complete/route.ts`](../pointer/app/api/entitlements/checkout-complete/route.ts): Handles the checkout return, verifies the authenticated user, and triggers an optimistic entitlement refresh. -- [`pointer/plugins/chargebee-plugin.ts`](../pointer/plugins/chargebee-plugin.ts): Configures plan limits, organization billing permissions, and the webhook event bus. -- [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts): Asynchronously updates the PostgreSQL subscription mirror and version records. +* [`pointer/app/choose-plan/_components/plan-picker.tsx`](../pointer/app/choose-plan/_components/plan-picker.tsx): Renders self-service plans and invokes `authClient.subscription.create` or `update` to open Hosted Checkout. +* [`pointer/app/_components/account-provisioning.tsx`](../pointer/app/_components/account-provisioning.tsx): Waits for the initial customer record before opening an upgrade checkout. +* [`pointer/lib/checkout-urls.ts`](../pointer/lib/checkout-urls.ts): Generates absolute success and cancel return URLs for hosted pages. +* [`pointer/app/api/entitlements/checkout-complete/route.ts`](../pointer/app/api/entitlements/checkout-complete/route.ts): Handles the checkout return, verifies the authenticated user, and triggers an optimistic entitlement refresh. +* [`pointer/plugins/chargebee-plugin.ts`](../pointer/plugins/chargebee-plugin.ts): Configures plan limits, organization billing permissions, and the webhook event bus. +* [`pointer/workers/chargebee-webhook-processor.ts`](../pointer/workers/chargebee-webhook-processor.ts): Asynchronously updates the PostgreSQL subscription mirror and version records. ## Go-Live Checklist - [ ] Is Product Catalog 2.0 active with plan item prices, addon prices, and currencies configured? - [ ] Does your checkout model match your platform (Hosted Checkout, Payment Components, or Omnichannel)? -- [ ] Are Chargebee secret API keys restricted to backend environments, with only the publishable key exposed in browser bundles? +- [ ] Are Chargebee secret API keys restricted to backend environments, with only publishable keys in browser bundles? - [ ] Is Chargebee.js loaded directly from `https://js.chargebee.com/v2/chargebee.js`? -- [ ] Are hosted pages created via the API on your backend rather than unauthenticated client buttons? +- [ ] Are hosted pages created via backend API calls rather than unauthenticated client buttons? - [ ] Are redirect URLs for PayPal, GoCardless, and Plaid tested with `embed: false`? - [ ] Do backend mutation calls include a unique, deterministic `chargebee-idempotency-key` header? - [ ] Does the custom checkout path compute estimates before generating Payment Intents?