feat(commerce): add payment-provider abstraction, commerce layer, and pricing schema

- migration 037: offerings.price_amount/price_currency, offering_registrations.reservation_expires_at,
  payment_transactions, payment_webhook_events (webhook_event_id as real PK — the idempotency mechanism)
- payment-provider.js: StripeProvider (real SDK, HMAC webhook verification) + TestProvider
  (deterministic, in-process, no network) behind one interface, selected via PAYMENT_PROVIDER
- commerce.js: createCheckoutSession (server-side price, never caller-supplied), processWebhookEvent
  (idempotent), handlePaymentSuccess/Failure/CheckoutAbandoned/Refund, reservation reaper support
- offerings.js: computeReservationExpiry, updateOffering (allowlisted PATCH), readiness gained
  price/payment_provider checks, convertRegistrationToEnrollment now fires the activation email
  every other enrollment path already triggers, and links the registration's guardian_id into
  student_guardians on conversion (real bug: a converted minor had no guardian link, so the new
  activation email had nowhere to resolve a recipient to)
- server.js: raw-body middleware scoped to /api/webhooks/payment, reservation-reaper poller
This commit is contained in:
2026-08-27 09:05:19 -05:00
parent 788fb197e0
commit b0e07bb17f
7 changed files with 541 additions and 15 deletions
+2 -1
View File
@@ -10,8 +10,9 @@
"bootstrap-owner": "node bootstrap-owner.js"
},
"dependencies": {
"better-sqlite3": "^9.4.3",
"express": "^4.19.2",
"pg": "^8.11.5",
"better-sqlite3": "^9.4.3"
"stripe": "^22.6.0"
}
}