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

11 KiB

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.