# Pathrule Pattern: Subscriptions & Usage Billing (1.0.0)
# ::pathrule:package:subscriptions-usage-billing

### [RULE] Report usage through Billing Meters with idempotent meter events  (path: /src/usage)
<!-- scope: folder | priority: high | strict -->

Meter all usage through Stripe Billing Meters and meter events; the legacy `usage_records` API is deprecated and must not be used for new code.

- Set a unique `identifier` on every meter event so retries dedupe inside Stripe's rolling 24 hour+ window; reuse the same identifier when you retry a failed send.
- Keep the event `timestamp` within the past 35 days and no more than 5 minutes in the future, or Stripe rejects it.
- For high throughput (over a few hundred events/sec) use the v2 meter event stream with a session token instead of one synchronous call per event.
- Treat your own `usage_events` table as the source of truth; Stripe meter summaries are for billing, not for showing customers their live usage.

---

### [RULE] Verify, dedupe, and acknowledge Stripe webhooks before doing work  (path: /src/api/webhooks)
<!-- scope: folder | priority: high | strict -->

Drive all billing state transitions from verified webhooks, never from browser redirects, because the user can close the tab before the redirect fires.

- Verify the signature with the raw request body and the endpoint secret; reject anything that fails before parsing.
- Make handlers idempotent by recording processed `event.id` values and skipping duplicates, since Stripe can deliver the same event more than once.
- Return a 2xx within seconds and offload slow work to a queue; long handlers cause Stripe retries and duplicate processing.
- Subscribe to `v1.billing.meter.error_report_triggered` and alert on `meter_event_customer_not_found` spikes, which signal a broken usage integration silently dropping events.

---

### [MEMORY] Entitlements are derived from subscription webhooks, not local guesses  (path: /src/billing)

Stripe Entitlements expose what each customer can access based on their active subscription, and we mirror them locally rather than hardcoding plan-to-feature maps.

- On `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, and `entitlements.active_entitlement_summary.updated`, refetch the customer's active entitlements and upsert them into our `entitlements` table.
- Gate every paid feature on the stored entitlement lookup, not on the price ID or product name, so plan and packaging changes do not require code edits.
- Treat `customer.subscription.deleted` and past-due states as access removal, but keep a short grace window driven by `status` rather than deleting rows immediately.
- Reconcile nightly by listing live subscriptions and entitlements to heal any missed webhook.

---

### [MEMORY] Proration, seats, and dunning are configured in Stripe, not hand-rolled  (path: /src/billing)

Plan changes, seat counts, and recovery from failed payments are owned by Stripe Billing; we configure behavior and react to the resulting events.

- For upgrades and downgrades, update the subscription item and let Stripe compute proration (`proration_behavior`); never compute partial-period charges ourselves.
- Model seats as a licensed (per-seat) subscription item quantity; adjust the quantity on team membership changes so proration applies automatically.
- Enable Smart Retries for dunning (default schedule is 7 retries over 21 days) and react to `invoice.payment_failed`, `invoice.paid`, and `customer.subscription.updated` to flip account status.
- Never store prices locally; always reference Stripe price IDs so amounts stay authoritative.

---

### [SKILL] subscriptions-usage-billing-review  (path: /)

---
name: subscriptions-usage-billing-review
description: Review checklist for subscription and usage-based billing changes on Stripe Billing. Use before merging any code that records usage, handles billing webhooks, syncs entitlements, changes plans or seats, or touches dunning.
---

# Subscriptions & usage billing review

- [ ] Usage is reported via Billing Meters and meter events, not the deprecated `usage_records` API.
- [ ] Every meter event sets a unique `identifier` and is safe to retry without double counting.
- [ ] Meter event timestamps fall within the past 35 days and under 5 minutes in the future.
- [ ] High-volume metering uses the v2 meter event stream rather than one synchronous call per event.
- [ ] A local `usage_events` ledger is written first and treated as the source of truth.
- [ ] Webhook signatures are verified against the raw body and endpoint secret before parsing.
- [ ] Webhook handlers are idempotent on `event.id` and tolerate duplicate delivery.
- [ ] Handlers return 2xx quickly and push slow work to a queue.
- [ ] `v1.billing.meter.error_report_triggered` is subscribed and alerts on `meter_event_customer_not_found`.
- [ ] Entitlements are synced from subscription and entitlement-summary webhooks and feature gates read the stored entitlement, not the price ID.
- [ ] Plan changes update the subscription item and let Stripe handle proration; no hand-rolled proration math.
- [ ] Seats are modeled as a per-seat item quantity that updates on membership changes.
- [ ] Dunning relies on Smart Retries plus reactions to `invoice.payment_failed` and `invoice.paid`.
- [ ] No prices are stored locally; code references Stripe price IDs.
- [ ] A nightly reconciliation job heals missed webhooks for subscriptions and entitlements.
