4.1 KiB
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()persistsstripeCustomerId/api/billing/checkoutsuccessfully creates Stripe Checkout session- Billing foundation fields exist in Prisma:
stripeCustomerIdsubscriptionStatusplan
Not yet complete:
- Webhook delivery not working (API not publicly reachable)
- As a result:
subscriptionStatusis NOT being updatedplanis 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:
trialingstateactivestate- 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:
stripeCustomerIdset (checkout path)subscriptionStatusremains nullplanremains null
Immediate Next Steps
Option A (Dev Testing - Recommended)
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
- Get Stripe CLI installed and running
- Verify webhook hits API
- Confirm DB updates:
subscriptionStatusplan
- Refresh portal billing page
- Verify UI reflects new state
Future Work (Post-Webhook)
- Implement trial handling (
trialingstate) - 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.