HatchedDocs
Pricing & billing

Stripe portal

Subscription management, invoices, and top-up purchases.

All subscription and top-up actions route through the Stripe Customer Portal. Hatched does not ship its own billing UI — the portal is the single source of truth for payment methods, invoices, plan switches, and credit bundle purchases.

Open the portal

From the dashboard: Billing → Manage billing opens a portal session scoped to the signed-in customer. Under the hood:

POST /api/v1/billing/portal
Authorization: Bearer <dashboard-jwt>
Content-Type: application/json

{ "flow": "default" }
{ "portal_url": "https://billing.stripe.com/p/session/…" }

flow can be:

  • default — the full portal (subscription, invoices, payment method, credit add-ons)
  • top_up — deep-link to the credit bundle add-on flow
  • cancel — deep-link to the cancel confirm

Subscription checkout (for new customers)

Free plan customers upgrading to Growth or Pro:

POST /api/v1/billing/checkout
Content-Type: application/json

{ "flow": "subscription", "plan": "growth" }
{ "checkout_url": "https://checkout.stripe.com/c/pay/cs_…" }

One-off credit bundle

Top-ups use a one-off Stripe Checkout session (or the portal's add-on UI).

POST /api/v1/billing/checkout
Content-Type: application/json

{ "flow": "credit_bundle", "credit_bundle": "100" }

Valid bundle keys: "100", "500", "1000".

The checkout.session.completed webhook (mode=payment) grants credits into the paid pool atomically, keyed on stripe_event_id so a double delivery is a no-op.

Webhook handling

Hatched subscribes to the following Stripe events:

EventEffect
checkout.session.completed (subscription)Set customer.plan, issue initial monthly credit grant.
checkout.session.completed (payment)Grant credits to paid pool (top-up metadata carries amount).
invoice.payment_succeeded (subscription_cycle)Grant monthly credit allotment for the plan.
invoice.payment_failedSet billing_status = past_due.
customer.subscription.updatedReconcile plan and status from Stripe truth.
customer.subscription.deletedDowngrade to starter, keep paid credits.
charge.refundedLogged; manual credit reversal required for top-ups.

All credit grants are idempotent on stripe_event_id via credit_transactions.uq_credit_tx_stripe_event.

Stripe product setup

One-time setup in the Stripe dashboard:

  1. Create two recurring products for plans:
    • Hatched Growthprice_growth → env STRIPE_GROWTH_PRICE_ID
    • Hatched Proprice_pro → env STRIPE_PRO_PRICE_ID
  2. Create three one-off products for top-ups:
    • 100 credits · $10 → env STRIPE_CREDITS_100_PRICE_ID
    • 500 credits · $50 → env STRIPE_CREDITS_500_PRICE_ID
    • 1,000 credits · $99 → env STRIPE_CREDITS_1000_PRICE_ID
  3. In Customer Portal, enable subscription update (Growth ↔ Pro), cancel, invoice history, and customer-initiated one-off purchases scoped to the three credit bundle products. Save the configuration id to STRIPE_PORTAL_CONFIGURATION_ID.