docs: P1g Stripe dues — API_REFERENCE + ARCHITECTURE + HANDOFF pass-42 (Phase 1 complete)
This commit is contained in:
@@ -952,3 +952,11 @@ Surfaced as the "Who's here" panel on the dashboard (present list + self check-i
|
||||
- `discloseAffiliations(input)` — `members.ts`; the member records their own conflict-of-interest affiliations.
|
||||
- `updateSpaceVisibility(input)` — `settings.ts`; admin toggles space/member-directory visibility.
|
||||
- `listDoorCards(input)` — `door.ts`; `door.operate`, service client; active cards for a connection's grant/revoke UI, returning only the masked `last4` (raw UID never returned).
|
||||
|
||||
### Stripe recurring dues: `stripe.ts` + `lib/stripe/*` + webhook (migration 040)
|
||||
Per-space OWN Stripe account (NOT Connect). Pure mapping in `lib/stripe-logic.ts` (pinned `STRIPE_API_VERSION`, `duesMemberStatus`, `graceExceeded`, `priceIdForTier`, `isStripeConfigured`) is unit-tested. Server helpers in `lib/stripe/config.ts` (vault store/read of the secret key + webhook signing secret — door secret pattern; `getStripeConfig`) and `lib/stripe/client.ts` (`getStripe`: a per-request client with the space's decrypted key + pinned apiVersion — never global, never client).
|
||||
- `getStripeSettings()` / `saveStripeSettings(input)` — Admin only. Settings view returns status flags only, **never secret values**; save is write-only for secrets (blank = keep), config (mode, publishable key, tier→price map, grace days) in `integrations.config`, secrets encrypted in the vault.
|
||||
- `startDuesCheckout()` — any member; resolves the caller + their tier server-side, ensures a Stripe Customer (reuse/create + `member_billing` upsert), returns a hosted Checkout Session URL in `subscription` mode with `client_reference_id` + metadata on the session AND `subscription_data.metadata`.
|
||||
- `startBillingPortal()` — any member with a customer; returns a Stripe Billing Portal URL (self-serve card/plan/cancel; the space must activate the Portal in its Stripe Dashboard once).
|
||||
- `getMyBilling()` — the caller's own billing status (service client; `member_billing` is admin-RLS, mirrors `getMyCards`). `listMemberBilling()` — admin/board/treasurer dues view.
|
||||
- Webhook `POST /api/stripe/webhook/[space]` (nodejs, force-dynamic, unauthenticated by design — verify is signature-based; no auth middleware in this app): raw `req.text()` body, loads THAT space's secret + signing secret from the vault, `constructEvent`, idempotency via `stripe_webhook_events` PK (replay→200). On `checkout.session.completed` / `customer.subscription.*` / `invoice.paid|payment_succeeded`: upsert `member_billing`, record a `stripe` `payments` row, and map `duesMemberStatus`+`graceExceeded` onto `space_members.status` (only `current`↔`late` — never auto-inactive or auto-approve `unverified`; `last_paid_at` on paid). Member resolved by metadata then customer id, always space-scoped. Bad signature/config → 400; handler error → 500 (Stripe retries; idempotency-safe).
|
||||
|
||||
Reference in New Issue
Block a user