docs(phase9): document commerce integration and Winter Break launch readiness

New: PHASE9_COMMERCE_AND_LAUNCH.md, WEBSITE_REGISTRATION_AND_CHECKOUT.md,
STRIPE_INTEGRATION_RUNBOOK.md, WINTER_BREAK_LAUNCH_CHECKLIST.md. Updated:
ROOTNODE_ACADEMY_BUSINESS_AND_PRODUCT_ARCHITECTURE.md (commerce boundary + PHASE 9 BUILT rows),
OFFERING_AND_REGISTRATION_MODEL.md (pricing, the payment/commerce boundary, three-status
principle), WEBSITE_ENROLLMENT_INTEGRATION.md (price fields, real agreement/checkout/webhook
sections replacing Phase 8 placeholders), WINTER_BREAK_CAMP_OPERATIONS.md (real payment step
replacing the manual placeholder).
This commit is contained in:
2026-08-27 09:06:04 -05:00
parent 281d60bc2d
commit 80a5f6e457
8 changed files with 884 additions and 74 deletions
+57 -22
View File
@@ -212,14 +212,21 @@ appear; `draft` and `cancelled` never do. Example response:
"registration_open": true,
"availability": "limited",
"location_name": "Community Room B",
"location_type": "community_center"
"location_type": "community_center",
"price_amount": 24900,
"price_currency": "usd",
"requires_payment": true
}
```
`availability` is `"open"` / `"limited"` / `"full"` — never a raw seat count (avoids inviting
scraping/timing games against a public endpoint). No internal notes, no organization contact
info, no instructor identity, no pricing — the website owns pricing display entirely; this
endpoint doesn't return it.
info, no instructor identity — just enough to build a pricing/availability display. **As of Phase
9**: `price_amount` is an integer in minor currency units (cents — `24900` = $249.00), `null` for
a free offering; `price_currency` is a lowercase ISO code (`"usd"`); `requires_payment` is a
plain derived boolean (`price_amount !== null`) so the website never has to reimplement that
check. The actual checkout amount is always read server-side from this same offering row at
checkout-creation time — the website never sends an amount anywhere in this contract.
### 2. Select an offering, then submit a registration (server-to-server)
@@ -285,35 +292,63 @@ required; `phone` optional). RootNode matches an existing guardian by email firs
family doesn't get a duplicate guardian record per sibling/per camp) or creates a new one. The
guardian's email is never written to the eventual student's own identity record.
### 6. Agreement flow
### 6. Agreement flow (Phase 9 — live)
Not yet exposed on this endpoint — an agreement acceptance is currently recorded by RootNode
staff (or a future self-service step) after the registration is submitted, before it can be
confirmed. If your registration flow collects agreement acceptance at submission time, talk to
the RootNode team about extending this contract before building around a workaround.
```
POST /api/integrations/registrations/:id/agreements
Body: { "agreement_id": "winter-camp-waiver-2026" }
```
### 7. Future payment step
Same shared-secret header as every other route in this section. Which party accepts is
server-derived from the registration's own `is_minor`, never a value you send: an adult accepts
as themselves (before their student account even exists yet), a minor's registration is accepted
by the guardian already on file from submission. `201 { "accepted": true, "agreement_id": ...,
"accepted_version": ... }` on success; `404 AGREEMENT_NOT_FOUND` if the id doesn't match a real
agreement configured on this offering. **Checkout is blocked (`409 AGREEMENTS_INCOMPLETE`) until
every required agreement is accepted** — call this before calling checkout below.
Not built. Today, a registration is manually moved to `confirmed` by RootNode staff once payment
is verified through whatever process exists outside the platform. A future phase's real payment
integration would do this automatically on a verified commerce event.
### 7. Checkout (Phase 9 — live)
### 8. Confirmation
```
POST /api/integrations/registrations/:id/checkout
Body: { "success_url": "https://rootnodeacademy.com/enroll/success", "cancel_url": "https://rootnodeacademy.com/enroll/cancelled" }
```
Once `confirmed` (manually today, automatically in a future payment-integrated phase) and every
required agreement is accepted, the registration can convert to a real enrollment.
Server-to-server only — the browser never calls this directly, and never supplies a price. The
amount, currency, and product description all come from the offering's own server-side
configuration. Success: `201 { "checkout_url": "...", "transaction_id": "..." }` — redirect the
customer's browser to `checkout_url` (RootNode's configured payment provider's own hosted
checkout page; today that's Stripe Checkout in test mode, or a fake URL from the deterministic
test provider in local dev — see `docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md`). A free offering
(`price_amount: null`) returns `409 NO_PRICE_CONFIGURED` — never call this for a free offering;
just proceed straight to confirmation. Full error table, the success-page design (never trust a
returning `?paid=true`), and the status-lookup endpoint are in `docs/
WEBSITE_REGISTRATION_AND_CHECKOUT.md`.
### 9. Enrollment activation
### 8. Payment confirmation — a verified webhook, never a browser redirect
Conversion creates (or matches) the real student account and a real academic enrollment, reusing
the exact same activation-email machinery `/api/integrations/enrollments` already triggers — no
second activation path exists.
RootNode's own webhook (not something the website calls) is the ONLY thing that confirms
payment. Once confirmed, the registration walks forward to `confirmed` and converts to a real
enrollment automatically — reusing the exact activation-email flow described below, no separate
"payment succeeded" email exists. See `docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md` for how to
build a safe success page against this (poll the status-lookup endpoint; never derive success
from the URL alone).
### 9. Confirmation and enrollment activation
Once `confirmed` (via the webhook above for a priced offering, or a manual admin transition for
a free one) and every required agreement is accepted, the registration converts to a real
enrollment — creating or matching the real student account and reusing the exact same
activation-email machinery `/api/integrations/enrollments` already triggers. No second
confirmation path and no second activation path exist.
### 10. What this endpoint does NOT do
No payment-card data (never sent, never has a field for it). No password (RootNode issues its
own credential/activation flow). No arbitrary registration status (server-defined transitions
only — see `docs/OFFERING_AND_REGISTRATION_MODEL.md`). All example data above is fake — do not
No payment-card data (never sent, never has a field for it — see `docs/
WEBSITE_REGISTRATION_AND_CHECKOUT.md` for the full payment-security posture). No password
(RootNode issues its own credential/activation flow). No arbitrary registration status
(server-defined transitions only — see `docs/OFFERING_AND_REGISTRATION_MODEL.md`). All example
data above is fake — do not
reuse these values against a real deployment.
## What the student experiences after you call this (as of Phase 5C)