Files
rootnode-academy/docs/PHASE9_1_STRIPE_TEST_VALIDATION.md
T
ced4568 77fd1869db docs(stripe): document Phase 9.1 blocked status and live-mode readiness checklist
New: PHASE9_1_STRIPE_TEST_VALIDATION.md — baseline verification, the exact Stripe-credential
search performed (none found), the Stripe architecture review (dynamic price_data, unchanged),
what was proven vs. blocked per the mission's own step list, and the live-mode readiness
checklist. Updated: STRIPE_INTEGRATION_RUNBOOK.md and WINTER_BREAK_LAUNCH_CHECKLIST.md to point
at the new opt-in test and the blocked status. PHASE9_COMMERCE_AND_LAUNCH.md gets a short
addendum. No application code changed this phase — no genuine defect was exposed without real
Stripe traffic, so no regression rerun was required.
2026-08-27 10:40:53 -05:00

181 lines
11 KiB
Markdown

# Phase 9.1 — Real Stripe TEST-Mode Integration Validation
Status: **BLOCKED — no Stripe TEST credentials are available to this session.** This document
records exactly what was verified without them, exactly what Chase needs to provide, and the
permanent, opt-in validation script that will run the real proof the moment those credentials
exist. No live money was risked; no Stripe network call of any kind was made during this phase.
## 1. Baseline verification
- Branch: `main`. Working tree: clean at start.
- `git fetch origin` — HEAD and `origin/main` both `80a5f6e4576cb9b83a60c9e9b57e4ebc52b62f07`,
exactly the Phase 9 milestone commit.
- Phase 9 payment code and documentation inspected in full: `payment-provider.js`,
`commerce.js`, `webhook-routes.js`, `offerings.js`'s registration/conversion path,
`admin-offerings-routes.js`'s payment routes, `integration-routes.js`'s checkout/agreement/
status routes, `platform/test/e2e-phase9.js`, `infra/scripts/drills/
test-phase9-winter-camp-commerce.js`, `docs/STRIPE_INTEGRATION_RUNBOOK.md`, `docs/
WINTER_BREAK_LAUNCH_CHECKLIST.md`. Nothing was changed as a result of this review — no defect
was found, and none was invented to justify a change (§1/§22's own instruction).
## 2. Stripe TEST credential search — none found
Searched, in order: process environment (`env | grep -i stripe`), every `.env`/`.env.example`
file reachable from this repository and the operator's home directory, this repository's own
Infisical project wiring (none exists — no `.infisical.json` in this repo), and this session's
own memory of prior credential-provisioning work (none references Stripe or this project). No
`STRIPE_SECRET_KEY`, no `STRIPE_WEBHOOK_SECRET`, in any test or live form, exists anywhere this
session can reach.
**This is exactly STOP condition 1/2 from this phase's own authorization.** Per §2/§27: do not
fake validation, stop after documenting exactly what's needed.
### What Chase needs to provide or configure
1. A Stripe account (test mode is available on any account, including one with no live
business verification completed yet).
2. From the Stripe Dashboard, in **Test mode**: the test **Secret key** (`sk_test_...`) →
`STRIPE_SECRET_KEY`.
3. A webhook signing secret for a test endpoint — either a permanent test-mode webhook endpoint
configured in the dashboard, or the ephemeral one the Stripe CLI prints when running
`stripe listen --forward-to <gateway>/api/webhooks/payment` → `STRIPE_WEBHOOK_SECRET`. Full
setup steps already documented in `docs/STRIPE_INTEGRATION_RUNBOOK.md`'s "Local testing
against real Stripe TEST mode" section (written during Phase 9, unchanged and still accurate).
4. Both values set as environment configuration on the running Platform API process
(`PAYMENT_PROVIDER=stripe` alongside them) — never committed to git, never placed in any
browser-reachable configuration. **No credential value appears anywhere in this document, any
commit, or any log produced during this phase.**
Once those exist, run the permanent validation script this phase adds (§4 below) to produce the
actual proof this phase was asked to deliver.
## 3. Stripe adapter architecture — confirmed, unchanged
Inspected `payment-provider.js`'s `StripeProvider.createCheckoutSession()`: it uses **dynamic
Checkout line-item `price_data`** (`unit_amount`/`currency`/`product_data.name` supplied inline
per session), **not** pre-created Stripe Price/Product IDs. RootNode's `offerings` table remains
the sole source of truth for identity, price, and currency; Stripe never becomes a second
business-domain model — no Stripe object id is ever treated as anything other than an opaque
`provider_reference`. This matches §3's explicit constraint and required no change.
## 4. Permanent opt-in Stripe integration test — added
`platform/test/stripe-integration-test.js` (new). Default behavior (no environment variable
set): prints a skip message and exits `0` with **zero Stripe network traffic** — confirmed by
running it with no configuration at all. **Verified this run**:
```
$ node test/stripe-integration-test.js
Stripe integration test skipped (RUN_STRIPE_INTEGRATION_TEST is not "true") — no Stripe network traffic, no credentials required.
```
Enabled via `RUN_STRIPE_INTEGRATION_TEST=true`, reusing the exact `STRIPE_SECRET_KEY`/
`STRIPE_WEBHOOK_SECRET`-adjacent naming Phase 9 already established (only `STRIPE_SECRET_KEY` is
actually needed by the script itself, to independently query Stripe's own API — the webhook
secret only needs to be configured on the server process, not read by this script). Guard
clauses verified directly, without needing real credentials:
| Condition | Verified behavior |
|---|---|
| `OWNER_EMAIL`/`OWNER_PASSWORD` unset | Exits `1` with a clear message. |
| `STRIPE_SECRET_KEY` unset | Exits `1`, names the exact variable needed. |
| `STRIPE_SECRET_KEY` starts with `sk_live_` | **Refuses to run** — "TEST MODE ONLY" — exits `1`. |
| `STRIPE_SECRET_KEY` any other non-test shape | Refuses to run out of caution — exits `1`. |
| A syntactically valid `sk_test_...` value, no real stack running | Reaches `main()` cleanly, fails only on the expected connection error — confirms no syntax defect in the enabled code path. |
When real test credentials exist and the dev stack is running with `PAYMENT_PROVIDER=stripe`,
this script will automatically and honestly prove, via pure API calls (no browser needed):
- RootNode creates a **real** Stripe TEST Checkout Session (not RootNode's own claim — verified
by retrieving the session directly from Stripe's own `checkout.sessions.retrieve` API).
- The amount/currency Stripe actually recorded matches the offering's server-side price exactly,
even when the request is sent with a tampered `amount`/`currency` in the body — proving
server-authoritative pricing against **Stripe's own object**, the strongest form of that proof.
- `session.livemode === false` is asserted directly — the script itself refuses to consider its
own run valid against anything but a real TEST-mode session.
- RootNode's webhook route rejects an invalid/garbage signature cleanly, with **zero domain side
effect** (the registration's status is re-read and asserted unchanged).
What it deliberately does **not** automate: actually completing Stripe's hosted Checkout page.
There is no supported pure-API way to "pay" a Checkout Session, and fabricating a signed
"payment succeeded" webhook for a session nobody actually paid would itself be exactly the kind
of faked validation this phase was told never to produce. Instead, the script prints the real
checkout URL and polls RootNode's own status endpoint for a bounded window (default 90s,
`STRIPE_INTEGRATION_TEST_WAIT_SECONDS`) while a human completes payment in a browser (Stripe's
test card `4242 4242 4242 4242`) or a parallel `stripe listen --forward-to` relays Stripe's real
event — see `docs/STRIPE_INTEGRATION_RUNBOOK.md`. If nothing arrives in that window (expected in
an unattended run), it says so plainly rather than reporting a false pass.
Never runs as part of normal regression suites or CI — no other test file requires or imports it,
and its own default is a guaranteed no-op.
## 5. What was actually proven this phase
| Step from the mission spec | Result |
|---|---|
| 1. Baseline verification | Done — see §1. |
| 2. Stripe TEST-mode safety | No live-mode action was possible or attempted; no credentials of any kind exist. |
| 3. Stripe architecture review | Done — dynamic `price_data`, confirmed, unchanged (§3). |
| 4-20 (disposable offering through security revalidation, all requiring a real Stripe TEST payment/webhook round trip) | **Not performed — blocked on missing credentials.** Faking any of these was explicitly disallowed. |
| 21. Permanent opt-in integration test | **Built** (§4) — the automatable portion is real and will run the moment credentials exist; the human/CLI-dependent portion is honestly deferred, not faked. |
| 22. Application changes | None made — no genuine defect was exposed (none could be, without real Stripe traffic), so §22's "smallest correct fix" rule had nothing to apply to. |
| 23. Regression | No registration/agreement/enrollment/notification/frontend code changed this phase, so per §23's own conditional rule, no regression rerun was required. The new test file's default no-op path was verified directly (§4) instead. |
| 24. Documentation | This document, plus the update noted below. |
| 25. Live-mode readiness checklist | See §6. |
## 6. Live-mode readiness checklist
Everything Stripe-side is still pending exactly as `docs/STRIPE_INTEGRATION_RUNBOOK.md`'s "Going
live" section already lays out, restated here as a checklist per §25:
**Stripe:**
- [ ] Business/account verification completed in the Stripe dashboard.
- [ ] Live API secret key (`sk_live_...`) generated.
- [ ] A permanent live-mode webhook endpoint created, pointed at the production API's
`/api/webhooks/payment` URL, and its signing secret (`whsec_...`) captured.
- [ ] Payout/bank details configured in Stripe.
- [ ] Business/support information (statement descriptor, support contact) configured in Stripe.
**RootNode:**
- [ ] A real, HTTPS production URL for the Platform API (Stripe requires HTTPS for live webhooks).
- [ ] `STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` (live values) provisioned into production
secret configuration only.
- [ ] Production deployment itself (explicitly out of scope for this phase and Phase 9 —
unchanged status).
- [ ] Backup/monitoring in place for the production database holding `payment_transactions`.
- [ ] The real Winter Break offering configured with its real price, once decided.
**Business** (Chase's decisions, not invented here): final price, refund/cancellation policy,
camp dates, venue, capacity — all still open, tracked in `docs/
WINTER_BREAK_LAUNCH_CHECKLIST.md`.
**Legal** (unchanged from every prior phase's own list): the actual agreement/waiver text,
guardian/minor consent review, privacy/retention requirements — see `docs/
STUDENT_GUARDIAN_DATA_MODEL.md`.
Nothing above is marked complete — none of it has been verified, and this phase did not attempt
to invent or shortcut any of it.
## 7. Bugs found
None. No genuine defect was exposed because no real Stripe traffic was possible this phase.
## 8. Security posture — reasoned about, not re-verified against real Stripe
Every item in the mission's §20 checklist was already true and tested against the deterministic
provider in Phase 9 (`e2e-phase9.js`), and nothing about switching providers changes the code
path that enforces them (the same `commerce.js`/`webhook-routes.js` code runs regardless of
which provider is active — see §3). This phase did not and could not re-verify them against
REAL Stripe traffic specifically; that re-verification is exactly what `stripe-integration-test.js`
(§4) will perform automatically the moment credentials exist.
## Final status
**PHASE 9.1 BLOCKED — NO STRIPE TEST CREDENTIALS AVAILABLE.**
No real Stripe TEST Checkout, real TEST payment, or real signed Stripe webhook was created or
observed this phase — none of the actions requiring them were attempted, per this phase's own
explicit instruction never to fake validation. The permanent, opt-in `stripe-integration-test.js`
is ready and waiting; provide `STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` (test mode) and set
`RUN_STRIPE_INTEGRATION_TEST=true` to run the real proof.