zlh-grind/SCRATCH/billing-stripe-handover-apr11-2026.md

4.1 KiB

Billing + Stripe Handover (Apr 11, 2026)

Context

This document captures the current billing integration state between:

  • zpack-api
  • zpack-portal
  • Stripe (sandbox/test mode)

This was developed during a developer-mode session where chat history is not persisted, so this doc acts as the continuity handoff.


Current Status

Stripe

  • Stripe sandbox/test mode configured
  • Products + recurring prices created
  • Checkout sessions successfully redirect to Stripe
  • Test checkout completed using Stripe test card
  • Stripe dashboard confirms:
    • checkout.session.completed
    • customer created
    • subscription created
    • invoice/payment succeeded (test)

API (zpack-api)

Working:

  • ensureStripeCustomer() persists stripeCustomerId
  • /api/billing/checkout successfully creates Stripe Checkout session
  • Billing foundation fields exist in Prisma:
    • stripeCustomerId
    • subscriptionStatus
    • plan

Not yet complete:

  • Webhook delivery not working (API not publicly reachable)
  • As a result:
    • subscriptionStatus is NOT being updated
    • plan is NOT being updated

Root cause:

  • Stripe cannot reach /api/billing/webhook
  • This is NOT a checkout issue
  • This is a webhook reachability issue

Portal (zpack-portal)

Working:

  • Billing page renders correctly for unsubscribed users
  • Plan selection UI present
  • Checkout button calls API
  • Redirect to Stripe works

Partially complete:

  • Needs final alignment for:
    • trialing state
    • active state
    • billing-exempt/admin display

Key Architectural Decisions

Billing Model

  • Users can register without paying
  • Subscription is optional until resource enforcement is implemented
  • Future plan: soft gate (likely 1 free server, then require subscription)

Stripe Integration Strategy

  • Use Stripe Checkout (NOT custom payment form)
  • Use Stripe Billing Portal for management
  • Use webhooks to update DB state
  • DB remains source of truth for access decisions

Metadata Usage

Stripe objects should include:

  • metadata.userId
  • optionally metadata.plan

This is used for safer webhook reconciliation.


Current Blocker

Webhook Delivery

Problem:

  • API is not publicly reachable
  • Stripe cannot POST webhook events

Observed behavior:

  • stripeCustomerId set (checkout path)
  • subscriptionStatus remains null
  • plan remains null

Immediate Next Steps

Use Stripe CLI forwarding:

stripe listen --forward-to http://10.60.0.18:4000/api/billing/webhook

Then:

  • copy CLI-provided whsec_...
  • set as STRIPE_WEBHOOK_SECRET
  • restart API
  • re-run test checkout

Expected result:

  • webhook hits API
  • DB updates correctly

Option B (Staging/Prod Path)

Expose only webhook endpoint via public domain:

Example:

https://billing.zerolaghub.com/api/billing/webhook

Requirements:

  • real reverse proxy or tunnel (NOT DNS redirect)
  • HTTPS
  • Stripe signature verification enabled

Important Notes

Namecheap Redirect

  • simple DNS/URL redirect is NOT sufficient
  • Stripe requires direct POST endpoint
  • must use proxy/tunnel instead

Stripe CLI

  • does NOT need to run on API VM
  • but easiest if it does
  • must be able to reach internal API IP

Test Cards

Use Stripe test card:

4242 4242 4242 4242

Known Good

  • Checkout flow: WORKING
  • Customer creation: WORKING
  • Portal integration: WORKING enough for testing

Known Broken

  • Webhook delivery: NOT working
  • Subscription state persistence: NOT working

Next Session Starting Point

  1. Get Stripe CLI installed and running
  2. Verify webhook hits API
  3. Confirm DB updates:
    • subscriptionStatus
    • plan
  4. Refresh portal billing page
  5. Verify UI reflects new state

Future Work (Post-Webhook)

  • Implement trial handling (trialing state)
  • Add quota enforcement (soft gate)
  • Improve billing UI states
  • Add billing portal button behavior
  • Consider exposing webhook endpoint cleanly for production

Summary

The system is effectively complete except for webhook reachability.

Once webhooks are flowing, billing becomes fully functional end-to-end.