# 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

### Option A (Dev Testing - Recommended)
Use Stripe CLI forwarding:

```bash
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.
