Credit Pack Purchasing
Credit packs let organizations buy extra credits on top of their subscription allowance. Purchased credits persist across billing cycles and sit in the third position of the credit waterfall — after op-type and included credits, before overdraft.
The purchasing flow was added in PR #1111. The webhook fulfillment side (crediting org_budget.purchased_credits on checkout.session.completed) was already landed in PR #127.
API Endpoints
Section titled “API Endpoints”List credit packs
Section titled “List credit packs”GET /api/billing/credit-packsAuth: None — the catalog is public, matching the subscription tier listing.
Returns all active packs sorted by display order. Each pack includes an is_checkout_ready flag that’s true only when the admin has populated a Stripe Price ID.
Response type — CreditPackInfo:
pub struct CreditPackInfo { pub id: Uuid, pub name: String, pub credits: i32, pub price_cents: i32, pub is_checkout_ready: bool,}Create checkout session
Section titled “Create checkout session”POST /api/billing/credit-packs/checkoutAuth: Owner-only. Non-owners get a 403.
Parameter: credit_pack_id: Uuid
Creates a Stripe Checkout Session in payment mode (not subscription) and returns a StripeRedirect { url }. The session includes two metadata keys that the webhook dispatcher uses to route the completion event:
| Metadata key | Value | Purpose |
|---|---|---|
credit_pack_id | UUID of the pack | Identifies which pack was purchased |
type | "credit_pack" | Disambiguates from future one-time payment flows |
The service calls ensure_org_stripe_customer to guarantee a Stripe customer exists before creating the session. Success and cancel URLs redirect back to /settings/tab/billing?checkout=success|canceled.
List purchase history
Section titled “List purchase history”GET /api/billing/credit-packs/purchasesAuth: Any org member (session-scoped to the caller’s org).
Returns the org’s purchase history, most recent first, joined with the pack name.
Response type — CreditPackPurchaseInfo:
pub struct CreditPackPurchaseInfo { pub id: Uuid, pub pack_name: String, pub credits: i32, pub amount_cents: i32, pub purchased_at: String, // RFC 3339}UI Component
Section titled “UI Component”CreditPacksSection renders on the billing settings tab when the org has an active subscription. It sits between the usage view and the subscription tier cards.
Layout:
- Pack cards — a 3-column grid showing name, price (via
format_price_cents), and credit count. Owners see a “Buy credits” button; non-owners see the catalog without actions. Packs without a Stripe Price ID show a disabled “Not available” button. - Purchase history — a responsive table (header row on
sm:and up) with columns: Date, Pack, Credits, Amount.
Purchase flow:
- Owner clicks “Buy credits” on a pack card
- Button shows “Redirecting…” and calls
create_credit_pack_checkout_session_api - On success,
navigate_to_externalredirects to Stripe’s hosted checkout page - On error, a toast notification surfaces the failure message
Shared Utilities
Section titled “Shared Utilities”This PR extracted two helpers from the billing section into src/shared/utils/:
| Function | Signature | Purpose |
|---|---|---|
format_price_cents | fn(i32) -> String | Formats cents as $X.XX with .abs() guard |
navigate_to_external | fn(&str) | Full-page JS redirect via document::eval — used for Stripe Checkout, Billing Portal, and OAuth handoffs |
File Map
Section titled “File Map”src/mods/billing/├── api/│ ├── list_credit_packs_api.rs # GET catalog│ ├── create_credit_pack_checkout_session_api.rs # POST checkout│ └── list_credit_pack_purchases_api.rs # GET history├── components/│ └── credit_packs_section_component.rs # UI surface├── services/│ └── create_credit_pack_checkout_session_service.rs # Stripe session creation└── types/ ├── credit_pack_info_type.rs # CreditPackInfo └── credit_pack_purchase_info_type.rs # CreditPackPurchaseInfo
src/shared/utils/├── format_price_cents.rs└── navigate_to_external.rs