StackTrading Docs

SRS: Stage 1 — Checkout Flow — Steps 0–5 (UC 2.1–2.6)

FieldValue
BA in ChargeHuyen Dinh
Date Created2026-07-23
Versionv19
Last Updated2026-08-24
Document ReferencesRFQ_ Website and Dashboard Implementation V7.pdf (§Step 0–§Step 5)RFQ_ Stack Trading Prop Tech V7.pdf (§/system/status, §/calculate-cart, §/execute-checkout)Zapier Integration V7.pdf (§3.1.9 checkout_ui_routing, §3.1.10 Payment_Method_Config, §Tables A–J)

⚠️ CR Priority (CR wins over all other sources):

  • [CHR-6] adds zip_code (VARCHAR, nullable) to Users table + new country_zip_requirements reference table; GET /system/status returns zip_requirements map.
  • [CHR-16] overrides RFQ V7 §3.3 (Pass Rate Aggregator cron) — cached_pass_rate is now computed via SUM(sims_passed)/(SUM(sims_passed)+SUM(sims_failed)) from the append-only Firm_Sim_Funnel_Snapshots table, populated daily by Zapier Flow 29 → POST /financial/daily-snap (no custom Node.js cron job needed).

UC Index

UC_IDUse Case NameBusiness Description
UC_2.1.1System Status APIOn checkout page load, frontend calls GET /system/status once to retrieve geo-routing, pricing, cohort status, and gate flags that govern all subsequent steps.
UC_2.1.2Geo-Based Compliance UI Variants (Flow A–J)System routes the user to a compliance UI variant (Flow A through J) based on their country and region, determined by the Cloudflare geo-IP headers.
UC_2.1.3Gate 1 — WaitlistWhenGlobal_Var_Allow_New_Signups == FALSE, the user is redirected to a dedicated Waitlist Lead Capture page (retains standard Marketing Header/Footer).
UC_2.1.4Gate 2 — GeoblockWhen the user's IP matches a sanctioned country, the checkout is replaced by a hard-stop 403 block page.
UC_2.1.5Pricing EngineFrontend parses cohort and pricing data from the /system/status response to dynamically populate the pricing display throughout the checkout.
UC_2.2Step 1: Asset Class SelectionUser selects their preferred asset class (Futures or Forex) as the first configuration step.
UC_2.3Step 2: Capital Allocation SelectionUser selects one of three Evaluation Package tiers (Associate, Accelerated Associate, Advanced Associate), each with distinct pricing, notional capital, and career ladder entry points.
UC_2.4Step 3: Platform SelectionUser selects their preferred trading platform from a dynamically loaded list filtered by chosen asset class.
UC_2.5Step 4: Market Data SelectionFutures users select their market data feed subscriptions (CME pre-selected and locked; NYMEX/CBOT/COMEX optional).
UC_2.6.1Step 5: PII Capture & ComplianceUser fills in personal information (Name, Email, Billing Address, Country/State, Shirt Size), compliance checkboxes (flow-dependent), and triggers tax calculation.
UC_2.6.2Step 5: Lead Capture & Cart AbandonmentTwo-phase capture: Email field onBlur → POST /capture-lead creates/updates a Guest record + fires Cart_Abandonment webhook. [Next] click → UPSERT full PII into the same record, navigate to Step 6. No Auth0 account exists at either phase.

Step 0: System Initialization & Access Gates

UC_2.1.1 — System Status API

1. Overview

FieldContent
IDUC_2.1.1
Use CaseSystem Status API
DescriptionOn checkout page load, the frontend calls GET /system/status exactly once. The response payload is stored in global checkout state and used by all subsequent steps to determine geo-routing, gate status, pricing, and cohort membership.
Zapier Flow
Zapier TableTable C (Global_Var_Allow_New_Signups, Founder_Cohort_Open, Founder_Cohort_Limit)Table J (Founder pricing / Standard pricing)
3rd PartyCloudflare (geo-IP detection)

2. Trigger

The user navigates to the dedicated checkout page URL for the first time in the session.

Note: Checkout is implemented as a dedicated page, not a modal or overlay on the marketing page. Four architectural requirements govern this decision: (1) Same-Tab Routing (2) Cart Abandonment Deep Link (3) Attention Ratio — once inside the checkout funnel, all off-page distractions are removed. (4) No Standard Marketing Header/Footer — the standard marketing header and footer are not rendered on this route, except for Gate 1 (Waitlist, Ref: UC_2.1.3): when triggered, the checkout page is unmounted and the user is redirected to a separate dedicated Waitlist page, which does render the standard Marketing Header and Footer.

3. Pre-conditions

  • User is on the checkout page.
  • No prior GET /system/status response exists in the current session.

4. Post-conditions

  • The full response payload (JSON below) is stored in the global checkout state.
  • Gate 1 and Gate 2 conditions are evaluated immediately.

Response Payload Schema:

{
  "Global_Var_Allow_New_Signups": boolean,
  "is_founder_cohort":    boolean,
  "pricing_tiers": {        // object — keyed by product_id
    "EVAL_L1":            float,  // raw price, no currency symbol (e.g. 650.00)
    "EVAL_L2":            float,
    "EVAL_L5":            float
  },
  "geo_blocked":          boolean,
  "geo_country":          string,
  "geo_region":           string,
  "required_flow":        string,
  "methods": [
    {
      "id":               string,
      "label":            string,
      "explanatory_text": string,
      "cta_text":         string,
      "icon_tags":        [string]
    }
  ],
  "is_launch_phase":      boolean,
  "historical_pass_rate": float,
  "zip_requirements": {     // object — keyed by country_iso2, built from country_zip_requirements table
    "US":                 boolean,
    "CA":                 boolean,
    "IE":                 boolean
    // ... one entry per country in country_zip_requirements
  }
}

Note: current_price (float) and founder_count (int) have been removed from this response.

Field Notes:

FieldMeaning
is_founder_cohortValue ofGlobal_Var_Founder_Cohort_Open (Table C) — global switch toggled manually by Ops. TRUE = all new signups at this moment receive Founder status and Founder pricing; FALSE = Standard pricing applies. Not a per-user flag — same value for every visitor at any given moment.
pricing_tiersObject containing raw float prices for all 3 tiers (EVAL_L1, EVAL_L2, EVAL_L5), built by the backend at Step 3e based on is_founder_cohort:- TRUE → fetch the Founder Price column from Zapier Table J for each tier.- FALSE → fetch the Challenge Price column from Zapier Table J for each tier.Frontend reads this object at Step 0 to populate the 3 pricing cards at Step 2. Actual values are managed centrally in Zapier Table J — not hardcoded in backend or frontend.
required_flowCompliance UI variant ID (FLOW_AFLOW_J) the frontend renders at Step 5. Full routing table: see UC_2.1.2.
methodsArray of payment methods available for the visitor'sgeo_country. See the detailed field table below.
is_launch_phase / historical_pass_rateSet in Step 3f. Consumed byUC_2.1.2 Flow B (UK/Australia) and Flow C (EU/EEA) to show the pass-rate disclosure at Step 5.
zip_requirementsObject built server-side from thecountry_zip_requirements table (see schema below) — keyed by country_iso2, value = requires_zip. Frontend stores this map in global checkout state on page load and reads it at Step 5 (UC_2.6.1) to decide whether the ZIP / Postal Code field is required or optional for the selected Country.

country_zip_requirements Table Schema:

ColumnTypeNotes
country_iso2CHAR(2), PRIMARY KEYISO 3166-1 alpha-2 country code.
requires_zipBOOLEAN, NOT NULLTRUE = ZIP / Postal Code is required for this country at Step 5. FALSE = optional.

Seed data source: The initial dataset for this table can be sourced from standard ISO 3166-1 postal code requirement datasets publicly available on GitHub or via services such as GeoNames, rather than manually compiled per country.

methods[] detail — rendered at Step 6 (Payment Method selection):

FieldTypeMeaningWhere rendered
idstring=method_id in the DB. Enum: CC, CRYPTO, APPLE_PAY, GOOGLE_PAY, DUSUPAY, T365.Selects the execution environment (NMI Collect.js / Triple-A modal / hosted-page redirect).
labelstringDisplay name (e.g. "Credit or Debit Card").Next to the radio button, Step 6.
explanatory_textstringDescriptive copy (fees, conditions).Info text block below the payment list, Step 6.
cta_textstringAction button text (e.g. "Complete Purchase"). Max 25 characters.Gold action button, Step 6.
icon_tags[string]Icon keys (e.g."pix", "gcash") mapped to SVG assets. Aggregator corporate logos (Dusupay, T365) are specifically excluded — only the label text is shown.Inline with the label, Step 6.

5. Basic Flow

  1. User navigates to the checkout page.
  2. Frontend sends GET /system/status (Cloudflare headers CF-IPCountry, CF-Region are included automatically).
  3. Backend processes, in order:
    • Step 3a — Geo-IP Gate: Extracts geo-IP from CF-IPCountry / CF-Region. Queries Compliance_geo_restrictions.

      • If match → sets geo_blocked = true and returns the payload immediately (skips Steps 3b–3e). Frontend then renders Gate 2 (UC_2.1.4) and stops — no further checkout UI is rendered.
      • If no match → continues to Step 3b.

      Table: Compliance_geo_restrictions

      ColumnTypeNotes
      idUUID (PK)
      countryVARCHARISO 3166-1 alpha-2 or standard string
      regionVARCHAR'All' = entire country blockedspecific subdivision = only that region blocked.Query logic: WHERE country = CF-IPCountry AND (region = 'All' OR region = CF-Region) — either row matching triggers the block.
      banned_reasonVARCHARRegulatory framework or policy justification
      created_atTIMESTAMP
      updated_atTIMESTAMP
    • Step 3b - Waitlist Gate: Reads Global_Var_Allow_New_Signups from Zapier Table C.

      • If Global_Var_Allow_New_Signups == FALSE -> backend returns the payload immediately (skips Steps 3c-3f). Frontend redirects (client-side navigation, same tab) to the dedicated Waitlist page (UC_2.1.3). Checkout UI is unmounted — no further checkout UI rendered.
      • If Global_Var_Allow_New_Signups == TRUE -> continues to Step 3c.
    • Step 3c — UI Routing: Queries checkout_ui_routing for a row matching geo_country (+ geo_region where applicable).

      • If match → sets required_flow to that row's value (e.g. FLOW_B, FLOW_C).
      • If no match → defaults required_flow = 'FLOW_A' (Override Model — see UC_2.1.2 §3 Routing Table).
      • Table: checkout_ui_routing
      ColumnTypeNotes
      idUUID (PK)
      countryVARCHARISO 3166-1 standard name
      regionVARCHAR'All' = applies to entire country (default)specific subdivision = overrides the 'All' row for that region only.Match logic (in order):(1) row where country = user_country AND region = user_region → use that required_flow;(2) else row where country = user_country AND region = 'All' → use that required_flow;(3) else → default FLOW_A.
      required_flowVARCHARe.g.'Flow B', 'Flow C'
      justificationVARCHARInternal reference
      created_atTIMESTAMP
      updated_atTIMESTAMP
    • Step 3d — Gateway Filtering & Localization: Queries Payment_Method_Config twice and merges results into one methods array:

      • Rows where target_country = 'GLOBAL' AND is_active = TRUE → payment methods offered to every country (CC, Apple Pay, Google Pay, Crypto). GLOBAL is a literal value stored in the table, not a computed default.
      • Rows where target_country = geo_country AND is_active = TRUE → country-specific rails (e.g. target_country = 'ZA' → Dusupay Mobile Money/Bank).
      • Backend exclusion rule: if geo_country == 'IN', remove CC, APPLE_PAY, and GOOGLE_PAY from the merged array. India users receive only Crypto (Triple-A).

      Table: Payment_Method_Config

      ColumnTypeNotes
      idUUID (PK)
      target_countryString'BR', 'CO', 'MX', 'PH', 'SG', 'JP', 'IN', 'ZA', 'KE', 'GB', 'FR', 'PL', 'NL', 'GLOBAL', 'AE'
      method_idString'CC', 'CRYPTO', 'APPLE_PAY', 'GOOGLE_PAY', 'DUSUPAY', 'T365'
      gateway_routerString'NMI', 'TRIPLE_A', 'DUSUPAY', 'T365'
      labelString
      explanatory_textString
      cta_textStringAction button text (e.g.'Complete Purchase'). Max 25 characters — enforced at both DB constraint level (VARCHAR(25)) and API validation.
      icon_tags[String]Icon keys for frontend asset mapping
      is_activeBooleanOps toggle per row
    • Step 3e — Cohort & Pricing Check: Fetches is_founder_cohort (= Global_Var_Founder_Cohort_Open) from Zapier Table C — pricing switch only:

      • is_founder_cohort == TRUE → build pricing_tiers from the Founder Price column (Table J) for EVAL_L1, EVAL_L2, EVAL_L5.
      • is_founder_cohort == FALSE → build pricing_tiers from the Challenge Price column (Table J) for EVAL_L1, EVAL_L2, EVAL_L5.

      Table: Zapier Table C (Global Variables) — flat key-value config, not a row-based DB table.

      VariableValueDescription
      Global_Var_Allow_New_SignupsbooleanMaster Throttling Switch — read atStep 3b (Waitlist Gate).TRUE → checkout proceeds.FALSE → backend returns immediately. User is redirected to the dedicated Waitlist page.
      Global_Var_Founder_Cohort_OpenbooleanMaster Cohort switch — read atStep 3e.TRUE → Founder pricing.FALSE → Standard pricing.
    • Step 3f — Launch Phase & Pass Rate (consumed by UC_2.1.2 Flow B and Flow C):- Reads system_launch_date from Platform_Configuration (row id = 1).

      • Sets is_launch_phase = TRUE if CurrentDate <= system_launch_date + 90 days, else FALSE.
      • Reads the cached_pass_rate column and returns it in the response as historical_pass_rate (rename only, same value — no re-calculation here; the value is pre-computed daily, see note below).
      • Used by Flow B (UK/Australia) and Flow C (EU/EEA) for the pass-rate disclosure at Step 5 — all other flows ignore them.

      Two separate mechanisms are involved:

      • Mechanism 1 — Zapier Flow 29 (write-only): runs daily at 5:05 PM EST (5 min after market close) and fires a webhook to POST /financial/daily-snap. Each run counts the number of users who transitioned to 'PASSED' or 'FAILED' status that day, and appends one new row to the Firm_Sim_Funnel_Snapshots table (columns sims_passed, sims_failed) - see schema below. Existing rows are never updated or deleted (append-only historical ledger). This mechanism never touches Platform_Configuration.
      • Mechanism 2 — Backend cron "Pass Rate Aggregator" (query corrected): a separate Node.js cron job, still required in the backend. Corrected formula: cached_pass_rate = SUM(sims_passed) / (SUM(sims_passed) + SUM(sims_failed)), computed across all rows of Firm_Sim_Funnel_Snapshots. Writes the resulting float back to Platform_Configuration (row id = 1, column cached_pass_rate). This avoids recalculating the ratio on every /system/status call.

      Table: Platform_Configuration — single-row global config, avoids aggregate queries on every checkout page load.

      ColumnTypeNotes
      idInteger (PK)Locked to1
      system_launch_dateDate
      cached_pass_rateFloat

      Firm_Sim_Funnel_Snapshots

      ColumnTypeNotes
      idPK
      dayDate, Indexed
      timeTimeInformational — exact timestamp the daily job ran.
      conversions_newIntegerNew sim payments processed that day.
      conversions_resetsIntegerSim reset payments processed that day.
      active_simsIntegerTotal users currently in an active sim state.
      sims_passedIntegerTotal users who successfully cleared the challenge that day.
      sims_failedIntegerTotal users who hit liquidation that day.
      pass_percentDecimalPer-day value:sims_passed / (sims_passed + sims_failed). Not the same as cumulative cached_pass_rate above.
      avg_pass_fail_durationDecimalAverage days elapsed from sim start to pass/fail.
  4. Backend returns JSON response (only reached if Step 3a did not trigger an early return due to geo_blocked = true).
  5. Frontend stores full response in global checkout state.

6. Exceptional Flow

  • [If GET /system/status fails — network error or timeout]

    • Ref: TE-SYS-01. Checkout UI is not rendered. User may retry once network is restored.
  • [If GET /system/status fails — 5xx server error]

    • Ref: FP-03. Full-page error screen replaces checkout UI.
  • [If geo headers are missing, empty, or return "XX"]

    • Backend defaults to geo_country = 'US', required_flow = 'FLOW_A'. User is NOT blocked.
  • [If checkout_ui_routing query returns 0 rows]

    • Backend defaults to required_flow = 'FLOW_A'. User is NOT blocked.

7. Business Rules

BR_2.1.1.1: Single Call Per Session

GET /system/status is called exactly once when the checkout page first loads. Step navigation between Steps 1–5 does NOT trigger a new call. A full page refresh (F5) triggers a new call to re-hydrate global checkout state (pricing, geo, flow routing, etc.) — this does NOT mean the user's in-progress step/data is discarded; see BR_2.1.1.3 for the per-step reload/recovery behavior.

BR_2.1.1.2: Response Persistence

The full API response payload is stored in global checkout state. All subsequent steps read from this cached state — no re-fetching.

BR_2.1.1.3: Checkout Routing & Session Recovery Matrix (All Steps)

URL Structure: The checkout flow utilizes query parameters on a single path to define the current step (e.g. /checkout?step=selectassetclass, /checkout?step=payment). This enables deep-linking and cart-abandonment recovery (Klaviyo) to drop the user at their exact abandonment point.

Table 1 — Trigger: Page Reload (F5)

StepBehavior
Step 1-5 (before Email)Stay on current step + keep data.
Step 5 (after Email onBlur, Guest created)Stay on current step + keep data. Do NOT re-callPOST /capture-lead.
Step 6 (Payment)On reload, the frontend immediately routes to the correct final state based on the backend outcome — no intermediate OV-05 processing overlay is re-rendered on reload:- If payment already succeeded: Frontend routes directly to Step 7 (Claim Account screen). User also receives a confirmation email (Ref: SES-01).- If payment failed (or no payment was attempted): Step 6 renders with the split-panel layout. If email is currently locked (5-Failure Email Lock, Ref: BR_2.8.1.2), Ref: OV-03 renders on top; otherwise normal split-panel, no overlay.Rationale: Payment is a single, continuous backend flow — there is no persistent mid-payment state to resume on reload. Backend idempotency is enforced via provider_event_id on the webhook (Ref: UC_2.8.1 §5 step 6.0). NMI transactions are normally fast (< 30 s), making the F5 window extremely narrow.
Step 7.1 (Claim Account)Stay on claim screen + keep entered phone number. JWT token in URL remains valid if within 48h. If SIM provisioning fails in the background at this point, no indication is shown (Ref:BR_2.8.3.5).
Step 7.3 (Provisioning)Check account status.Active_SIM → redirect to Auth0 login (Ref: UC_2.8.4 §5). Guest → keep showing provisioning/waiting state indefinitely (no frontend timeout). Failure handling is delegated to the backend auto-retry mechanism (Ref: UC_2.8.2 §Step 3 Exception); on exhaustion, overlay OV-07 renders (Ref: UC_2.8.4 §6 Exceptional Flow).

Table 2 — Trigger: URL Link Click (Deep-Link)

StepBehavior
Step 1-5 (before Email)Local storage exists → Resume at exact step. Local storage lost → Restart at Step 1. Email re-entry triggers Duplicate Account Check normally.
Step 5 (after Email onBlur, Guest created)Local storage exists → Resume at exact step. Local storage lost → Restart at Step 1.
Step 6 (Payment)Local storage exists → Resume at Step 6 (same direct-routing logic as Table 1 above applies). Local storage lost → Restart at Step 1.
Step 7.1 (Claim Account)URL has valid JWT → Lands on Step 7.1 with validtransaction_id. URL has no/expired token → Fallback to Step 1.
Step 7.3 (Provisioning)Reached via raw URL without token → Fallback to Step 1 → progress to Step 5 → re-check status.

Table 3 — Trigger: Back from Marketing Page (Exit)

  • If local browser storage still has the in-progress cart state: frontend restores it, and the next click on the [Checkout] button re-opens the checkout overlay directly at the step the user was on when they left — not back at Step 1. If that step is Step 6, the same direct-routing logic as Table 1 applies.
  • If local storage is unavailable (cache cleared, new device): the checkout overlay restarts from Step 1, and the user re-types their email at Step 5 — including fields already captured before (name, billing address, etc.) — Step 5 displays the corresponding message.

BR_2.1.1.4: UTM Capture — Global, Root Layout (per CR-12)

The frontend reads current UTM values (utm_source, utm_medium, utm_campaign, utm_term, utm_content) from localStorage (written by the CR-12 root-layout script, not re-parsed from the URL at Step 0). This applies on every branch — including when the Waitlist Gate (UC_2.1.3, Step 3b) intercepts the flow — so email + UTM can still be attached to the direct API payload sent to ActiveCampaign/Klaviyo.

Attribution rule — last-touch, NOT first-seen wins: If the URL at any page load/route change contains UTM params, the root-layout script overwrites the existing localStorage values (last-touch attribution). If the URL has no UTM params, localStorage is left untouched (not cleared, not nulled).

All downstream consumers read the current localStorage value at the moment of their own action (not a value cached once at Step 0): Step 5 (UC_2.6.1), POST /capture-lead (UC_2.6.2), POST /execute-checkout at Step 6, and the Waitlist direct API calls to ActiveCampaign + Klaviyo (UC_2.1.3).


UC_2.1.2 — Geo-Based Compliance UI Variants (Flow A–J)

1. Overview

FieldContent
IDUC_2.1.2
Use CaseGeo-Based Compliance UI Variants (Flow A–J)
DescriptionBased onrequired_flow from /system/status, the frontend renders flow-specific compliance content (text disclosures, mandatory checkboxes) at Step 5 only. Step 0 itself renders no flow-specific UI.
Zapier Flow
Zapier Table
3rd Party

2. Trigger

required_flow (stored in global checkout state at Step 0) is read when Step 5 renders.

3. Routing Table

Sourced from checkout_ui_routing PostgreSQL table (Override Model: unmatched countries default to Flow A).

FlowCountries / RegionsUI Behavior at Step 5
Flow AUSA + all unmatched countries (default)2 standard checkboxes only
Flow BUK, Australia2 standard checkboxes + pass-rate disclosure (content varies onis_launch_phase)
Flow CEU/EEA (Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Iceland, Ireland, Italy, Latvia, Liechtenstein, Lithuania, Luxembourg, Malta, Netherlands, Norway, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden)2 standard checkboxes + pass-rate disclosure (content varies onis_launch_phase) + 1 EU withdrawal waiver checkbox (3 total)
Flow DCanada — Quebec region onlyEntire checkout UI rendered in French — including all labels, placeholders, button text, error messages, policy text, popups, banners, and toasts triggered from any checkout screen (Steps 1–7). Standard 2 checkboxes (in French).
Flow EUnited Arab Emirates2 standard checkboxes + UAE non-regulation disclaimer
Flow FSanctioned countries (seeCompliance_geo_restrictions table)Hard block — Gate 2 (UC_2.1.4). Step 5 is never reached
Flow GIndia2 standard checkboxes.
Flow HPhilippines2 standard checkboxes only.
Flow IBrazil2 standard checkboxes only.
Flow JSouth Africa2 standard checkboxes only.

4. Standard Compliance Checkboxes (All Flows Except F)

Checkbox 1 — Commercial Acknowledgment:

"I acknowledge that I am purchasing a skills assessment software evaluation for commercial purposes to secure an independent contractor agreement with a US-domiciled C-Corporation, and I am not opening a retail financial, brokerage, or investment account."

Checkbox 2 — Age & Terms of Service:

"By clicking 'Complete Purchase', I confirm that I am at least 18 years of age and agree to the Terms of Service for the Data Processing and Performance Evaluation Service (Associate Track)."

Both: mandatory, default unchecked, displayed at Step 5 only.

Checkbox 2 — "Terms of Service" Click Behavior: The words "Terms of Service" inside the Checkbox 2 label are a clickable hyperlink. Clicking it opens a popup/modal (does not navigate away from Step 5, does not close/reset the checkout flow) displaying the Terms of Service content. Content is identical to the public /terms page (Ref: UC_29) — same legal text, same numbered sections, same "Last Updated" timestamp. This supersedes the prior distinct-document framing in UC_29 BR-29-03/A-10 (see changelog note there). Closing the popup (via close button or overlay click) returns the user to Step 5 with checkbox state and all other form data unchanged.

5. Flow B — Pass-Rate Disclosure (UK & Australia)

Static text block at Step 5, above standard checkboxes. Same text for UK and Australia.

  • If is_launch_phase == TRUE:

    "This is a newly launched proprietary trading evaluation program. Historical pass-rate and success data is currently unavailable."

  • If is_launch_phase == FALSE:

    "Historically, only [historical_pass_rate]% of participants successfully pass the evaluation to become authorized traders."

6. Flow C — EU/EEA Additional Checkbox

Checkbox 3 — EU 14-Day Withdrawal Waiver:

"I expressly consent to the immediate commencement of the digital evaluation service and waive my 14-day right of withdrawal under EU consumer protection law."

Default: unchecked. Mandatory. Positioned between standard checkboxes and the Next button.

7. Flow D — Quebec Localization

Entire checkout UI (Steps 1–7) renders in French. All labels, placeholders, button text, and ad copy within the checkout are localized to French.

8. Flow E — UAE Disclaimer

Static text block at Step 5, above standard checkboxes:

"Stack Trading is a U.S.-domiciled entity and is not licensed, registered, or regulated by the Dubai Financial Services Authority (DFSA) or the Abu Dhabi Global Market (ADGM)."

9. Hypothetical Performance Disclaimer (All Flows)

Mandatory legally-required static text block at Step 5 for all flows:

"HYPOTHETICAL OR SIMULATED PERFORMANCE RESULTS HAVE CERTAIN LIMITATIONS. UNLIKE AN ACTUAL PERFORMANCE RECORD, SIMULATED RESULTS DO NOT REPRESENT ACTUAL TRADING. ALSO, SINCE THE TRADES HAVE NOT ACTUALLY BEEN EXECUTED, THE RESULTS MAY HAVE UNDER-OR-OVER COMPENSATED FOR THE IMPACT, IF ANY, OF CERTAIN MARKET FACTORS, SUCH AS LACK OF LIQUIDITY. SIMULATED TRADING PROGRAMS IN GENERAL ARE ALSO SUBJECT TO THE FACT THAT THEY ARE DESIGNED WITH THE BENEFIT OF HINDSIGHT. NO REPRESENTATION IS BEING MADE THAT ANY ACCOUNT WILL OR IS LIKELY TO ACHIEVE PROFITS OR LOSSES SIMILAR TO THOSE SHOWN."

Static, always visible, non-collapsible. No user interaction.

7. Business Rules

BR_2.1.2.1: Flow-to-UI Mapping is Frontend-Hardcoded

The mapping of required_flow → specific UI content at Step 5 is hardcoded in the frontend. FE reads required_flow from global checkout state and renders the corresponding variant via a fixed enum switch. Changing what a flow displays requires a frontend code change. See §3 Routing Table for the full mapping.

BR_2.1.2.2: Country-to-Flow Assignment is Dynamic (DB-Driven)

Which country maps to which flow is managed via the checkout_ui_routing PostgreSQL table — configurable by Ops without a code deploy. Adding a new country rule only requires inserting a row into this table; it does not require a FE change.


UC_2.1.3 — Gate 1: Waitlist

1. Overview

FieldContent
IDUC_2.1.3
Use CaseGate 1 — Waitlist
DescriptionWhenGlobal_Var_Allow_New_Signups == FALSE in the /system/status response, the user is redirected (client-side navigation, same tab) to a dedicated Waitlist page — rendered with the standard Marketing Header and Footer, replacing the prior overlay-on-checkout-page design. The user selects their primary market and enters email to join the waitlist.
Zapier Flow
Zapier TableTable C (Global_Var_Allow_New_Signups flag)
3rd PartyActiveCampaign + Klaviyo (frontend makes direct, parallel API calls to BOTH, sending captured email + UTM parameters — no Zapier orchestration for either).

References/Wireframe/Stage 1/Checkout Flow - Waitlist.png Waitlist — empty state References/Wireframe/Stage 1/Join waitlist (choosing asset).png Waitlist — dropdown open References/Wireframe/Stage 1/Join waitlist (Processing).png Waitlist — submitting References/Wireframe/Stage 1/Join waitlist (succeed).png Waitlist — success state References/Wireframe/Stage 1/Join waitlist - Invalid email.png Waitlist — validation error References/Wireframe/Stage 1/Checkout Flow - Fail due to error.png Waitlist — CRM API submit failed

2. Trigger

Global_Var_Allow_New_Signups == FALSE detected in the GET /system/status response.

3. Pre-conditions

  • User is on the checkout page.
  • GET /system/status returns Global_Var_Allow_New_Signups == FALSE.

4. Post-conditions

  • Frontend makes direct API calls to BOTH Klaviyo and ActiveCampaign, sending the captured email and UTM parameters (read from localStorage at submit time, per CR-12 / BR_2.1.1.4) to each — no Zapier orchestration for either call.
  • Page content replaced by success state. Ref: OV-04.
  • Scope boundary: System responsibility ends at firing both direct API calls. Klaviyo automatically triggers the Waitlist_Welcome drip (Ref: KLA-22) — out of system; the marketing team manages that sequence's content/cadence. ActiveCampaign creates the CRM record and attaches identity to behavioral tracking — it does not send automated emails.

5. Basic Flow

  1. GET /system/status returns Global_Var_Allow_New_Signups == FALSE.
  2. Frontend redirects (client-side navigation, same tab) to the dedicated Waitlist page. The standard Marketing Header and Footer render on this page.
  3. User selects primary market (Futures or Forex).
  4. User enters email.
  5. User clicks [Join Waitlist].
  6. Button switches to "Processing..." (disabled). Frontend reads current UTM values from localStorage (per CR-12) and makes direct API calls to BOTH Klaviyo and ActiveCampaign, sending lead data (primary market + email) and those UTM parameters to each.
  7. On success → page content replaced by success state. Ref: OV-04.
  8. User clicks [Return to Homepage] → navigated to homepage.

6. Exceptional Flow

  • [If CRM API fails on submit]

    • Ref: BN-02. [Join Waitlist] button remains available for retry.
    • Form data is NOT cleared.

7. Business Rules

BR_2.1.3.1: Global Scope

Global_Var_Allow_New_Signups == FALSE is a global toggle — redirects ALL countries to the Waitlist page simultaneously.

BR_2.1.3.2: No Interrupt Logic

If Global_Var_Allow_New_Signups changes to FALSE while a user is mid-checkout (started when Global_Var_Allow_New_Signups == TRUE), the user completes the ENTIRE checkout flow without interruption. Frontend does NOT re-check after initial page load.

BR_2.1.3.3: CRM-Side Deduplication (No Information Leakage)

No custom backend check or database query is built to verify waitlist status. ActiveCampaign and Klaviyo handle identity resolution and deduplication natively. If the submitted email already exists, the CRM silently deduplicates or updates the record — the frontend still transitions to the success UI state (Ref: OV-04) regardless, to prevent information leakage (i.e., never reveal whether an email is already registered). This applies identically to both the ActiveCampaign and Klaviyo direct API calls — both receive the same email + UTM payload.

8. Screen Description

Page Content Header: (rendered below the standard Marketing Header — Ref: UC_1.1.1)

ComponentContent
Title"Be First on the Waitlist"
Body"Enrollment is currently paused. Join the waitlist to secure your spot and you will be notified when we re-open."

Form Fields:

No.Field NameField TypeValidation Rule / Behaviour
1Primary MarketDropdown (Single-selection)Display rule:- Placeholder: "Select primary market".- Options: Futures, Forex.Validation:- Required. Ref: CR-03.
2EmailTextboxDisplay rule:- Placeholder: "Enter email".Validation:- Required. Ref: CR-09 §9.1.
3Join WaitlistButton (Primary)Behaviour:- On click: validates all fields. If invalid → show inline errors, do NOT submit.- If valid → button switches to "Processing..." (disabled). Frontend reads UTM from localStorage (per CR-12) and makes direct API calls to BOTH Klaviyo and ActiveCampaign, sending the captured email and those UTM parameters.- CRM API fail → button reverts to "Join Waitlist" (enabled). Ref: BN-02. Form data preserved.- CRM API success → Ref: OV-04.

UC_2.1.4 — Gate 2: Geoblock

1. Overview

FieldContent
IDUC_2.1.4
Use CaseGate 2 — Geoblock (Flow F)
DescriptionWhen GET /system/status returns HTTP 403 (user is in a sanctioned region), the entire checkout UI is replaced by a hard-stop block page. The user cannot proceed.
Zapier Flow
Zapier Table— (source:Compliance_geo_restrictions PostgreSQL table)
3rd PartyCloudflare (geo-IP detection)

References/Wireframe/Stage 1/Flow F Gate 2 - Geoblock (Absolute).png Gate 2 — Geoblock full-page block

2. Trigger

GET /system/status returns HTTP 403 — geo_blocked == true because CF-IPCountry/CF-Region matches a row in Compliance_geo_restrictions.

3. Business Rules

BR_2.1.4.1: Hard Stop — Full Page Replacement

HTTP 403 = absolute hard stop. The geoblock state replaces the ENTIRE checkout UI — no header, nav, footer, step indicators, or appeal link. Only the block message is shown (message content includes a support contact email per FP-02).

4. Screen Description

Full-page 403 block — checkout UI fully replaced. Screen content defined at Ref: FP-02.


UC_2.1.5 — Pricing Engine

1. Overview

FieldContent
IDUC_2.1.5
Use CasePricing Engine
DescriptionFrontend parses the /system/status response to dynamically populate pricing at Step 2 for all 3 Evaluation Package tiers, applying Founder or Standard pricing based on cohort status.
Zapier Flow
Zapier TableTable C:Global_Var_Founder_Cohort_OpenTable J: Founder Prices, Standard Challenge Prices per tier
3rd Party

2. Business Rules

BR_2.1.5.1: Founder Cohort Detection

Frontend reads is_founder_cohort from the /system/status response:

  • TRUE → display prices from the Founder Price column (Table J) for each tier.
  • FALSE → display prices from the Challenge Price column (Table J) for each tier.

Prices are not hardcoded in the frontend — always read from the pricing_tiers object in the /system/status response. Actual values are managed centrally in Zapier Table J.

BR_2.1.5.2: Race Condition — Stale Pricing

Triggered at Step 6 (Pay click) when the server-side price/tax re-check (Ref: UC_2.8.1 §5 step 6.1.a) detects either of the following:

  • Founder cohort sold out: Founder cohort slot filled up between page load and Pay click, reverting to Standard pricing.
  • Tax rate changed: Tax rate changed server-side between POST /calculate-cart (Step 5) and POST /execute-checkout (Step 6), causing the total to differ.

In either case, the backend returns PRICE_CHANGED — no charge is created, no promo reservation occurs. Frontend dismisses Ref: OV-05 and surfaces Ref: OV-08.

[Refresh now] behavior: Clicking [Refresh now] on OV-08 closes the overlay and refreshes the Order Summary with the updated price/tax. No full page reload. The user may then click [Pay] again with the updated total.

BR_2.1.5.3: Price Data Source

Founder Prices and Standard Challenge Prices for all 3 tiers are stored in Zapier Table J. Backend reads the appropriate column and packs the values into the pricing_tiers object in the /system/status response at Step 3e. Frontend renders the 3 pricing cards at Step 2 from this object — no additional API call required.

(Source: Zapier Integration V7.pdf §3.7)

TrackChallenge PriceFutures Reset PriceForex Reset PriceExtension FeeFounder PriceFounder Reset Fee
Associate Track (Level 1)$650$375$325$150$499$325
Accelerated Track (Level 2)$1,250$725$625$275$1,049$600
Advanced Track (Level 5)$7,000$3,800$3,500$1,500$5,599$3,250

Note: Data above reflects current Ops configuration snapshot (source: Zapier Table J). All pricing values may be changed by Ops at any time without a code deploy.


Step 1: Asset Class Selection

UC_2.2 — Asset Class Selection

1. Overview

FieldContent
IDUC_2.2
Use CaseStep 1: Asset Class Selection
DescriptionUser selects their preferred trading asset class —Futures (via CME) or Forex (Currency pairs).
Zapier Flow
Zapier Table
3rd Party

References/Wireframe/Stage 1/Checkout Flow - Step 1.png Asset Class Selection

2. Trigger

User passes Gate 1 and Gate 2 at Step 0.

3. Pre-conditions

  • Global_Var_Allow_New_Signups == TRUE
  • geo_blocked == FALSE
  • /system/status response stored in global checkout state.

4. Post-conditions

  • asset_class stored in session state.
  • User proceeds to Step 2.

5. Basic Flow

  1. Step 1 renders with 2 large selection cards: Futures and Forex.
  2. User clicks one card.
  3. Selection stored in session state.
  4. User clicks [Next] to proceed.

6. Business Rules

BR_2.2.1: Fixed Options

2 asset class options (Futures, Forex) are fixed in the frontend — not driven by API. Both always displayed.

BR_2.2.2: Session Persistence

Asset class selection is stored in session state for the duration of the checkout flow. Persistence and reload/recovery behavior across a full page refresh is governed centrally by BR_2.1.1.3 (Step 0) — a refresh does NOT return the user to Step 1 as long as local storage cart state is present; see that rule for the exact per-step matrix.

BR_2.2.3: Asset Class Change — Progress Bar & Step Count Impact

Progress bar step count is dynamic — total number of steps differs by asset class:

  • Futures: 7 steps — Step 1 → 2 → 3 → 4 (Market Data) → 5 → 6 → 7
  • Forex: 6 steps — Step 1 → 2 → 3 → 5 → 6 → 7 (Step 4 omitted from flow)

This applies on initial selection at Step 1 and when navigating back to Step 1 to change selection. Progress bar re-renders to reflect the new total step count immediately on selection.

If user navigates back to Step 1 and changes asset class selection:

  • Futures → Forex: Step 3 (Platform) selection is reset. Step 4 (Market Data) is removed from the flow; progress bar updates to show 6 steps total.
  • Forex → Futures: Step 3 (Platform) selection is reset. Step 4 (Market Data) is added back to the flow; progress bar updates to show 7 steps total.

Step 5 (PII & Compliance) data is NOT reset on asset class change. Data is only fully cleared on full page refresh — see BR_2.2.2.

7. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1FuturesRadio GroupDisplay rule:- Title: "Futures"- Subtitle: "via CME"- Default: unselected.Behaviour:- On click: stores asset_class = 'FUTURES'. Deselects Forex if previously selected.- Impact (if switching from Forex): Ref: BR_2.2.3.
2ForexRadio GroupDisplay rule:- Title: "Forex"- Subtitle: "Currency pairs"- Default: unselected.Behaviour:- On click: stores asset_class = 'FOREX'. Deselects Futures if previously selected.- Impact (if switching from Futures): Ref: BR_2.2.3.
3NextButton (Primary)Behaviour:Disabled until one asset class is selected. On click when enabled: navigates to Step 2.

Step 2: Capital Allocation Selection

UC_2.3 — Capital Allocation Selection

1. Overview

FieldContent
IDUC_2.3
Use CaseStep 2: Capital Allocation Selection
DescriptionUser selects one of three Evaluation Package tiers. Each tier has distinct pricing, notional capital, career ladder entry, and live stop loss parameters.
Zapier Flow
Zapier TableTable C:daily_loss_ratioTable J: Founder pricing, Standard pricing per tier
3rd Party

References/Wireframe/Stage 1/Step 2 future standard price.png Capital Allocation — Futures, Standard price References/Wireframe/Stage 1/Step 2 future founder price.png Capital Allocation — Futures, Founder price References/Wireframe/Stage 1/Step 2 forex standard price.png Capital Allocation — Forex, Standard price References/Wireframe/Stage 1/Step 2 forex founder price.png Capital Allocation — Forex, Founder price References/Wireframe/Stage 1/Step 2 tooltip.png Capital Allocation — Live Stop Loss tooltip

2. Trigger

User clicks [Next] at Step 1 with an asset class selected.

3. Pre-conditions

  • asset_class in session state.
  • /system/status pricing data in global checkout state.

4. Post-conditions

  • product_id (EVAL_L1, EVAL_L2, or EVAL_L5) stored in session state.
  • User proceeds to Step 3.

5. Evaluation Package Data

ParameterAdvanced Associate TrackAccelerated Associate TrackAssociate TrackSource (Zapier / Doc)
product_idEVAL_L5EVAL_L2EVAL_L1Fixed (no Zapier table to config)
Standard Price (Challenge Price)$7,000$1,250$650Table J (Challenge Price)
Founder Price$5,599$1,049$499Table J (Founder Price)
Status RibbonRecommended for ProsBest ValueFoundationFixed (no Zapier table to config)
Eval Requirement — Futures7 ES16 Micros8 MicrosTable B (Futures Configuration Matrix, Max_Contracts_ES_Equiv)
Eval Requirement — Forex$150,000 Notional$50,000 Notional$25,000 NotionalTable A (Forex Configuration Matrix, Max_Forex_Notional)
Evaluation Target / Stop14% Target / 7.5% Stop (Dynamic), 60 days (Fixed)14% Target / 7.5% Stop (Dynamic), 60 days (Fixed)14% Target / 7.5% Stop (Dynamic), 60 days (Fixed)Table C (SIM_target_percent / SIM_stop_percent) [CHR-49] — 60-day window is a separate hardcoded literal (Today + 60 Days), no Table C field
Career Entry LevelLevel 5Level 2Level 1Fixed (no Zapier table to config)
Distance to W21 Promotion Away4 Promotions Away5 Promotions AwayFixed (= 6 − Career Entry Levelcareer ladder has 6 levels total; Level 6 = W2/international retainer eligibility)
Live Capital Allocation — Futures7 ES16 Micros8 MicrosTable B (Futures Configuration Matrix, Max_Contracts_ES_Equiv)
Live Capital Allocation — Forex$150,000 Notional$50,000 Notional$25,000 NotionalTable A (Forex Configuration Matrix, Max_Forex_Notional)
Live Stop Loss$10,500$2,500$1,250Table A/B (Market_Loss_When_Stopped)
Live Profit Target$14,100$3,750$1,875Table A/B (Profit_Target)

Values above are a current snapshot — Tables A, B, C, and J are Ops-editable without a code deploy; frontend/backend must fetch live from /system/status, not hardcode.

Daily Loss Limit (Daily_Loss_Ratio × max_drawdown) is a backend/ops metric — it is not displayed in the Step 2 checkout UI. Daily_Loss_Ratio is read from Table C at runtime.

6. Business Rules

BR_2.3.1: Pricing Mode — Standard vs Founder

Frontend reads is_founder_cohort from /system/status.

  • TRUEFounder mode: each card renders the original Standard Price with strikethrough (~~$7,000~~) followed by the Founder Price at full size ($5,599). The "one-time" label is NOT shown.
  • FALSEStandard mode: each card renders only the Standard Price followed by the "one-time" label (e.g., $7,000 one-time). No strikethrough.

BR_2.3.2: Card Selection

Clicking a card body to select that tier. Only one card can be selected at a time; selecting a new card deselects the previous one. The card's selected state is reflected visually on the card.

The [Next] button at the bottom of the page is a separate navigation control — it is disabled until a card is selected (see §7 Screen Description).

7. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Pricing Card (×3) — Top sectionRadio Group (Single-selection)Display rule:- 3 cards, left-to-right: Advanced Associate Track · Accelerated Associate Track · Associate Track.- Each card displays in order: Status Ribbon, Track name, Price block, Evaluation Requirements box, [Select Track] button.- Price block (Standard modeis_founder_cohort == FALSE): Standard Price + "one-time" label.- Price block (Founder modeis_founder_cohort == TRUE): Original Standard Price rendered with strikethrough + Founder Price at full size. No "one-time" label.- Evaluation Requirements box: Futures asset class → Eval Requirement — Futures from §5 (contract units). Forex asset class → Eval Requirement — Forex from §5 (Notional USD). Row 2: "{SIM_target_percent}% Target / {SIM_stop_percent}% Stop, 60 days" — target/stop % read live from Table C (current: 14%/7.5%), rendered dynamically not as a static string; 60 days remains a fixed literal. Same for both asset classes. [CHR-49]- No card pre-selected by default. Tier → product_id: Advanced Associate Track = EVAL_L5 · Accelerated Associate Track = EVAL_L2 · Associate Track = EVAL_L1.Behaviour:- On click anywhere on the card (including [Select Track] button): selects that tier, stores product_id in session state. Deselects previously selected card. Ref: BR_2.3.2.
2Pricing Card (×3) — Bottom sectionStatic DisplayDisplay rule:- Always visible below the [Select Track] button on each card. Not collapsible.- Header: "Live Account & Career Path (Upon Passing)".- Fields displayed (top to bottom):  - Career Ladder Entry Upon Passing  - Distance to W2 (or international retainer) Offer: 1 / 4 / 5 Promotions Away  - Live Capital Allocation: Futures → contract units; Forex → Notional USD  - Live Stop Loss — accompanied by "Firm takes 100% of the risk" and a tooltip icon ⓘ (see Row 3)  - Live Profit Target 
3Live Stop Loss — Tooltip ⓘTooltipDisplay rule:- Icon ⓘ appears inline next to the Live Stop Loss label.- Behaviour:On hover  → shows tooltip: "If you pass the evaluation, the firm backs your account with this exact amount of real capital at risk. We absorb the losses so you can focus on execution."
4[Select Track]Button (Secondary)Behaviour:Per-card selection button. Clicking selects the card (equivalent to clicking the card body). Ref:BR_2.3.2.
5[Back]Button (Secondary)Behaviour:On click → navigates back to Step 1.
6[Next]Button (Primary)Validation:Required. Disabled until one card is selected.Behaviour:On click when enabled → navigates to Step 3.

Step 3: Platform Selection

UC_2.4 — Platform Selection

1. Overview

FieldContent
IDUC_2.4
Use CaseStep 3: Platform Selection
DescriptionUser selects their preferred trading platform from a dynamically loaded list filtered by the asset class chosen at Step 1.
Zapier Flow
Zapier TableTable I (Platform Registry — maps platform names to gateway routing)
3rd Party

References/Wireframe/Stage 1/Checkout Flow - Step 3 (Future).png Platform Selection — Futures References/Wireframe/Stage 1/Checkout Flow - Step 3 (Forex).png Platform Selection — Forex References/Wireframe/Stage 1/Checkout Flow - Step 3 - No platforms available.png Platform Selection — No platforms available

2. Trigger

User clicks [Next] at Step 2 with a package selected.

3. Pre-conditions

  • asset_class in session state.

4. Post-conditions

  • platform stored in session state.
  • User proceeds to Step 4 (Futures) or Step 5 (Forex — Step 4 skipped).

5. Platform Registry (Zapier Table I)

Platform availability is controlled by the Is_Active flag in Zapier Table I. Ops can enable or disable any platform at any time without a code deploy.

Platform_NameAsset_ClassGateway_RouterIs_ActiveRisk_Group_Template
TradeSeaFuturesRithmicTRUESim_Rithmic_Default
QuantowerFuturesRithmicTRUESim_Rithmic_Default
ATASFuturesRithmicTRUESim_Rithmic_Default
MotiveWaveFuturesRithmicTRUESim_Rithmic_Default
Sierra ChartFuturesRithmicTRUESim_Rithmic_Default
MetaTrader 5ForexMT5TRUESim_MT5_Default
TradingViewForexTraderEvolutionTRUESim_TV_Default

Note: Data above reflects current Ops configuration snapshot (source: Zapier Table I). Is_Active and Risk_Group_Template may be changed by Ops at any time without a code deploy — frontend must fetch this table dynamically (via GET /public/platform-options), not hardcode it. TradeSea replaces NinjaTrader 8, same asset class and gateway — source table snapshot still lists NinjaTrader 8, superseded by CR.

6. Basic Flow

  1. Step 3 renders.
  2. Frontend calls GET /public/platform-options?asset_class=[asset_class].
  3. Platform options render as logo-driven selection tiles.
  4. User clicks a tile → stored in session state.
  5. User clicks [Next].

7. Exceptional Flow

  • [If GET /public/platform-options returns empty array]

  • [If GET /public/platform-options returns HTTP 500 or network timeout]

    • Full-page error: FP-03.

8. Business Rules

BR_2.4.1: Dynamic List — No Hardcoding

Platform options fetched from GET /public/platform-options?asset_class=[asset_class].

Response structure returns only an array of Platform_Name strings (e.g. { "platforms": ["TradeSea", "Quantower"] }) — no logo/icon field exists in the response schema.

Platform logos are static frontend assets; the frontend is responsible for mapping each returned Platform_Name string to its corresponding local logo asset (no backend-provided icon URL).

BR_2.4.2: Pre-selection with 1 Result

1 platform returned → auto pre-select (highlighted). User must still click [Next] — no auto-advance.

BR_2.4.3: Navigation — Back from Later Steps

Changing platform by navigating back from Step 5 → Market Data (Step 4) is NOT reset (independent). Step 5 data is NOT reset.

9. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Platform TilesRadio Group (Single-selection)Display rule:- Logo-driven selection tiles.- API returns only Platform_Name strings (no logo field in response) — frontend maps each Platform_Name to a local static logo asset.- Default: unselected (or pre-selected if only 1 option returned, per BR_2.4.2).- Ref: BR_2.4.1.Behaviour:- On click: selects tile, deselects previous. Stores platform = [Platform_Name].
2[Back]Button (Secondary)Behaviour:On click → navigates back to Step 2.
3[Next]Button (Primary)Behaviour:Disabled until a platform tile is selected. On click when enabled: navigates to Step 4 (Futures) or Step 5 (Forex).

Step 4: Market Data Selection

UC_2.5 — Market Data Selection (Futures Only)

1. Overview

FieldContent
IDUC_2.5
Use CaseStep 4: Market Data Selection
DescriptionFutures users select market data feed subscriptions. CME is pre-selected and locked. NYMEX, CBOT, and COMEX are optional. Step is skipped for Forex users.
Zapier Flow
Zapier TableTable C (Global Variables) — product list and per-feed prices are read from Table C, not hardcoded. (Source: RFQ_ Stack Trading Prop Tech V7.pdf §3.2, Market Data Products API)
3rd Party

References/Wireframe/Stage 1/Checkout Flow - Step 4.png Market Data Selection (Futures only)

2. Trigger

User clicks [Next] at Step 3 AND asset_class == 'FUTURES'.

3. Pre-conditions

  • asset_class == 'FUTURES'
  • GET /public/market-data-products returns a non-empty array.

4. Post-conditions

  • addon_ids[] array stored in session state.
  • User proceeds to Step 5.

5. Basic Flow

  1. Step 4 renders.
  2. Frontend calls GET /public/market-data-products. Backend fetches the product list and per-feed prices from Zapier Table C (Global Variables) — prices are never hardcoded in backend code (see BR_2.5.5).
  3. Renders selection grid of toggle switches.
  4. CME: pre-checked and locked (cannot be toggled OFF).
  5. User toggles optional feeds (NYMEX, CBOT, COMEX) as desired.
  6. All selected feeds display: "Cost: $0.00 (Covered by Stack Trading)".
  7. User clicks [Next].

6. Exceptional Flow

  • [If GET /public/market-data-products returns 0 products]
    • Full-page error: FP-03. Treated as system error.

7. Business Rules

BR_2.5.1: CME Locked ON

CME is pre-checked and locked (locked ON) by default — cannot be unchecked by the user.

BR_2.5.2: Optional Feed Defaults

NYMEX, CBOT, COMEX default to unchecked (OFF). User actively toggles to ON.

BR_2.5.3: Cost Display

All selected options display "Cost: $0.00 (Covered by Stack Trading)". Downstream billing (Level 1–2 pay; Level 3+ reimbursed) is handled by Zapier Flow 22 (Monthly Data Fee Billing) — not surfaced here. (Source: Zapier Integration V7.pdf, Flow 22)

BR_2.5.4: Navigation — Back from Later Steps

If the user proceeds past Step 4 and later navigates back to Step 4 (e.g. Back button, or editing a prior step), the previously selected addon_ids[] toggle states are preserved and re-rendered as-is (CME still locked ON; any optional feeds the user had toggled ON remain ON). Selections are not reset to default.

BR_2.5.5: Product & Price Source — Table C (No Hardcoding)

GET /public/market-data-products MUST read the product list (id, name) and per-feed price (e.g. NYMEX_Bundle) from Zapier Table C (Global Variables) at request time. Backend code must not hardcode product IDs or prices — Table C is the single source of truth so Ops can change pricing without a deploy. This rule also applies to the authenticated dashboard variant GET /market-data-products (see Market Data Management, Dashboard Settings), which reads from the same Table C but additionally filters out feeds the user already owns. (Source: RFQ_ Stack Trading Prop Tech V7.pdf §3.2, Market Data Products / Public Market Data API — "Do not hardcode prices"; client confirmation 2026-07-31.)

8. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1CME FeedToggle/SwitchDisplay rule:- ON label: "CME Level 2". Default: ON (pre-selected as mandatory). "Most Popular" badge shown.- Behaviour:Always ON, non-toggleable. Always included in addon_ids[].
2Optional Feeds (NYMEX / CBOT / COMEX)Toggle/SwitchDisplay rule:- 3 separate toggles.- ON label per feed: "NYMEX" / "CBOT" / "COMEX".- Default: OFF for all.- Cost display: "Cost: $0.00 (Covered by Stack Trading)".Behaviour:- Toggle ON: adds feed to addon_ids[]. Toggle OFF: removes from addon_ids[].
3Market Data Lifecycle DisclosureLabelDisplay rule:- Static text block below feed grid. Always visible. Non-collapsible.- Line 1: "Associate Track Evaluation: The Firm pays 100% of data costs."- Line 2: "Level 1 and 2: The Trader pays (Standard Exchange Professional Data rates apply)."- Line 3: "Level 3: The Trader is fully reimbursed for all Base CME market data costs incurred during Levels 1 and 2."- Line 4: "Level 3 to Level 24: The Firm covers 100% of Base CME data costs."
4[Back]Button (Secondary)Behaviour:On click → navigates back to Step 3.
5[Next]Button (Primary)Behaviour:Always enabled (CME always selected by default). On click: stores finaladdon_ids[] in session state. Navigates to Step 5.

Step 5: PII Capture, Compliance & Cart Abandonment

UC_2.6.1 — PII Capture & Compliance

1. Overview

FieldContent
IDUC_2.6.1
Use CaseStep 5: PII Capture & Compliance
DescriptionUser fills in personal information, flow-dependent compliance checkboxes render, a Sanctions pre-check runs immediately on Country/State selection, and tax calculation runs once on [Next] click (with the Sanctions Gate re-checked server-side). No price/tax display at this step (seeBR_2.6.1.5).
Zapier Flow
Zapier Table
3rd PartyQuaderno (sales tax calculation via POST /calculate-cart)Everflow JavaScript SDK (affiliate click tracking — initialized on page load, storeseverflow_id as first-party cookie)

References/Wireframe/Stage 1/Step 5 Flow A.png PII & Compliance — Flow A (Standard — US & most regions) References/Wireframe/Stage 1/Step 5 Flow B_ Data available.png PII & Compliance — Flow B (UK/AU, pass rate data available) References/Wireframe/Stage 1/Step 5 Flow B_ Launch state (Data unavailable).png PII & Compliance — Flow B (UK/AU, launch state) References/Wireframe/Stage 1/Step 5 Flow C Data available.png PII & Compliance — Flow C (EU/EEA, data available) References/Wireframe/Stage 1/Step 5 Flow C Launch state (Data unavailable).png PII & Compliance — Flow C (EU/EEA, launch state) References/Wireframe/Stage 1/Step 5 Flow D.png PII & Compliance — Flow D (Canada) References/Wireframe/Stage 1/Step 5 Flow E.png PII & Compliance — Flow E (UAE) References/Wireframe/Stage 1/Step 5 Flow G.png PII & Compliance — Flow G (India)

2. Trigger

User clicks [Next] at Step 4 (Futures) or Step 3 (Forex).

3. Pre-conditions

  • Session state: asset_class, product_id, platform, addon_ids[].
    • For the Forex path (Step 4 skipped): addon_ids must be present as an empty array [].
  • required_flow available in global checkout state.

4. Post-conditions

  • All PII data validated.
  • POST /calculate-cart returned HTTP 200 (Sanctions Gate passed).
  • All required compliance checkboxes checked.
  • User can click [Next] to proceed to Step 6.

5. Basic Flow

  1. Step 5 renders with all PII input fields. Flow-dependent compliance UI renders simultaneously per UC_2.1.2 (required_flow).

  2. Current UTM values are read from localStorage (per CR-12 / BR_2.1.1.4) — not re-parsed from the URL here.

  3. User fills all fields. On Email field onBlur → triggers UC_2.6.2 Phase 1 (Capture Lead), independent of the flow below.

  4. User selects Country. State/Province/Region and ZIP / Postal Code fields are all visible on initial Step 5 load. On Country selection, frontend re-evaluates both fields: if the selected country has no subdivisions → hide State/Region; if zip_requirements[billing_country] = false → hide ZIP. Otherwise the field stays visible.

  5. Sanctions pre-check (Ref: BR_2.6.1.1): on Country selection and on State/Region selection, the system immediately checks whether the selected value is a blocked jurisdiction.

    • Blocked → the warning defined in IN-01 displays immediately, and Region/ZIP fields are disabled per BR_2.6.1.9.
    • Not blocked → no warning; user continues filling the form.
  6. User checks all required compliance checkboxes and fills the remaining fields.

  7. User clicks [Next]. Frontend calls POST /calculate-cart (Ref: BR_2.6.1.1):

    • Input: product_id, addon_ids, billing_country, billing_region, user_ip, user_id, promo_code (NULL at Step 5), zip_code (included only when zip_requirements[billing_country] = true; omitted otherwise).

    While this call is in flight, frontend displays the text "Calculating regional taxes..." directly below the State/Region dropdown, and [Next] is disabled (Ref: BR_2.6.1.2).

    Backend processes, in order (5 steps):

    StepNameLogic
    1Sanctions GateFull request payload:product_id, addon_ids, billing_country, billing_region, zip_code (included only when zip_requirements[billing_country] = true; omitted otherwise), user_ip, user_id, promo_code (NULL at Step 5).- Validate billing_country + billing_region against Compliance_geo_restrictions (same table/query logic as UC_2.1.1 §5 Step 3a).- If a match is found where country = billing_country AND (region = 'All' OR region = billing_region) → return HTTP 403 immediately. See IN-01 for the exact user-facing message.- Steps 2–5 are skipped when this returns HTTP 403.
    2Pricing Engine- Ifproduct_id is EVAL_L1/EVAL_L2/EVAL_L5: query Table J (Evaluation and Reset Pricing) for the base price.- If Global_Var_Founder_Cohort_Open == TRUE (Table C), the backend queries the Founder Price column in Table J instead of the Standard Price column — this sets a lower base_price directly (not a discount on top of Standard Price). See UC_2.1.5.- Returning user detection [CHR-38]: if the entered email matches an existing Users record with status IN ('Failed', 'Terminated'), the engine must additionally branch on days_since_failure vs. Discount_Code_Duration_Days (Table C) and is_founder/is_professional to select locked_reset_price / locked_rebuy_price / Retry_Discount-adjusted price instead of full Challenge Price — kept in sync with the Dashboard Failure Modal and Klaviyo email pricing. Not yet fully elaborated in this UC — full branch logic documented in References/CR/2026-08-02_CR38_returning-user-checkout-pricing/CR_summary.md (Ref: [CHR-38]) and in the Failure/Recovery research docs; needs a dedicated BR write-up here before dev handoff.- Otherwise, fetch prices for product_id + addon_ids.- promo_code = NULL at Step 5 → discount_amount = 0.- During the evaluation phase, all addon_ids are $0 ("Covered by Stack Trading").- Math: Final_Amount = (base + addons) - discount_amount 
    3Location Check- Ifbilling_country is missing, query the Users table via user_id to retrieve it.- Not applicable at Step 5billing_country is always supplied by the user's own form input here.- This step only triggers for the authenticated Dashboard reuse of this endpoint (see BR_2.6.1.1).
    4Tax Engine- Ping the third-partyQuaderno API with user_ip, billing_country, billing_region, zip_code (included only when zip_requirements[billing_country] = true; omitted otherwise), and the running amount computed in Step 2 (base_price − discount_amount).- Quaderno checks whether the buyer's jurisdiction has crossed a tax threshold (e.g. EU VAT, APAC GST) or qualifies for a domestic exemption. If not, tax_amount = 0; otherwise Quaderno returns the applicable tax_amount.
    5Return- Return{ base_price, discount_amount, tax_amount, total_price } to the frontend, where total_price = base_price − discount_amount + tax_amount.- At Step 5 this is saved silently to session state (see BR_2.6.1.5) — nothing is rendered.- At Step 6 the same shape populates the Order Summary.
  8. If POST /calculate-cart returns HTTP 200: the "Calculating regional taxes..." text disappears. Triggers UC_2.6.2 Phase 2 (full PII UPSERT), then navigates to Step 6. No price/tax is shown at this step (see BR_2.6.1.5).

6. Exceptional Flow

  • [Selected Country or State/Region is a blocked jurisdiction]

    • The warning defined in IN-01 displays immediately on selection.
    • Placement rules (mutually exclusive):
      • region = 'All' matched (entire country blocked) → warning displays under the Country field; State/Region and ZIP fields are disabled (Ref: BR_2.6.1.9).
      • region = [specific region] matched → warning displays under the State / Province / Region field; ZIP field is disabled (Ref: BR_2.6.1.9).
    • [Next] remains disabled.
    • Warning auto-clears, and disabled fields re-enable, the next time a valid (non-restricted) Country/State combination is selected.
  • [If POST /calculate-cart returns HTTP 5xx]

    • Full-page error: FP-03. Full-page error screen replaces checkout UI.

7. Business Rules

BR_2.6.1.1: Sanctions Pre-Check & Tax Calculation Timing [CHR-47]

Two independent mechanisms — replaces the prior debounced-per-field-settle design entirely:

  1. Sanctions pre-check (immediate): on every Country selection AND every State/Region selection, the system immediately checks whether the selected value is a blocked jurisdiction (same restricted-jurisdiction data as the Sanctions Gate below).
    • Blocked → the IN-01 warning displays immediately. State/Region and/or ZIP fields are disabled per placement rules — Ref: BR_2.6.1.9.
    • Not blocked → no warning; fields stay enabled.
    • Does not apply to Email or Confirm Email.
  2. Tax calculation call on [Next] click: POST /calculate-cart fires only when the user clicks [Next] (Ref: §5 Basic Flow step 7) — not on any field's onChange/onBlur. Email and Confirm Email field changes never trigger this call (Ref: BR_2.6.1.7, unchanged). While the call is in flight, "Calculating regional taxes..." displays and [Next] is disabled (Ref: BR_2.6.1.2). The Sanctions Gate inside this call (Ref: §5 Basic Flow step 7 table, Step 1) remains the authoritative server-side check.

BR_2.6.1.2: Next Button Gate

[Next] is disabled until ALL conditions are simultaneously met:

  1. All required fields have valid data.
  2. No unresolved Sanctions pre-check match is active (Ref: BR_2.6.1.1 mechanism 1) — the currently selected Country/State combination is not a blocked jurisdiction.
  3. All required compliance checkboxes are checked.
  4. Confirm Email is filled and matches the Email field (Ref: BR_2.6.1.7).

On click, if conditions 1–4 above are met, [Next] triggers POST /calculate-cart (Ref: BR_2.6.1.1 mechanism 2) and stays disabled while that call is in flight ("Calculating regional taxes..." showing). Only on HTTP 200 does navigation to Step 6 actually proceed (Ref: §5 Basic Flow step 8).

Everflow first-party cookie & S2S Attribution Fallback:

On Step 5 page initialization, the frontend runs the following two paths in parallel — both are mandatory and always execute on every page load, regardless of SDK status:

  • Path 1 — Everflow SDK: Frontend initializes the Everflow JavaScript SDK (installed via NPM). Using the SDK helper methods, frontend captures the Affiliate ID and Offer ID from the URL parameters, executes the click client-side, and stores the generated transaction_id as a first-party cookie. At Step 6, everflow_id is read from this cookie via getTransactionId() and appended as a hidden payload parameter to POST /execute-checkout.
  • Path 2 — Direct affiliate cookie: Frontend parses the raw affiliate URL parameters (affid, oid, ef_tok) directly from the browser URL and writes them into a first-party cookie named st_affiliate_data. This write does not route through the Everflow SDK and is therefore not blocked by ad blockers.

Everflow SDK failure behavior — S2S Fallback:

If the Everflow SDK fails to load (e.g. blocked by an ad blocker or network error), the failure is silent — Step 5 rendering is NOT blocked. The following S2S fallback sequence applies:

  1. Frontend appends everflow_sdk_blocked: true (boolean flag) to the hidden payload sent to POST /execute-checkout.
  2. Frontend passes the st_affiliate_data cookie content to POST /execute-checkout alongside the other payload parameters.
  3. Backend — On-the-fly Transaction Generation: If the Node.js middleware receives everflow_id = null but st_affiliate_data is present, it invokes Everflow's Server-to-Server (S2S) Click API directly from the server to generate a transaction_id before processing the conversion postback.

BR_2.6.1.4: Data Persistence on Back Navigation

Step 5 data is preserved across back navigation (same general rule as BR_2.2.3). Reload/refresh behavior at Step 5 is governed centrally by BR_2.1.1.3 (Step 0) — data is kept, not cleared, as long as local storage cart state is present.

BR_2.6.1.5: No Price Display at Step 5

Step 5 does not render base_price, tax_amount, total_price, or any other line item from the POST /calculate-cart response. The response is held in session state only; the Order Summary is first rendered at Step 6.

BR_2.6.1.6: ZIP / Postal Code Dynamic Validation

The ZIP / Postal Code field is visible by default on Step 5 load. Its required/optional state and visibility after Country selection are driven entirely by zip_requirements, read from the global checkout state populated by UC_2.1.1 GET /system/status.

On Country onChange: requires_zip = true → field stays visible and is required; requires_zip = false → field is hidden from the UI.

Only a Country change re-evaluates this rule — a State/Region change does not. billing_address and zip_code are both stored in session state and forwarded to the payment gateway at POST /execute-checkout for AVS (Address Verification System) check; there is no secondary billing address support — only the address collected at Step 5 is used for payment verification.

BR_2.6.1.7: Confirm Email — Frontend-Only Match Validation [CHR-18]

The Confirm Email field has no database column and is never included in any API payload (POST /capture-lead, POST /execute-checkout, or any other endpoint).

Sequencing:

  1. Email field onBlur → runs format validation, then Company Email Domain Block (Ref: BR_2.6.1.10) — a blocked domain halts here (Ref: IN-11). Phase 0 (Blacklist Check + Duplicate Account Check, Ref: UC_2.6.2 §5 Phase 0 — Blacklist Check & Duplicate Account Check) only runs if this gate is passed. Inline error displays immediately if any gate fails.
  2. Confirm Email field onBlur → validates format, checks Company Email Domain Block (Ref: BR_2.6.1.10), then compares against Email:
    • 2.1. Match: No error. If Phase 0 has already passed for the current Email value, Phase 1 (POST /capture-lead) fires.
    • 2.2. Invalid format: Ref: IN-07. Phase 1 does not fire.
    • 2.3. Blocked domain: Ref: IN-11. Phase 1 does not fire. Checked independently of the Email field's own result — Ref: BR_2.6.1.10.
    • 2.4. Valid format, not blocked, but mismatch: Ref: IN-05. Phase 1 does not fire.
  3. POST /capture-lead fires only when Phase 0 has passed AND Confirm Email matches (step 2.1).

Editing Email after Confirm Email already matched it:

Confirm Email must be re-validated against the new Email value:

  • If it no longer matches → Ref: IN-05 renders under Confirm Email: "Emails do not match" (same as step 2.3).
  • If it still matches → since the Email value changed, Email must go through Phase 0 again (Ref: UC_2.6.2 §5 Phase 0 — Blacklist Check & Duplicate Account Check):
    • New Email matches an existing DB record → follow the Duplicate Account Check matrix (Ref: UC_2.6.2 §5, Duplicate Record table) — e.g. UPSERT onto the existing Guest record where applicable, or block per the matrix.
    • New Email has no existing record → proceed with the normal Phase 0 → Phase 1 flow (new Guest record).

Re-matching Confirm Email against an Email value that has NOT changed does not re-fire POST /capture-lead — Phase 1 only fires again when the Email value itself has actually changed.

BR_2.6.1.8: Invalid Field Data Display on Reload / Navigation

Applies to the 4 free-text fields at Step 5 with a format or length rule: Full Name, Email, Billing Address, ZIP / Postal Code.

At the moment checkout state is re-rendered — full page refresh (F5, per BR_2.1.1.3 Table 1), backward navigation ([Back]), or forward navigation ([Next]) — if a field holds previously-entered data that fails its format/length rule, the invalid data is displayed with an inline error message and red border.

Explicit exclusion — NULL/empty fields: if a field is NULL or empty (never filled, or cleared by the user before leaving the step), this rule does not apply and no error is shown automatically on reload/navigation.

BR_2.6.1.9: Field Disable on Sanctions Pre-Check Match

When the Sanctions pre-check (Ref: BR_2.6.1.1 mechanism 1) matches the selected Country or State/Region against a blocked jurisdiction:

  • Country match (region = 'All'): State/Province/Region field (if visible) AND ZIP / Postal Code field (if visible) both become disabled.
  • State/Region match (region = [specific region]): ZIP / Postal Code field (if visible) becomes disabled. State/Region itself stays enabled (user may pick a different region).

Fields re-enable automatically the moment the user changes the Country (or Region, for the region-match case) to a value that is not a blocked jurisdiction. Disabling does not clear any value the user had already entered into ZIP / Region — it is restored (still visible, greyed out) if the user re-selects the same blocked value again.

BR_2.6.1.10: Company Email Domain Block (Email & Confirm Email)

Domain check: value's domain is @stacktrading.com → Block.

Email field onBlur — check order:

  1. Format validation (Ref: CR-09 §9.1) — fails here, halts.
  2. Company Email Domain Block: If domain is @stacktrading.com → HALT. Phase 0 (Blacklist Check / Duplicate Account Check, Ref: UC_2.6.2 §5) only runs if this gate is passed.

Confirm Email field onBlur — check order (extends BR_2.6.1.7):

  1. Format validation (IN-07 on failure).
  2. Company Email Domain Block: If domain is @stacktrading.com → HALT (IN-11). Match comparison against Email only runs if this gate is passed. Checked independently of the Email field's own result — covers the case where a user populates Confirm Email (paste/autofill) before the Email field has ever been blurred.

On block (either field): Inline error IN-11 renders beneath the field that triggered it. [Next] stays disabled — hard stop, same pattern as IN-02/IN-03, Ref: BR_2.6.1.2 condition 1. No Guest row is written for a blocked Email value.

8. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Full NameTextboxDisplay rule:- Placeholder: "Enter full name".Validation:- Required. Ref: CR-02.Behaviour:- Ref: CR-02. DB column is full_name- Invalid on reload/navigation: Ref: BR_2.6.1.8
2EmailTextboxDisplay rule:- Placeholder: "Enter email".Validation:- Required. Ref: CR-09 §9.1.Behaviour:- On onBlur (field loses focus after user finishes typing) → runs format validation, then Company Email Domain Block (Ref: BR_2.6.1.10), then — only if the domain is not blocked — triggers UC_2.6.2 Phase 0 (Blacklist Check + Duplicate Account Check). No visible UI change on pass. Fires independently of Country/State/checkbox state. Ref: UC_2.6.2.- Invalid on reload/navigation: Ref: BR_2.6.1.8.
2bConfirm EmailTextboxDisplay rule:- Placeholder: "Enter confirm email".Validation:- Required. Must be a valid email format (Ref: CR-09 §9.1).- Must not be a blocked company domain (Ref: BR_2.6.1.10).- Must match the Email field (Row 2) exactly.Behaviour:- Ref: BR_2.6.1.7, BR_2.6.1.10.
3Billing AddressTextboxDisplay rule:- Placeholder: "Enter billing address".Validation:- Required. Ref: CR-02.Behaviour:- Not used as input to the Sanctions Gate check (see BR_2.6.1.1 — only Country + State/Region are evaluated).- Accepted even when the billing address is in a different country from the Cloudflare-detected geo country.- Invalid on reload/navigation: Ref: BR_2.6.1.8.  
3bZIP / Postal CodeTextboxDisplay rule:- Placeholder: "Enter ZIP / postal code".- Visible by default on Step 5 load (alongside Country and State/Region). On Country onChange, frontend reads zip_requirements from global checkout state (loaded via GET /system/status, see UC_2.1.1). requires_zip = false → Hide the field from the UI; requires_zip = true → Field stays visible.Validation:- requires_zip = true → Field is required. Max 20 characters (Ref: CR-02).Behaviour:- Disabled when the selected Country/Region is a blocked jurisdiction. Ref: BR_2.6.1.9.- Not tied to any field-triggered API call — the tax calculation call fires only once, on [Next] click. Ref: BR_2.6.1.1 mechanism 2, §5 Basic Flow step 7.- Accepts alphanumeric characters and spaces. No integer-only validation.- Stored in session state alongside billing_address. Passed to POST /execute-checkout payload for AVS check at the NMI gateway. Ref: BR_2.6.1.6.- Invalid on reload/navigation: Ref: BR_2.6.1.8.
4CountryDropdown (Single-selection)Display rule:- Placeholder: "Select country".Validation:- Required. Ref: CR-03.Behaviour:- On selection → clears the State / Province / Region field selection (if any) and repopulates its option list with the new Country's subdivisions. If the selected country has no subdivisions → hide the State/Region field entirely. Also re-evaluates zip_requirements[billing_country]: false → hide ZIP / Postal Code field (Row 3b); true → ZIP stays visible and required. A subsequent State/Region change does NOT re-evaluate ZIP visibility.- On selection → immediately checks whether the selected Country is a blocked jurisdiction. Blocked (region = 'All') → IN-01 displays under this field immediately; State/Region and ZIP become disabled (Ref: BR_2.6.1.9). Ref: BR_2.6.1.1 mechanism 1.- Does NOT call POST /calculate-cart by itself — that call fires only once, on [Next] click (Ref: BR_2.6.1.1 mechanism 2).
5State / Province / RegionDropdown (Single-selection)Display rule:- Placeholder: "Select state / region".- Visible by default on Step 5 load. On Country selection: if the selected country has no subdivisions → hide this field entirely; otherwise repopulate dropdown options and keep visible.Validation:- Required when visible. Ref: CR-03.Behaviour:- On selection → immediately checks whether the selected Region is a blocked jurisdiction. Blocked → IN-01 displays under this field immediately; ZIP becomes disabled (Ref: BR_2.6.1.9). Ref: BR_2.6.1.1 mechanism 1.- Does NOT call POST /calculate-cart by itself — that call fires only once, on [Next] click (Ref: BR_2.6.1.1 mechanism 2).
6Shirt sizeDropdown (Single-selection)Display rule:- Placeholder: "Select shirt size".- Options: S, M, L, XL, XXL.Validation:- Required. Ref: CR-03.
7Compliance Checkbox 1 — Commercial AcknowledgmentCheckboxDisplay rule:- Default: unchecked.- Label: Ref: UC_2.1.2 §4 Checkbox 1Validation:- Ref: BR_2.6.1.2. Mandatory — [Next] disabled if unchecked.
8Compliance Checkbox 2 — Age & ToSCheckboxDisplay rule:- Default: unchecked.- Label: Ref: UC_2.1.2 §4 Checkbox 2Validation:- Ref: BR_2.6.1.2. Mandatory — [Next] disabled if unchecked.
9Compliance Checkbox 3 — EU Waiver (Flow C only)CheckboxDisplay rule:- Rendered only if required_flow == 'FLOW_C'. Default: unchecked.- Label: Ref: UC_2.1.2 §6 Flow CValidation:- Ref: BR_2.6.1.2. Mandatory when visible — [Next] disabled if unchecked.
10Flow B Disclosure Text (Flow B only)LabelDisplay rule:- Rendered only if required_flow == 'FLOW_B'. Static, non-interactive.- Text: Ref: UC_2.1.2 §5 Flow B per is_launch_phase.
11Flow C Disclosure Text (Flow C only)LabelDisplay rule:- Rendered only if required_flow == 'FLOW_C'. Static, non-interactive.- Text: Ref: UC_2.1.2 §6 Flow C per is_launch_phase.
12Flow E UAE Disclaimer (Flow E only)LabelDisplay rule:- Rendered only if required_flow == 'FLOW_E'. Static, non-interactive.- Text: Ref: UC_2.1.2 §8 Flow E.
13Hypothetical Performance DisclaimerLabelDisplay rule:- All flows. Static block, non-collapsible.- Text: Ref: UC_2.1.2 §9 Hypothetical Disclaimer.
14Sanctions Gate Warning (conditional)LabelDisplay rule:- Rendered when the Sanctions pre-check matches (Ref: BR_2.6.1.1 mechanism 1) or, in the rare defense-in-depth case, when POST /calculate-cart's Sanctions Gate matches on [Next] click. Inline red warning.- Text: Ref: IN-01.
15[Back]Button (Secondary)Behaviour:On click → navigates back to Step 4 (Futures) or Step 3 (Forex — Step 4 does not exist in the Forex flow).
16[Next]Button (Primary)Display rule:- Disabled until ALL conditions in BR_2.6.1.2 are met.Behaviour:- On click when enabled → triggers UC_2.6.2 Phase 2 (full-PII UPSERT into the existing Guest record created at Email onBlur — no new Guest record is created here). Navigates to Step 6.

UC_2.6.2 — Lead Capture & Cart Abandonment (Three-Phase)

1. Overview

FieldContent
IDUC_2.6.2
Use CaseStep 5: Lead Capture & Cart Abandonment
DescriptionThree sequential phases write to the same Guest record.Phase 0 fires on Email field onBlur: runs Blacklist Check and Duplicate Account Check. If either gate blocks, the flow stops with an inline warning.Phase 1 fires only after both Phase 0 gates pass: captures a minimal lead via POST /capture-lead and fires a Cart_Abandonment webhook to Zapier/Klaviyo. Phase 2 fires on [Next] click: the same record is UPSERTed with the full PII collected across Step 5, and the user proceeds to Step 6.
Zapier FlowCart_Abandonment (outbound webhook event → Zapier bus → Klaviyo), fired at Phase 1 only.
Zapier Table
3rd PartyKlaviyo (lifecycle email nurture — triggered via Zapier)

2. Trigger

  • Phase 0: Email field onBlur at Step 5 — runs Blacklist Check and Duplicate Account Check. If either gate blocks → flow stops (see Phase 0 in §5 Basic Flow).
  • Phase 1: Both Phase 0 gates pass, AND Confirm Email is filled, valid, and matches Email (Ref: BR_2.6.1.7) → POST /capture-lead fires async (background, no UI change).
  • Phase 2: User clicks [Next] at Step 5 with all BR_2.6.1.2 conditions satisfied.

3. Pre-conditions

  • Phase 0: Email field contains a value in valid email format.
  • Phase 1: Both Phase 0 gates (Blacklist Check and Duplicate Account Check) have passed, AND Confirm Email is filled, valid, and matches Email.
  • Phase 2: All BR_2.6.1.2 (Next Button Gate) conditions are met.

4. Post-conditions

  • Phase 0 — blocked (Company Email Domain Block, Blacklist, or Duplicate Account Check): No Users row is written. [Next] is disabled and an inline warning is shown beneath the Email field (or Confirm Email field, if the block is on that field's own domain check — Ref: BR_2.6.1.10) (see Phase 0 in §5 Basic Flow).
  • Phase 1 — succeeded: A row in the Users table with status = 'Guest' exists for this email — newly created on the user's first-ever visit, or updated in place if a Guest row for this email already existed (e.g. re-entry, see BR_2.6.2.3). Cart_Abandonment webhook fired to Zapier automation bus.
  • Phase 2: The same Guest row is updated with full PII and session data. user_id returned and stored in session state. User navigated to Step 6.

5. Basic Flow

Confirm Email Gate — Confirm Email onBlur:

  1. Check Confirm Email: On Confirm Email field onBlur:
    • 3a. Invalid format: If the value is not a valid email format → display Ref: IN-07 ("Please enter a valid email address") and HALT.
    • 3b. Mismatch: If the value is a valid format but does not match the Email field → display Ref: IN-05 ("Emails do not match") and HALT. (see BR_2.6.1.7).

Phase 1 Trigger:

  1. Capture Lead: Once Email has passed gates (1) and (2), AND Confirm Email has passed gate (3), silently call POST /capture-lead in the background (Phase 1). This fires from whichever field's onBlur completes the full condition set last.

Phase 0 — Blacklist Check & Duplicate Account Check:

Company Email Domain Block runs first, immediately after format validation passes on Email onBlur (Ref: BR_2.6.1.10). If the email's domain is @stacktrading.com → HALT immediately — no Users row is written, [Next] is disabled, inline warning IN-11 renders beneath the Email field. Blacklist Check and Duplicate Account Check do not run.

The two checks below run on Email onBlur only when the domain is not blocked, and act as the primary gate before any capture-lead call fires.

Blacklist Check runs next. Query the blacklist table (schema below) WHERE email = payload.email AND is_active = TRUE. A match HALTs the flow — no Users row is written, [Next] is disabled, and inline warning Ref: IN-02 renders beneath the Email field. [CHR-17]

blacklist Table Schema:

ColumnTypeNotes
idSERIAL PRIMARY KEY
emailVARCHARIndexed for fast lookup at Step 5 onBlur.
reasonTEXTInternal note, never shown to user.
added_byVARCHARAdmin/ops identifier.
added_atTIMESTAMP
is_activeBOOLEANSoft-delete flag; onlyTRUE rows block.

Duplicate Account Check runs only after the Blacklist Check clears, and evaluates the existing Users record (if any) for this email against the four branches below:

Existing record stateOutcome
status = 'Active_SIM' OR status = 'Active_DMA'HALT. [Next] disabled. Inline warning Ref:IN-03.
status = 'Guest' AND provider_event_id IS NOT NULL (paid, not yet claimed — covers both provisioning "still in-progress" and "failed")HALT. [Next] disabled. Inline warning Ref:IN-04.
status = 'Failed' OR status = 'Terminated'Allow. Proceeds to the normal UPSERT (Phase 1) exactly like a fresh entrant.
status = 'Guest' AND provider_event_id IS NULL (never paid)Allow. Proceeds to the normal UPSERT (Phase 1).

If no existing record matches at all, the check also allows the flow through (first-ever visit with this email).


Phase 1 — Capture Lead (Confirm Email onBlur, once all gates pass):

  1. Once Phase 0 gates (1)–(2) and the Confirm Email Gate (3) have all passed → proceed to Phase 1 (Capture Lead).

  2. Frontend calls POST /capture-lead (fires async, in the background — no spinner, no UI change while pending), with input: email (required), full_name (optional), utm_source (optional — read from localStorage per CR-12 / BR_2.1.1.4; not re-parsed from the URL here), abandoned_step (optional).

    • abandoned_step: Records the furthest step the user has ever reached for this row — monotonic, never regresses on re-entry. See BR_2.6.2.10. All possible values the backend can insert:
    ValueTrigger point
    "Step 5: PII & Details"EmailonBlur at Step 5
    "Step 6: Payment"User clicks[Next] from Step 5 → arrives at Step 6 (Checkout & Payment)
    "Step 7: Order Processing"User clicks[Complete Purchase] on Step 6 → payment submitted
    "Step 7: Claim Account"User reaches the Provisioning Gate (Claim Account — Phase 2 of Step 7)
    "Completed"POST /claim-account returns session token successfully (Step 7 Phase 2 complete)

    [CHR-14] 'Completed' state — Klaviyo Kill Signal: When POST /claim-account returns HTTP 200, the backend sets abandoned_step = 'Completed' and fires a completion event to the Zapier automation bus to cancel the abandonment nurture sequence for this user. [CHR-14]

  3. UPSERT (keyed by email) on the core Users table, runs server-side inside POST /capture-lead:

    • First-ever visit with this email → no existing row matches → backend INSERTs a new row with status = 'Guest', using the abandoned_step value passed up in the request.
    • Re-entry with an email already captured before (see BR_2.6.2.3) → an existing Guest row matches → backend UPDATEs that same row instead of creating a duplicate (abandoned_step follows the monotonic rule — see BR_2.6.2.10).
    • There is no separate "Capture Lead" table — this always writes to the same Users row (see BR_2.6.2.5).
    • "Pending Order" is only the conceptual label ActiveCampaign/Klaviyo uses to track this record; it is not a DB status value.
    • Backend fires outbound webhook event Cart_Abandonment → Zapier automation bus.
  4. Zapier triggers a Klaviyo lifecycle email nurture campaign (see BR_2.6.2.7 for scope boundary).

  5. Backend returns HTTP 200 OK. No visible UI change — no spinner, no banner. Public endpoint, no Auth required.

Phase 2 — Full PII UPSERT (Next click):

  1. User clicks [Next] at Step 5 (all BR_2.6.1.2 conditions met).
  2. System performs an UPSERT on the same Guest record keyed by email:
    • status = 'Guest'
    • All PII fields (name, email, billing address, zip_code, country, region, shirt size).
  3. System returns user_id → stored in session state. (transaction_id does not exist yet at this point — it is only generated later, at Step 6 checkout/payment execution)
  4. Frontend navigates user to Step 6.

5a. Exceptional Flow

  • [Phase 1 — POST /capture-lead returns HTTP 5xx or times out]
    • Silent failure — no error UI shown (background lead-capture call, not a blocking step). Does not disable or delay [Next]; Phase 2 still runs its own UPSERT independently.
  • [Phase 0 — Company Email Domain Block, Blacklist Check, or Duplicate Account Check blocks the user]
    • [Next] stays disabled until the user changes the blocked field (Email and/or Confirm Email) to a value that clears all checks.
  • [User closes the browser or navigates away before clicking [Next], after Phase 0 & Phase 1 already ran]
    • See BR_2.6.2.6 / BR_2.1.1.3 — no login gate exists for this; the existing Guest record is simply updated (UPSERT) the next time the user re-enters an email on Step 5.

6. Business Rules

BR_2.6.2.1: Trigger — Three Sequential Phases

Phase 0 (Gates) is triggered by Email field onBlur. Phase 1 (Guest record creation/update + Cart_Abandonment webhook) requires BOTH Phase 0 to have passed AND Confirm Email to have been filled, validated, and matched against Email — it is not triggered by [Next]. See BR_2.6.1.7 for the full Confirm Email sequencing and the re-fire rule when Email is edited after a match.

Phase 2 ([Next] click) performs an additional UPSERT of full PII onto the same record and does not re-fire the Cart_Abandonment webhook.

Edge case — [Next] clicked without the Email or Confirm Email field ever losing focus (e.g. users fill in all other fields first, then click "Next" without blurring either field): Phase 0 and Phase 1 have not run, so no Guest row exists yet for this email. Frontend must run Phase 0 gates, the Confirm Email match check, and Phase 1 (§5) first — synchronously, before Phase 2's UPSERT — so the Cart_Abandonment webhook still fires and the row exists before Phase 2 updates it.

BR_2.6.2.2: Email onBlur — Debounce

Frontend starts a 500ms debounce timer on onBlur. If the user refocuses the email field within 500ms (e.g. to fix a typo) and blurs again, the pending call is cancelled and the timer resets. POST /capture-lead only fires after the email field has been blurred and remained unfocused for the full 500ms.

BR_2.6.2.3: Email onBlur — Change-Detection

POST /capture-lead only fires if the email that just passed the debounce timer is different from the last email successfully submitted. If the user blurs the same email again (e.g. accidentally refocuses and blurs without changing the value), no duplicate API call is made.

BR_2.6.2.4: Email Change — Old Guest Row Handling

When a user changes their email address and the new email passes both the debounce timer (BR_2.6.2.2) and the change-detection check (BR_2.6.2.3), a new Guest row is created for the new email. The old email’s Guest row is not deleted immediately — it is cleaned up by the existing 30-day cron job defined in BR_2.6.2.8.

BR_2.6.2.5: Guest Record Schema — No Separate Table

There is no dedicated “Capture Lead” table. The email is written directly to the core Users (PostgreSQL) table with status = 'Guest'. The full Users table schema (all columns) is documented at Step 7.

BR_2.6.2.6: Re-entry Handling — No Login Gate

There is no Auth0 account for a Guest record at any point before Step 7 is completed, so a “login” cannot occur for these users. Re-entry (whether via reload, back navigation, or a deep-link) is instead governed centrally by BR_2.1.1.3 (Step 0).

BR_2.6.2.7: Klaviyo Scope Boundary

Sotatek’s responsibility ends at firing the Cart_Abandonment webhook (Phase 1) to the Zapier automation bus. Klaviyo email campaign is orchestrated by Zapier — out of scope for Sotatek implementation.

BR_2.6.2.8: Guest Record Auto-Delete [CHR-19]

A background cron job runs periodically and hard-deletes records matching BOTH conditions:

  • status = 'Guest'
  • created_at < NOW() - INTERVAL '30 days'

Note: Deletion is based on created_at (original record creation date), not updated_at. A record that was created 30 days ago and still has not converted is deleted — regardless of any subsequent updates to it.

BR_2.6.2.10: abandoned_step — Monotonic Progression Guard

abandoned_step must always reflect the furthest step the user has ever reached for that row, never the current/latest one. On every write (Phase 1 UPSERT or any later re-entry UPDATE), the backend compares the incoming step against the stored value using a fixed ordinal rank and only overwrites if the incoming rank is strictly greater:

RankValue
1Step 5: PII & Details
2Step 6: Payment
3Step 7: Order Processing
3Step 7: Claim Account
4Completed (terminal — see BR_2.6.2.7 for Klaviyo kill signal)

Effectively abandoned_step = GREATEST(rank(stored), rank(incoming)), resolved back to the value name. Example: user reaches Step 7: Order Processing then reloads and lands back on Step 5 → abandoned_step stays Step 7: Order Processing; it does not regress to Step 5: PII & Details.

Source: Client instruction (chat), 2026-07-30.


Changelog

| Date | Version | Updated Item | Before | After | Notes |

| 2026-09-03 | v20 | UC_2.3 §5 "Evaluation Target / Stop" row + §7 Screen Description Row 1 (Evaluation Requirements box, Row 2 text) | Labeled "14% Target / 7.5% Stop (Static)" with Source "Fixed (no Zapier table to config)" — both percentages hardcoded as static literals in the UI copy. | Corrected to (Dynamic) — both percentages are Ops-editable Zapier Table C fields: SIM_target_percent (default 14%) and SIM_stop_percent (default 7.5%). Source column updated to Table C (SIM_target_percent / SIM_stop_percent) [CHR-49]. 60 days remains a separate fixed literal (Today + 60 Days, no Table C field — Ref: Zapier Integration V7.pdf, Flow 3A Step "Store expiration_date"), called out explicitly so it isn't conflated with the two dynamic percentages. §7 Row 2 UI copy changed from a static string literal to a dynamically-rendered template. | User audit finding, 2026-09-03. Both SIM_stop_percent and SIM_target_percent confirmed under [CHR-49] — per official CHR mapping table docs/BA/Impact_matrix_CR/CHR_Tag_Impact_Matrix_20260821_v1.md L156: CR-20260820-001 \| CHR-49 \| SIM stop/target percent. CR_summary.md's own "Nội dung thay đổi cụ thể" section only details the stop-percent formula rewrite (scoped to UC_4.10.1/UC_4.10.2, which have no Profit Target) and its Open Questions line notes SIM_target_percent wasn't raised as a formula change for UC_4.8.1/UC_4.10.4 specifically — that is a scope note for those UCs, not a statement that SIM_target_percent sits outside CHR-49. The raw CR-index CSV row (line 427-437, client Adrian, Slack thread 2026-08-14) requests both fields as one combined Table C addition, and UC_5.1.md v2.2 (2026-08-24) already dynamizes SIM_target_percent off the same Table C field — that doc should also be tagged [CHR-49] (currently cites only "Client confirmed 2026-08-24", no CHR tag; flagged for a follow-up CHR-tag pass). | | 2026-09-03 | v21 | UC_2.3 §5 "Eval Requirement — Futures" / "Eval Requirement — Forex" rows | Source column "Fixed (no Zapier table to config)" — implied these SIM-side notional/contract values were hardcoded UI literals. | Source column corrected to Table B (Futures Configuration Matrix, Max_Contracts_ES_Equiv) and Table A (Forex Configuration Matrix, Max_Forex_Notional) respectively — same fields already cited for the "Live Capital Allocation — Futures/Forex" rows below, since SIM Eval Requirement and Live Capital Allocation read the identical Table A/B values for Levels 1/2/5 (only the SIM vs Live label differs, not the underlying data source). | User request, 2026-09-03. Consistent with the v19 fix already applied to the "Live Capital Allocation" rows (Source: Zapier Integration V7.pdf, Appendix A "Forex Configuration Matrix" / Appendix B "Futures Configuration Matrix"). |

2026-08-24v19BR_2.1.1.3 — Session Recovery Matrix (Table 1/2/3), Step 6 row — F5 reload during payment processing no longer renders OV-05Step 6 reload evaluated a "payment in progress" condition: if true → OV-05 processing overlay rendered, then routed to OV-06 (success) or raw decline banner (failure).F5 during payment processing routes directly to the final state: if payment already succeeded → frontend routes immediately to Step 7 (Claim Account); if payment failed / no payment attempted → Step 6 split-panel renders normally (email-lock check still applies). No OV-05 is re-rendered on reload. Rationale: payment is a single continuous backend flow with no resumable mid-state; backend idempotency enforced viaprovider_event_id; NMI transactions are normally < 30 s. Table 2 and Table 3 cross-references updated from "payment-in-progress check" to "direct-routing logic".Client directive (chat), 2026-08-24. Companion change:UC_2.7-2.8_v1.md v4.0 (UC_2.7.1 §3/§5 updated).
2026-08-20v18UC_2.6.2 §5 Phase 2 (step 7) — "Session data" bullet removed;everflow_click_id/everflow_transaction_id renamedPhase 2 UPSERT step 7 listed a 3rd bullet: "Session data:asset_class, product_id, platform, addon_ids[], UTM parameters..., the calculate-cart result held from Step 5" — not traceable to any source doc; source (RFQ V7/Zapier V7) instead shows these fields flowing through POST /execute-checkout (Step 6) and Flow 1 Step 4 (post-payment), not the Step 5 UPSERT. Also, field name inconsistently appeared as everflow_transaction_id in this doc vs everflow_click_id in UC_2.7-2.8_v1.md.Bullet deleted (audit finding — BA session, 2026-08-20); no source backs this claim. Field renamed toeverflow_id across this doc and UC_2.7-2.8_v1.md for consistency — Ref: QnA STAGE1-061 confirms the underlying rename from impact_click_id, though QnA's exact new name is everflow_transaction_id; everflow_id was chosen instead per user directive, 2026-08-20 — flagged as a deviation from QnA wording.
2026-08-15v17BR_2.1.1.3 — Session Recovery Matrix (Table 1), Step 7.1 (Claim Account) rowNo mention of what happens if the background SIM provisioning (POST /provision-sim-user) fails while the user is still on Step 7.1, entering their phone number — gap flagged during OV-07/BN-06 consolidation.Added: if SIM provisioning fails at this point, no indication is shown on Step 7.1 — cross-referenced to newBR_2.8.3.5 in UC_2.7-2.8_v1.md (failure stays silent until surfaced by OV-07 on the Phase 3 interstitial).Client directive (chat), 2026-08-15. Companion change:UC_2.7-2.8_v1.md v3.4 (new BR_2.8.3.5).
2026-07-25v1Initial documentFirst version
2026-07-26v1BR_2.6.1.3 — Everflow SDK failure behaviorFail-open: attribution lost khi SDK bị chặnS2S Attribution Fallback:everflow_sdk_blocked flag + st_affiliate_data cookie + backend S2S Click API on-the-fly transaction generationSTAGE1-068 (Adrian Stack, Google Docs, 2026-07-26)
2026-07-26v1Zapier Table J — tất cả pricing rowsGiá cũ (Advanced $4,500/$3,599; Accelerated $1,250/$1,049; Associate $650/$499) + Reset/Extension fees cũGiá mới (Advanced $7,000/$5,599; Accelerated $1,250/$1,049; Associate $650/$499) + Reset/Extension fees mới theo data khách cung cấp 2026-07-26UC_2.1.5 Table J + UC_2.3 §5
2026-07-26v1BR_2.2.3 — Progress bar step countChưa nêu rõ số step (7 vs 6)Thêm: Futures=7 steps, Forex=6 steps; áp dụng cả initial selection và back navigationUC_2.2 §6
2026-07-26v1abandoned_step — Completed state & Klaviyo kill signalThiếu"Completed" state và Klaviyo kill signalThêm"Completed" vào abandoned_step table + note inline trong Phase 1 §5 (gộp vào flow, không tách BR)Adrian Stack (Google Docs, 2026-07-26)
2026-07-27v2UC_2.1.1 — ZIP requirements map + table schemaGET /system/status response schema had no ZIP-related fieldAddedzip_requirements object to response payload, Field Notes row, and new country_zip_requirements Table Schema[CHR-6]; impact analysis
2026-08-14v13UC_2.6.1 §5/§6/§7/§8 — Sanctions pre-check + single calculate-cart call on [Next]POST /calculate-cart fired on a shared debounce across Country/State/ZIP, and only that call surfaced the 403 Sanctions Gate error — user had to fill all fields before seeing the block. BR_2.6.1.8 did not exclude NULL fields from the reload invalid-data display.Added Sanctions pre-check on Country/State selection (immediate, before any API call) with new BR_2.6.1.9 (field disable on match). POST /calculate-cart now fires exactly once, on [Next] click, dropping the debounce entirely (rewrote BR_2.6.1.1, BR_2.6.1.2, §5 Basic Flow, §6 Exceptional Flow, §8 Screen Description Country/State/ZIP rows). BR_2.6.1.8 now explicitly excludes NULL/empty fields from the reload auto-error.BA directive, 2026-08-14.
2026-07-27v2UC_2.6.1 — ZIP / Postal Code fieldNo ZIP field at Step 5; debounce logic covered only Country + State/RegionAdded Screen Description Row 3b (ZIP / Postal Code); updated Country/State rows; rewrote BR_2.6.1.1 (3-field debounce); added BR_2.6.1.6 (ZIP dynamic validation)[CHR-6]; impact analysis
2026-07-27v2UC_2.6.2 — Blacklist & Duplicate Account Check moved to Step 5Both checks previously ran post-payment only, in Zapier Flow 1 (UC_2.8.2)Added Step A (Blacklist Check) and Step B (Duplicate Account Check, 3-branch) to Phase 1 Basic Flow, ahead of the capture-lead UPSERT; relocatedblacklist Table Schema here; added BR_2.6.2.9; Phase 2 UPSERT payload now includes zip_codeImpact analysis
2026-07-27v2BR_2.6.2.9 — Duplicate Account Check branch table3-branch table with generic "Inline warning beneath the Email field" text, no message copy, noFailed/Terminated caseExpanded to 4 branches: addedFailed/Terminated → Allow (no-op at Step 5; archive/reset stays strictly post-payment per STAGE1-086 — moving it to Step 5 would risk wiping a user's history before payment clears if they abandon cart); replaced generic text with exact copy + refs to new IN-02/IN-03/IN-04; Step A/B narrative in §5 updated to matchBA feedback item 7 (2026-07-27); STAGE1-083/STAGE1-084/STAGE1-086
2026-07-28v3UC_2.1.1 §Step 3f —cached_pass_rate calculation & sourceFormula:COUNT(user_id WHERE status='Passed') / COUNT(total_challenges), refreshed by custom Node.js cron @ 00:00 UTC; no Firm_Sim_Funnel_Snapshots schema documented in this docFormula:SUM(sims_passed) / (SUM(sims_passed)+SUM(sims_failed)) from Firm_Sim_Funnel_Snapshots (append-only); refreshed via Zapier Flow 29 → POST /financial/daily-snap @ 5:05 PM EST — no custom cron job needed; added Firm_Sim_Funnel_Snapshots Table Schema[CHR-16]; client correction of RFQ V7 §3.3
2026-07-29v4UC_2.1.1 (new BR_2.1.1.4), UC_2.6.1 (BR_2.6.1.3), UC_2.1.3UTM captured only at Step 5 page init (BR_2.6.1.3), first-seen-wins anchored there; Waitlist Gate (UC_2.1.3) = ActiveCampaign-only direct call, Klaviyo fully out-of-scopeUTM now captured globally at Step 0 (new BR_2.1.1.4), applying to every branch incl. the Waitlist Gate; first-seen-wins re-anchored to Step 0; BR_2.6.1.3 renamed to "Everflow Cookie Capture" (UTM sub-section removed, cross-refs BR_2.1.1.4); Waitlist Gate (UC_2.1.3) now fires direct parallel API calls to BOTH ActiveCampaign + Klaviyo with email+UTM — scope boundary rewritten (ActiveCampaign = CRM/identity only, no emails; Klaviyo Waitlist_Welcome drip = out-of-system once the API call fires); POST /capture-lead utm_source cross-referenced to session-state value from BR_2.1.1.4Client clarification, 2026-07-29 (Waitlist/Newsletter direct dual-call architecture + UTM capture timing)
2026-07-29v5UC_2.6.1 §8 (Row 2 Email, new Row 2b Confirm Email), BR_2.6.1.2, new BR_2.6.1.7EmailonBlur triggered Phase 1 directly; no Confirm Email field; [Next] gate had 4 conditionsEmailonBlur now triggers Phase 0 only; added Row 2b Confirm Email (frontend-only match validation); [Next] gate now has 5 conditions (added Confirm Email match); added BR_2.6.1.7 defining Confirm Email sequencing and re-validation rule on Email editCR-20260729-001; Client (chat), 2026-07-29
2026-07-29v5UC_2.6.2 §2/§3/§5/§6 (BR_2.6.2.1)Phase 1 (POST /capture-lead) fired once both Phase 0 gates passedPhase 1 now also requires Confirm Email filled/valid/matched; §5 Basic Flow split into Phase 0 — Blacklist Check & Duplicate Account Check + Confirm Email Gate + Phase 1 Trigger; BR_2.6.2.1 cross-refs BR_2.6.1.7CR-20260729-001; Client (chat), 2026-07-29
2026-07-29v5BR_2.1.1.3 — Session Recovery Matrix (Table 1, Table 2, Table 3), Step 6 rowsStep 6 rows on reload/deep-link/back-from-marketing did not account for an in-progress paymentAdded payment-in-progress check on Step 6 mount: if in progress → OV-05 loading → OV-06 success (Step 7) or failure banner; if not, normal layoutCR-20260729-002; Client (chat), 2026-07-29
2026-07-30v6BR_2.1.1.3 — Session Recovery Matrix (Table 1), Step 7.3 (Provisioning) rowGuest → kept showing provisioning/waiting state, and the frontend 60s timeout/retry mechanism remained active (Ref: UC_2.8.4 §5 Exceptional Flow)Guest → keep showing provisioning/waiting state indefinitely (no frontend timeout); failure handling delegated to backend auto-retry mechanism (Ref: UC_2.8.2 §Step 3 Exception); on exhaustion, banner BN-06 renders (Ref: UC_2.8.4 §6 Exceptional Flow)Client confirmed (chat), 2026-07-30 — remove 60s provisioning timeout/retry mechanism (see UC_2.7-2.8_v1.md changelog v2.2 for the primary change)
2026-07-30v7BR_2.1.1.4 (renamed), UC_2.1.3 §4/§5/§8, UC_2.6.1 §5, UC_2.6.2 §5 Phase 1/2UTM captured once at Step 0, stored insession state, first-seen-wins (no overwrite on subsequent loads/deep-links)UTM capture logic extracted to new cross-cuttingCR-12 (root-layout script, site-wide, not checkout-only). Storage changed to localStorage; attribution rule changed to last-touch (overwrite on presence, untouched if URL has no UTM) — supersedes QnA Q9 first-seen-wins. All UTM read-points in this doc updated to "read from localStorage per CR-12" at time-of-action, instead of a session-state value cached once at Step 0CR-20260730-002; Adrian Stack (Slack, 2026-07-30) via Hexie
2026-07-30v8UC_2.6.2 §8 Field Notes (abandoned_step), new BR_2.6.2.10abandoned_step updated to the user's current step on every Phase 1/re-entry write — could regress backwards (e.g. Step 7 → Step 5 on reload)abandoned_step now monotonic — only overwritten when the incoming step's rank is strictly greater than the stored value's rank; new BR_2.6.2.10 defines the ordinal rank table and GREATEST()-style comparison rule; Field Notes description updated with cross-refClient instruction (chat), 2026-07-30
2026-08-01v10UC_2.1.1 (Trigger note item 4, §5 Basic Flow Step 3b), UC_2.1.3 (UC Index description, §1 Overview Description, §5 Basic Flow steps 2/7, §6 Exceptional Flow, §7 BR_2.1.3.1, §8 Screen Description)Gate 1 rendered a Waitlist Lead Captureoverlay on top of the checkout page (which itself has no Header/Footer); overlay had a [✕] Close icon that dismissed it and returned the user to the checkout page underneath.Gate 1 now redirects (client-side navigation, same tab) to adedicated Waitlist page that retains the standard Marketing Header and Footer (exception carved into UC_2.1.1's "No Standard Marketing Header/Footer" rule). Checkout UI is unmounted on redirect — there is no longer a checkout page underneath to return to. The [✕] Close icon and its "returns to checkout" behavior are removed entirely — no Close action exists on the Waitlist page. "Overlay Header" renamed to "Page Content Header" to avoid confusion with the site-wide Header.User decision (BA session), 2026-08-01 — architecture change from overlay to dedicated page; avoids the redirect-loop risk of "Close → back to checkout" once the checkout page no longer exists underneath. Paired withUC_1.1-1.6_v1.md new BR_1.1.1.4 (Header CTA suppression on this page) and list-toast-popup.md OV-04 update.
2026-07-31v9UC_2.1.2 §4 — Checkbox 2 "Terms of Service" click behaviorCheckbox 2 label text documented, no click/hyperlink behavior for the "Terms of Service" words within itAdded: "Terms of Service" text is a hyperlink opening a popup at Step 5 (no navigation away, no flow reset); popup content identical to the public/terms page (UC_29) — same text, same "Last Updated" timestamp; closing popup returns to Step 5 with all form/checkbox state preserved. Cross-updated UC_29 BR-29-03 (supersedes A-10 "distinct document" framing) and corrected its scope note UC reference from UC_49 (Regional Payment Aggregators) to this sectionUser request 2026-07-31
2026-08-08v13BR_2.1.5.2 — Stale Pricing: thêm tax change trigger + spec [Refresh now]Chỉ đề cập Founder cohort sold out; [Refresh now] không có spec behavior.Thêm tax rate change là trigger thứ hai của OV-08. Spec [Refresh now]: đóng OV-08 và refresh Order Summary với giá/tax mới — không reload page.BA directive 2026-08-08.
2026-08-04v11BR_2.6.1.7 §5 (Sequencing step 2.2), UC_2.6.2 §5 (Confirm Email Gate step 3)Single IN-05 for both invalid format AND mismatch:"Emails do not match."Split into two separate inline error messages:IN-07 ("Please enter a valid email address") for invalid email format, IN-05 ("Emails do not match") for mismatch. BR_2.6.1.7 steps renumbered: 2.2 → 2.2 (invalid format, IN-07) + 2.3 (mismatch, IN-05); UC_2.6.2 §5 step 3 split into 3a/3b; cross-ref from re-validation section updated to "step 2.3".BA directive — separate error messages for email format vs mismatch on Confirm Email field. (Source: list-toast-popup.md IN-05/IN-07)
2026-08-05v12UC_2.6.1 §5 step 4, §8 Rows 3b/4/5, BR_2.6.1.1, BR_2.6.1.6 — Country/Region/ZIP field visibility patternProgressive disclosure pattern: State/Region disabled until Country selected; ZIP hidden by default, shown only whenrequires_zip=true. BR_2.6.1.1 debounce rule hardcoded 2-or-3-field combinations.Reverse visibility pattern: all 3 fields (Country, State/Region, ZIP) visible by default on Step 5 load. On Country selection → hide State/Region if country has no subdivisions; hide ZIP ifrequires_zip=false. BR_2.6.1.1 debounce rule rewritten to check "currently visible fields" with 4-branch matrix (State visible/hidden × ZIP visible/hidden).Client directive (chat), 2026-08-05.
2026-08-09v14BR_2.1.1.3 Table 1 Step 6 — email lock overlay added; split-panel always renders on mountStep 6 reload only documented payment-in-progress overlay (OV-05); email lock state not evaluated on mount; wording implied split-panel was conditional on payment check.Revised: Step 6 always renders split-panel on reload. Two overlay conditions evaluated independently after render: email locked (BR_2.8.1.2) → OV-03 on top; payment in progress → OV-05 on top. If neither: normal split-panel, no overlay.BA gap analysis, 2026-08-09.
2026-08-10v15UC_2.1.1 §4 Field Notes (id, icon_tags rows), §5 Step 3d, Payment_Method_Config Table Schema (method_id, gateway_router)Payment method enum/config listedNOMUPAY, NUVEI, DUSUPAY; Step 3d used target_country = 'BR' → Nuvei Pix/Boleto as the country-routing exampleNomupay and Nuvei removed from all enums/config rows (method_id, gateway_router, id field notes); added T365; Step 3d example rewritten to target_country = 'ZA' → Dusupay Mobile Money/Bank; icon_tags exclusion list updated to Dusupay/T365Client supplement (chat), 2026-08-10 — aligns with UC_2.7-2.8_v1.md changelog v3.2 (Dusupay redirect flow + new T365 method; Nomupay/Nuvei removed). WBS intentionally NOT updated per client instruction.
2026-08-12v16UC_2.6.1 new BR_2.6.1.8, §8 Rows 1/2/3/3b (Full Name, Email, Billing Address, ZIP/Postal Code)No rule covering invalid field data at Step 5 on reload/back/next; behavior undefinedAdded BR_2.6.1.8: on full page refresh (F5, per BR_2.1.1.3 Table 1), [Back], or [Next], an invalid field displays its invalid data as-is together with an inline error message and red border; cross-referenced from Rows 1/2/3/3bUser directive (chat), 2026-08-12
2026-08-15v17UC_2.6.1 new BR_2.6.1.10, §8 Rows 2/2b, BR_2.6.1.7 (Sequencing); UC_2.6.2 §4/§5/§5a (Phase 0)No rule blocking Stack Trading company email addresses (e.g.stacktrading.com) at Step 5 Email/Confirm EmailAdded BR_2.6.1.10 (Company Email Domain Block): Email and Confirm Email onBlur each independently reject domain@stacktrading.com, halting before Phase 0 (Blacklist/Duplicate Account Check) with new inline error IN-11 ("Stacktrading email addresses are not allowed for this field"); updated BR_2.6.1.7 sequencing, §8 Rows 2/2b, and UC_2.6.2 §4/§5/§5a accordingly[CHR-50]; Adrian Stack (Slack, via Hexie), 2026-08-15

On this page

UC IndexStep 0: System Initialization & Access GatesUC_2.1.1 — System Status API1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.1.1.1: Single Call Per SessionBR_2.1.1.2: Response PersistenceBR_2.1.1.3: Checkout Routing & Session Recovery Matrix (All Steps)BR_2.1.1.4: UTM Capture — Global, Root Layout (per CR-12)UC_2.1.2 — Geo-Based Compliance UI Variants (Flow A–J)1. Overview2. Trigger3. Routing Table4. Standard Compliance Checkboxes (All Flows Except F)5. Flow B — Pass-Rate Disclosure (UK & Australia)6. Flow C — EU/EEA Additional Checkbox7. Flow D — Quebec Localization8. Flow E — UAE Disclaimer9. Hypothetical Performance Disclaimer (All Flows)7. Business RulesBR_2.1.2.1: Flow-to-UI Mapping is Frontend-HardcodedBR_2.1.2.2: Country-to-Flow Assignment is Dynamic (DB-Driven)UC_2.1.3 — Gate 1: Waitlist1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.1.3.1: Global ScopeBR_2.1.3.2: No Interrupt LogicBR_2.1.3.3: CRM-Side Deduplication (No Information Leakage)8. Screen DescriptionUC_2.1.4 — Gate 2: Geoblock1. Overview2. Trigger3. Business RulesBR_2.1.4.1: Hard Stop — Full Page Replacement4. Screen DescriptionUC_2.1.5 — Pricing Engine1. Overview2. Business RulesBR_2.1.5.1: Founder Cohort DetectionBR_2.1.5.2: Race Condition — Stale PricingBR_2.1.5.3: Price Data SourceStep 1: Asset Class SelectionUC_2.2 — Asset Class Selection1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Business RulesBR_2.2.1: Fixed OptionsBR_2.2.2: Session PersistenceBR_2.2.3: Asset Class Change — Progress Bar & Step Count Impact7. Screen DescriptionStep 2: Capital Allocation SelectionUC_2.3 — Capital Allocation Selection1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Evaluation Package Data6. Business RulesBR_2.3.1: Pricing Mode — Standard vs FounderBR_2.3.2: Card Selection7. Screen DescriptionStep 3: Platform SelectionUC_2.4 — Platform Selection1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Platform Registry (Zapier Table I)6. Basic Flow7. Exceptional Flow8. Business RulesBR_2.4.1: Dynamic List — No HardcodingBR_2.4.2: Pre-selection with 1 ResultBR_2.4.3: Navigation — Back from Later Steps9. Screen DescriptionStep 4: Market Data SelectionUC_2.5 — Market Data Selection (Futures Only)1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.5.1: CME Locked ONBR_2.5.2: Optional Feed DefaultsBR_2.5.3: Cost DisplayBR_2.5.4: Navigation — Back from Later StepsBR_2.5.5: Product & Price Source — Table C (No Hardcoding)8. Screen DescriptionStep 5: PII Capture, Compliance & Cart AbandonmentUC_2.6.1 — PII Capture & Compliance1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.6.1.1: Sanctions Pre-Check & Tax Calculation Timing [CHR-47]BR_2.6.1.2: Next Button GateBR_2.6.1.3: Everflow Cookie CaptureBR_2.6.1.4: Data Persistence on Back NavigationBR_2.6.1.5: No Price Display at Step 5BR_2.6.1.6: ZIP / Postal Code Dynamic ValidationBR_2.6.1.7: Confirm Email — Frontend-Only Match Validation [CHR-18]BR_2.6.1.8: Invalid Field Data Display on Reload / NavigationBR_2.6.1.9: Field Disable on Sanctions Pre-Check MatchBR_2.6.1.10: Company Email Domain Block (Email & Confirm Email)8. Screen DescriptionUC_2.6.2 — Lead Capture & Cart Abandonment (Three-Phase)1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow5a. Exceptional Flow6. Business RulesBR_2.6.2.1: Trigger — Three Sequential PhasesBR_2.6.2.2: Email onBlur — DebounceBR_2.6.2.3: Email onBlur — Change-DetectionBR_2.6.2.4: Email Change — Old Guest Row HandlingBR_2.6.2.5: Guest Record Schema — No Separate TableBR_2.6.2.6: Re-entry Handling — No Login GateBR_2.6.2.7: Klaviyo Scope BoundaryBR_2.6.2.8: Guest Record Auto-Delete [CHR-19]BR_2.6.2.10: abandoned_step — Monotonic Progression GuardChangelog