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.
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 andorigin/mainboth80a5f6e4576cb9b83a60c9e9b57e4ebc52b62f07, 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
- A Stripe account (test mode is available on any account, including one with no live business verification completed yet).
- From the Stripe Dashboard, in Test mode: the test Secret key (
sk_test_...) →STRIPE_SECRET_KEY. - 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 indocs/STRIPE_INTEGRATION_RUNBOOK.md's "Local testing against real Stripe TEST mode" section (written during Phase 9, unchanged and still accurate). - Both values set as environment configuration on the running Platform API process
(
PAYMENT_PROVIDER=stripealongside 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.retrieveAPI). - The amount/currency Stripe actually recorded matches the offering's server-side price exactly,
even when the request is sent with a tampered
amount/currencyin the body — proving server-authoritative pricing against Stripe's own object, the strongest form of that proof. session.livemode === falseis 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/paymentURL, 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.