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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user