StackTrading Docs

SRS: Stage 1 — Checkout Flow — Step 6 & 7 (UC 2.7–2.8)

FieldValue
BA in ChargeHuyen Dinh
Date Created2026-07-26
Versionv4.2
Last Updated2026-08-27
Document ReferencesRFQ_ Website and Dashboard Implementation V7.pdf (§Step 6, §Step 7)Zapier Integration V7.pdf (§Flow 1: New Trader Onboarding — Steps 1–9) 

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

  • [CHR-11] overrides Zapier V7 Step 8: Welcome_Sim_Challenge split into 3 separate emails. Password field REMOVED from Phase 2.
  • [CHR-23] overrides Wireframe Step 6 Flow A/H/I/J: Skrill REMOVED from payment methods. Wireframes are outdated.
  • CR-20260720-003 overrides Zapier V7 Step 4 (impact_click_ideverflow_id) and Step 5 (/impact/register-partner → Everflow equivalent).
  • [CHR-6] adds zip_code (VARCHAR, nullable) to Users table + new country_zip_requirements reference table; GET /system/status returns zip_requirements map.
  • [CHR-20] adds Promo Code Pessimistic Locking (reserve on [Pay] click, rollback on failure) + new promo_code_usage_log audit table.
  • [CHR-9] changes the 5-Failure payment lockout from IP-based to Email-based blocking.
  • [CHR-10] splits POST /resend-welcome into two endpoints: Admin-only POST /resend-welcome (Admin JWT, unchanged) and new public POST /public/resend-activation-link (no auth, Anti-Enumeration — always returns HTTP 200) for the "Link Expired" self-service flow.
  • CR-20260810-001 — Triple-A/Crypto is pending removal. All Triple-A content is kept unchanged; only (pending) annotations are added at relevant locations. No business logic has been removed.
  • [CHR-53] changes is_founder derivation at Flow 1 Step 4 from Global_Var_Founder_Cohort_Open to comparing the price charged against the Founder Price for the user's tier (Table J).

UC Index

WBS UC_IDUse Case Name (WBS)Business Description
UC_2.7.1Order SummaryStep 6 renders the split-panel checkout page. Left: payment method list and execution environment. Right: order summary, value reinforcement, and promo code panel. Includes: payment methods unavailable guard (methods[] empty → blocking modal).
UC_2.7.2Credit Card (NMI Collect.js)User selects Credit/Debit Card → NMI Collect.js hosted iframe fields render → action button → tokenize → POST /execute-checkout → dark overlay → Payment Successful. Includes: payment method DOM swap behavior for all methods, popup close behavior, payment failure & retry logic, and Email block (5 failures / 10-min, Ref: [CHR-9]).
UC_2.7.3Apple PayNMI integration invokes native Apple Pay wallet sheet (iOS Safari / macOS Safari only). WebSocket PAYMENT_RESULT routes result.
UC_2.7.4Google PayNMI integration invokes native Google Pay wallet sheet (Android Chrome / Chrome desktop). WebSocket PAYMENT_RESULT routes result.
UC_2.7.5Crypto (Triple-A) (pending)Triple-A modal: currency selection dropdown, 25-min countdown timer, QR code, one-click copy. WebSocket PAYMENT_RESULT routes result. (pending, Ref: CR-20260810-001)
UC_2.7.6SkrillRemoved by [CHR-23].  
UC_2.7.7DusupayRedirect flow: CTA opens Dusupay hosted-page link (Mobile_money / Bank / Card) in a new browser tab. Ref: OV-05 processing overlay renders on the checkout tab and holds until PAYMENT_RESULT (success/failure) or a 10-minute timeout. Frontend keeps a WebSocket listener; OV-06 renders on success, error banner on failure. Replaces the former Nomupay/Nuvei/Dusupay popup+WebSocket group.
UC_2.7.8Apply Promo CodeUser expands promo panel → enters code → clicks Apply → POST /calculate-cart → order summary refreshes with discount. Includes: [Remove] button swap to clear applied code.
UC_2.7.9T365Redirect flow: CTA opens T365 hosted payment link in a new browser tab. Ref: OV-05 processing overlay renders on the checkout tab and holds until PAYMENT_RESULT (success/failure) or a 10-minute timeout. Same pattern as UC_2.7.7 Dusupay (WebSocket listener; OV-06 on success, error banner on failure).
UC_2.8.1Phase 1 - Payment ExecutionDark overlay after payment trigger. CC: auto-transition after 2 s. Async methods: hold until WebSocket PAYMENT_RESULT. Includes: duplicate payment detection (HTTP 409). Post-payment restricted region 403 full-page screen.
UC_2.8.2Flow 1 - Provisioning PipelineBackend-only. Zapier Flow 1: Dedup check → Restricted region check (secondary fail-safe) → Duplicate check → Data/Platform parsing → SIM provisioning → DB write → Everflow registration → Discord token → Perk check → Email dispatch (Email 1 Welcome + Email 2 Claim Account).
UC_2.8.3Phase 2 - Account ClaimUser clicks "Claim Your Account" link in Email 2 → "Create an Account" screen → enters phone number → POST /claim-account. Includes: expired link self-service resend (POST /public/resend-activation-link → Email 3) and separate Admin-only resend via BPS (POST /resend-welcome).
UC_2.8.4Phase 3 - Provisioning & Redirect to LoginEntered via POST /claim-account 200 (fresh) OR reopening an already-claimed link (resumed) → "Setting up your trading floor" animated interstitial (4-step sequence) → holds until Auth0 + SIM done → redirect to Auth0 login (Ref:UC_3.1); if provisioning fails, OV-07 "Account Creation Failure" popup renders instead.

Step 6: Checkout & Payment

UC_2.7.1 — Order Summary

1. Overview

FieldContent
IDUC_2.7.1
Use CaseOrder Summary
DescriptionStep 6 renders a split-panel checkout page. Left panel: payment method selection and execution form.Right panel: order summary, value reinforcement block, and promo code section. All data is read from state cached at Step 0 — no new API call. Includes guard: ifmethods[] is empty, a blocking overlay modal prevents checkout.
Zapier Flow
Zapier Table
3rd Party

References/Wireframe/Stage 1/Step 6 Flow A.png Checkout — Base Layout (Credit Card state) References/Wireframe/Stage 1/Step 6 - no payment method available.png No payment methods — blocking overlay

2. Trigger

User navigates to Step 6 after completing Step 5 (POST /calculate-cart succeeded + [Next] clicked).

3. Pre-conditions

  • POST /calculate-cart succeeded at Step 5. total, tax, base_price stored in checkout session state.
  • methods[] array loaded from GET /system/status at Step 0 and stored in state (see BR_2.7.1.3).
  • User has passed the Step 5 compliance gate.

4. Post-conditions

  • Step 6 checkout page renders fully.
  • The first method in methods[] is pre-selected.
  • The matching execution environment renders based on the pre-selected method.

5. Basic Flow

  1. On Step 6 mount (arriving from Step 5, or re-entering via reload/deep-link), Step 6 always renders with a split-panel layout. Headline: "Secure Checkout". After rendering, the frontend evaluates the following overlay condition:- Email currently locked (5-Failure Email Lock, Ref: BR_2.8.1.2): Ref: OV-03 renders on top of Step 6. Applies on every mount — including forward navigation from Step 5 and page reload.Note — F5 reload during payment processing: Payment is a single, continuous backend flow with no resumable mid-payment state. If the user reloads mid-process, the frontend routes directly to the correct final state — no OV-05 overlay is re-rendered. See BR_2.1.1.3 (Session Recovery Matrix Table 1, Step 6) for the full direct-routing logic.

  2. System reads methods[] from checkout session state (populated at Step 0 — GET /system/statusPayment_Method_Config table, see BR_2.7.1.3). Each method renders as a radio button with label (from label field), explanatory text block (from explanatory_text), inline icons (from icon_tags[]), and CTA button (label from cta_text).

  3. The first method in methods[] is auto-selected. DOM swap executes immediately. For per-method execution detail, see:

  4. Frontend applies client-side OS/browser detection for Apple Pay and Google Pay (see BR_2.7.1.4).

  5. User clicks the CTA button → initiates payment. Flow differs by method group:

    Group A — For methods handled directly by NMI without a 3rd-party modal (CC only):

    • User fills the CC form directly in the checkout page. No external modal launched.
    • User clicks the CTA button → payment executes immediately. See UC_2.8.1 — Phase 1 Payment Execution for the full execution flow.

    Group B — For methods that launch a 3rd-party modal (Apple Pay, Google Pay, Crypto / Triple-A (pending)):

    • User selects method → clicks the CTA button → 3rd-party payment modal opens (hosted by provider).
    • Case A — User closes modal before initiating payment: Modal closes → CTA button displays normal state. No overlay shown.
    • Case B — Payment initiated inside modal, then modal is closed (e.g. accidental close mid-payment): Modal closes → Ref: OV-05 Processing overlay renders immediately, covering the checkout UI. Frontend maintains WebSocket listener for PAYMENT_RESULT event.
    • Case C — Payment completes inside modal: Modal auto-closes → Ref: OV-05 → on PAYMENT_RESULT = success → Ref: OV-06 → checkout transitions to confirmation state → Ref: UC_2.8.2.

    Group C — For methods that redirect to an external tab (Dusupay, T365):

    • User selects method → clicks the CTA button → provider's hosted payment link opens in a new browser tab. Ref: OV-05 Processing overlay renders immediately on the checkout tab (Step 6), covering the checkout UI. Frontend starts a WebSocket listener for PAYMENT_RESULT at this point (same event as Group B).
    • User completes (or abandons) payment in the external tab. The checkout tab continues to display Ref: OV-05 regardless of whether the user has switched back to it yet.
    • On PAYMENT_RESULT = success (the WebSocket event may arrive while the user is still on the external tab, or after they return) → Ref: OV-05 transitions to Ref: OV-06 → checkout transitions to confirmation state → Ref: UC_2.8.2.
    • On PAYMENT_RESULT = failure → Ref: OV-05 dismisses → error banner renders on the checkout tab (raw decline reason, same convention as UC_2.8.1 §5 step 8.3).
    • If neither result arrives within 10 minutes of the external tab opening, Ref: OV-05 dismisses and the general 10-minute frontend timeout rule applies (Ref: UC_2.8.1 §5 step 8.1) — same as any other method.
    • See UC_2.7.7 (Dusupay) and UC_2.7.9 (T365) for per-method detail.

6. Exceptional Flow

  • [If methods[] is empty]

    Ref: OV-02. A blocking popup renders. The user cannot proceed.

7. Business Rules

BR_2.7.1.1: Data Source at Step 6

  • Payment methods (methods[]): Read from session state populated at Step 0 (GET /system/status). No re-query at Step 6.
  • Initial pricing (base price, tax, total): Read from session state populated at Step 5 (POST /calculate-cart). No re-query on page load.
  • POST /calculate-cart IS called at Step 6 when the user applies a promo code via UC_2.7.8.

BR_2.7.1.3: Payment Method List Is Dynamic

The frontend must not hardcode payment methods. All methods are constructed from the methods[] array returned by GET /system/status at Step 0.

The array is built server-side at Step 3d — Gateway Filtering & Localization in UC_2.1.1: the backend queries table Payment_Method_Config by target_country and is_active, merges GLOBAL rows + country-specific rows, then capplies backend exclusion rules (e.g. India strips CC, Apple Pay, Google Pay). The resulting methods[] array is stored in checkout session state and read at Step 6 — no re-query.

BR_2.7.1.4: Apple Pay and Google Pay — Client-Side Environment Detection

Apple Pay (APPLE_PAY) and Google Pay (GOOGLE_PAY) are included in the methods[] array globally. The frontend must apply client-side detection before rendering them as options:

  • Apple Pay: Render only if the client environment is detected as Apple device/Safari.
  • Google Pay: Render only if the client environment is detected as supported (Android/Chrome).

If the environment does not support either wallet, the method radio button is not rendered — even though it exists in methods[].

8. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Payment method radio listRadio Group (Single-selection)Display rule: Rendered dynamically from methods[]. Each row: radio button + label (from label) + inline SVG icons (from icon_tags[]). Corporate logos of aggregators excluded. Apple Pay / Google Pay only rendered if OS/browser supports them. Behaviour: On click → DOM swap (renders selected payment method container). Default: first method in methods[] pre-selected on page load.
2explanatory_text blockStatic TextDisplay rule: Renders the explanatory_text string from the selected method object. Updates on each method selection.
3Order Summary — Selections (Asset Class / Platform / Market Data)Static TextDisplay rule: Three read-only lines populated from session state. If Asset Class is Forex, Market Data line is not rendered. Each line: Truncate with tooltip on overflow.
4Order Summary — Financials ([Tier] Evaluation Price / Tax / Total / Discount)Static TextDisplay rule: Values populated verbatim from calculate-cart response — display the value exactly as returned (Ref: CR-11). No hover tooltip on this row.
5Value Reinforcement BlockStatic TextDisplay rule: Three green checkmark items: "Instant Platform Credentials" / "Zero Trailing Drawdowns & No Consistency Rules" / "One-Time Fee (Zero Activation or Monthly Fees)".
6"Have a promo code?" + [Expand] iconText Link / ButtonBehaviour: On click → expands session to add code. If data is entered, the session can be collapsed/expanded but the entered code and text below it remains intact. Ref: UC_2.7.8 Apply Promo Code.
7Trust AnchorsStatic DisplayDisplay rule: 256-bit SSL icon, PCI-DSS badge, payment logos. No interaction.
8[Back]Button (Secondary)Behaviour: On click → navigates back to Step 5.

UC_2.7.2 — Credit Card (NMI Collect.js)

1. Overview

FieldContent
IDUC_2.7.2
Use CaseCredit Card (NMI Collect.js)
DescriptionUser selects Credit/Debit Card. NMI Collect.js injects PCI-compliant hosted iframes. User fills the form and clicks the action button. NMI tokenizes the card data. Frontend calls POST /execute-checkout. On HTTP 200, checkout transitions toUC_2.8.1.
Zapier FlowFlow 1 (New Trader Onboarding) — triggered by payment success webhook
Zapier Table
3rd PartyNMI Collect.js

References/Wireframe/Stage 1/Step 6 Flow A.png Checkout — Credit Card DOM state

2. Trigger

User clicks the CTA button (label from cta_text) after filling the CC form.

3. Pre-conditions

  • CC method selected.
  • NMI Collect.js hosted fields injected successfully.
  • Name on card, Card Number, Expiration, CVC filled.

4. Post-conditions

  • HTTP 200: Ref: OV-06 → auto-transitions to UC_2.8.1.
  • Declined: Ref: UC_2.8.1 §5 step 8. Failure banner displays the raw decline reason returned by NMI for this attempt — the frontend does not rewrite or generalize it.

5. Basic Flow

  1. User clicks CTA button.
  2. Frontend fires POST /capture-lead (Ref: BR_2.7.2.2) to updates abandoned_step = "Step 7: Order Processing".
  3. NMI Collect.js tokenizes card data server-side → payment_token.
  4. Process transitions to UC_2.8.1 for payload assembly and payment execution.

6. Business Rules

BR_2.7.2.1: NMI Collect.js Required — Custom Inputs Forbidden

Card Number, Expiration, and CVC MUST use NMI Collect.js hosted iframes. Custom HTML inputs for these fields are strictly forbidden for PCI DSS compliance.

BR_2.7.2.2: Lead Capture

When the user clicks the action button, POST /capture-lead fires to update the abandoned step record. The call does not block payment execution — POST /execute-checkout proceeds regardless of the outcome. (Ref: UC_2.6.2.)

8. Screen Description

No.Field NameField TypeDetail
1Name on cardText InputValidation: PCI-compliant. Handled by NMI. Custom HTML input forbidden.
2Card NumberNMI Collect.js iframeValidation: PCI-compliant. Handled by NMI. Custom HTML input forbidden.
3Expiration (MM/YY)NMI Collect.js iframeValidation: PCI-compliant. Handled by NMI. Custom HTML input forbidden.
4CVCNMI Collect.js iframeValidation: PCI-compliant. Handled by NMI. Custom HTML input forbidden.

UC_2.7.3 — Apple Pay

1. Overview

FieldContent
IDUC_2.7.3
Use CaseApple Pay
DescriptionUser selects Apple Pay and clicks the action button. NMI's web integration invokes the native Apple Pay wallet sheet. The frontend maintains a WebSocket listener for PAYMENT_RESULT to route on success or failure.
Zapier FlowFlow 1 (triggered by payment success webhook)
Zapier Table
3rd PartyNMI Apple Pay web integration

2. Visibility Rule

Refer to BR_2.7.1.4.

3. Basic Flow

Group B — Provider-hosted modal method.

  1. User clicks CTA button.
  2. Frontend fires POST /capture-lead fire-and-forget (Ref: BR_2.7.2.2).
  3. NMI integration invokes the native Apple Pay wallet sheet (3rd-party modal, hosted by Apple).
  4. Apple Pay modal interaction outcomes (Case A / Case C — note: Case B is N/A for Apple Pay as authentication is atomic):| Case | Trigger | Result | | ----------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Case A | User dismisses wallet sheet before authenticating | Sheet closes → action button displays normal state. No overlay shown. | | Case B | N/A | Apple Pay authentication is atomic — user either completes biometric auth (Touch ID / Face ID) or cancels. No mid-authentication close state exists. | | Case C | User authenticates and payment completes | Sheet closes → Ref:OV-05 → on PAYMENT_RESULT = success → Ref: OV-06 → Ref: UC_2.8.1. | | Failure | PAYMENT_RESULT = failure | Overlay removed → Ref:UC_2.8.1 §5 step 8. Failure banner displays the raw response returned by Apple Pay / NMI for this attempt — the frontend does not rewrite or generalize it. |

UC_2.7.4 — Google Pay

1. Overview

FieldContent
IDUC_2.7.4
Use CaseGoogle Pay
DescriptionUser selects Google Pay and clicks the action button. NMI's web integration invokes the native Google Pay wallet sheet. The frontend maintains a WebSocket listener for PAYMENT_RESULT to route on success or failure.
Zapier FlowFlow 1 (New Trader Onboarding) — triggered by payment success webhook
Zapier Table
3rd PartyNMI Google Pay web integration

2. Visibility Rule

Refer to BR_2.7.1.4.

3. Basic Flow

Group B — Provider-hosted modal method. NMI's web integration invokes the native Google Pay wallet sheet (hosted by Google).

  1. User clicks action button.
  2. Frontend fires POST /capture-lead fire-and-forget (Ref: BR_2.7.2.2).
  3. NMI integration invokes the native Google Pay wallet sheet (3rd-party modal, hosted by Google).
  4. Google Pay modal interaction outcomes (Case A / Case C — note: Case B is N/A for Google Pay as authentication is atomic):| Case | Trigger | Result | | ----------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Case A | User dismisses wallet sheet before authenticating | Sheet closes → action button displays normal state. No overlay shown. | | Case B | N/A | Google Pay authentication is atomic — user either completes device auth (biometrics / PIN) or cancels. No mid-authentication close state exists. | | Case C | User authenticates and payment completes | Sheet closes → Ref:OV-05 → on PAYMENT_RESULT = success → Ref: OV-06 → Ref: UC_2.8.1. | | Failure | PAYMENT_RESULT = failure | Overlay removed → Ref:UC_2.8.1 §5 step 8. Failure banner displays the raw response returned by Google Pay / NMI for this attempt — the frontend does not rewrite or generalize it. |

UC_2.7.5 — Crypto (Triple-A)

1. Overview

FieldContent
IDUC_2.7.5
Use CaseCrypto (Triple-A)
DescriptionUser selects Pay with Crypto and clicks the action button. A Triple-A modal opens with a currency selection dropdown, a 25-minute rate countdown timer, QR code, and one-click copy buttons. The frontend maintains a WebSocket listener for PAYMENT_RESULT.
Zapier FlowFlow 1 (triggered by Triple-A payment success webhook)
Zapier Table
3rd PartyTriple-A

⚠️ Status: Pending removal (Ref: CR-20260810-001). All business logic below is unchanged and has not been removed. This method may be discontinued in the future — pending final confirmation from the client.

References/Wireframe/Stage 1/Step 6_ Pay with Crypto (Dropdown).png Modal 1 — Currency Selection References/Wireframe/Stage 1/Step 6_ Pay with Crypto (QR Code).png Modal 2 — Payment Details, QR code state (State B) References/Wireframe/Stage 1/Step 6_ Pay with Crypto (QR Code) - time expired.png Modal 2 — Timer Expired state (State C) References/Wireframe/Stage 1/Step 6_ Pay with Crypto (Payment Successful).png Modal 2 — Payment confirmed

2. Trigger

User selects "Pay with Crypto" and clicks the CTA button on Step 6.

3. Pre-conditions

  • TRIPLE_A method is available in methods[] for the user's country.
  • User is on Step 6 (checkout page).

4. Post-conditions

  • Timer not expired, payment success: Triple-A confirms payment → PAYMENT_RESULT = success → Ref: UC_2.8.1 §5 step 7b (Success — Asynchronous).
  • Timer expired: Modal 2 transitions to the Timer Expired state (see BR_2.7.5.1 and Screen Description State C). Modal does not close; the checkout screen underneath is not restored.
  • Payment failure: Ref: UC_2.8.1 §5 step 8 (Payment Failure & Retry) and §6 Exceptional Flow (5-Failure Email Lock).

5. Basic Flow

  1. User clicks CTA button.
  2. Frontend fires POST /capture-lead (Ref: BR_2.7.2.2). Updates abandoned_step = "Step 7: Order Processing".
  3. Modal 1 — Currency Selection opens. Currency Dropdown (Single-selection) renders. See BR_2.7.5.1.
  4. User selects a cryptocurrency.
  5. Modal 1 closes; Modal 2 — Payment Details opens, rendering: Amount (in crypto), Wallet address, QR code, 25-minute countdown timer, one-click copy for Amount and Wallet address.
  6. Frontend starts WebSocket listener for PAYMENT_RESULT.
  7. Payment execution and result handling follow UC_2.8.1 §5 (steps 7b success, 8 failure). Timer-expiry handling specific to Modal 2 is covered in Exceptional Flow below.

6. Exceptional Flow

  • [User selects a token not supported by Triple-A]

    If the user selects a token from the Cryptocurrency Dropdown that Triple-A does not currently support (e.g. an admin added a new token to Payment_Method_Config that has not yet been enabled on the Triple-A side):

    • A toast error renders, displaying the raw error message returned by Triple-A — the frontend does not rewrite or generalize it.
    • The dropdown does not close — it remains open so the user can select a different token.
    • No payment request is made.
  • [25-minute timer expires before payment]

    Modal 2 transitions to the Timer Expired state (Screen Description State C): QR code section is replaced by an inline alert: "Time is expired. Please reload to get the latest crypto amount due." A [Reload] button allows the user to fetch a new rate and restart the 25-minute countdown, re-entering Modal 2's QR code state (State B), without closing the modal.

  • [User closes modal before initiating any action]

    Applies while Modal 1 (Currency Selection) is open, or Modal 2 is open but no payment has been sent yet. Modal closes. Checkout screen restores with state intact. No WebSocket listener is active to hold open.

  • [User closes modal while payment is processing]

    Applies once the user has sent funds and the frontend is awaiting PAYMENT_RESULT. Modal closes but, per BR_2.7.5.3, the frontend does not terminate the WebSocket listener — Ref: OV-05 renders over the restored checkout screen and holds until PAYMENT_RESULT is received or a timeout occurs.

  • [User closes modal after payment has already completed]

    If PAYMENT_RESULT = success was already received (Modal 2 Payment Confirmed state shown) before the user closes it, closing is a no-op for payment state — checkout has already transitioned per UC_2.8.1 §5 step 7b. No retry or restore-to-Step-6 behavior applies.

7. Business Rules

BR_2.7.5.1: Two Separate Modals — Currency Selection and Payment Details

The Triple-A flow uses two distinct modals, not a single modal with a blurred/empty state:

  • Modal 1 — Currency Selection: shown first. Contains only the currency dropdown.
  • Modal 2 — Payment Details: shown after a currency is selected. Renders amount, wallet address, QR code, and the 25-minute timer (State B), and later transitions in place to the Timer Expired state (State C) or the Payment Confirmed state depending on outcome — it never closes and reopens as a separate modal for those transitions.

BR_2.7.5.2: Crypto Payment Irreversibility

Crypto payments are irreversible — unlike Reversible methods, they cannot be auto-refunded via a gateway API call. If a restricted region match is detected post-payment by Flow 1, the frontend renders the restricted region screen (see UC_2.8.1 Exceptional Flow — Post-Payment Restricted Region Fail-Safe). Full backend refund/restitution logic (Reversible vs. Irreversible handling, Ref: [CHR-8] for the Crypto ledger-log + Freshdesk ticket path) lives in UC_2.8.2 §2 — Refund Mechanism. Freshdesk action is an admin workflow outside checkout scope.

BR_2.7.5.3: WebSocket Listener Survival Across Modal Close

If the user closes the Triple-A modal after a payment has been initiated inside it, the frontend must not terminate the WebSocket listener. The listener remains open until PAYMENT_RESULT is received or a timeout occurs.

BR_2.7.5.4: Crypto Token List Is Dynamic

The frontend must not hardcode the list of supported cryptocurrencies. The TRIPLE_A method entry in Payment_Method_Config includes an icon_tags[] field — an ordered array of token identifiers (e.g. ["btc", "usdt", "eth"] at launch). Modal 1's Cryptocurrency Dropdown is populated entirely from this array at render time. Adding or removing a token in Payment_Method_Config by the admin is reflected automatically without any frontend code change.

8. Screen Description

Per BR_2.7.5.1, the Triple-A flow renders as two separate checkout-overlay modals, not one modal with a blurred state. Modal 1 is shown once, then closes and hands off to Modal 2, which transitions in place across State B -> State C (or B -> Payment Confirmed) without being replaced by a third modal.

Modal 1 -- Currency Selection

No.ElementTypeNotes
1Modal titleStatic Text"Action Required to Complete Purchase"
2[X] Close buttonButton (Icon)Display rule: Top-right corner. Behaviour: On click → dismisses modal. Ref: Exceptional Flow — Exceptional Flow: User closes modal before initiating any action.
3Instructional textStatic TextDisplay rule: "You must actively send from your personal crypto wallet to the address provided below. This order will not complete until is detected on the blockchain."
4"Select a cryptocurrency:" labelLabelDisplay rule: Static label above the dropdown.
5Cryptocurrency DropdownDropdown (Single-selection)Display rule: Default: placeholder text "Select" shown, no currency pre-selected. Options list: populated dynamically from Payment_Method_Config — the icon_tags[] field of the TRIPLE_A method entry (e.g. ["btc", "usdt", "eth"] at launch, see BR_2.7.5.4). Each option rendered with currency icon + ticker symbol. Behaviour: On selection, if the selected token is not supported by Triple-A: toast error renders (raw error message returned by Triple-A), dropdown does not close. See Exceptional Flow for full behavior.
6Hint textStatic TextDisplay rule: "Select a cryptocurrency to generate payment details."
7ContinueButton (Primary)Validation: Disabled until a currency is selected. Behaviour: On click → Modal 1 closes, Modal 2 opens (State B).

Modal 2 -- Payment Details, State B (QR Code, after currency selected)

No.ElementTypeNotes
1Modal titleStatic TextDisplay rule: "Action Required to Complete Purchase"
2[X] Close buttonButton (Icon)Display rule: Top-right corner. Behaviour: On click → dismisses modal. Ref: Exceptional Flow — Exceptional Flow: User closes modal while payment is processing.
3Instructional textStatic TextDisplay rule: Same as Modal 1.
4Selected currency indicatorStatic Text + iconDisplay rule: Shows selected currency with icon (e.g. Bitcoin (BTC)). Read-only — currency cannot be changed from Modal 2; user must close the modal and restart from Modal 1 to select a different currency.
5Countdown timerStatic Text (auto-updating)Display rule: Label: "Guaranteed Rate Expires in:" — displays 25m : 00sec counting down.
6"Scan This QR Code with your Mobile Crypto Wallet App"Section headingDisplay rule: Static label above QR image.
7QR code imageImageDisplay rule: Encodes wallet address for selected currency. Rendered by Triple-A.
8Crypto Amount DueStatic Text + Copy iconDisplay rule: Label: "Crypto Amount Due". Value: float in selected crypto (e.g. 0.0074 BTC). Truncate with tooltip on overflow. Behaviour: On click of copy icon → copies value to clipboard → button label/icon changes to "Copied!" for 5 seconds → reverts to default copy icon.
9Network Address Static Text + Copy iconDisplay rule: Label: "Bitcoin Network Address" (or equivalent for selected currency). Value: wallet address string. Truncate with tooltip on overflow. Behaviour: On click of copy icon → copies value to clipboard → button label/icon changes to "Copied!" for 5 seconds → reverts to default copy icon.
10Network warningWarning bannerDisplay rule: "Warning: Send only [currency] via the [network] network. Sending via any other network will result in the permanent loss of your funds." Displayed below address field.

Modal 2 -- Payment Details, State C (Timer Expired)

No.ElementTypeNotes
1-3Modal title, X button, instructional textSame as State BDisplay rule: Identical to State B rows 1–3.
4Timer-expired alertAlert (Inline, error style)Display rule: "Time is expired. Please reload to get the latest crypto amount due."
5[Reload]Button (Primary)Behaviour: On click → fetches a new rate from Triple-A and restarts the 25-minute countdown. Modal 2 State B is re-entered.

UC_2.7.6 — Skrill

1. Overview

FieldContent
IDUC_2.7.6
Use CaseSkrill
DescriptionRemoved by [CHR-23].  
Zapier Flow
Zapier Table
3rd Party

UC_2.7.7 — Dusupay

1. Overview

FieldContent
IDUC_2.7.7
Use CaseDusupay
3rd PartyDusupay (Africa) — Mobile_money, Bank, Card, all via Hosted Page redirect

2. Display Rules

  • Dusupay's corporate logo must not appear in the payment method list radio group.
  • icon_tags[] strings (e.g. "m-pesa", "capitec") are mapped to local SVG payment method icons.
  • Dusupay offers 3 sub-methods, all using the same Hosted Page redirect pattern:
    1. Mobile_money — Hosted Page
    2. Bank — Hosted Page
    3. Card — Hosted Page

3. Basic Flow

  1. User clicks CTA button.
  2. Frontend fires POST /capture-lead (Ref: BR_2.7.2.2).
  3. Frontend calls POST /execute-checkout with provider-specific parameters (sub-method: Mobile_money / Bank / Card).
  4. Backend returns Dusupay's hosted-page link for the selected sub-method. Frontend opens this link in a new browser tab. Ref: OV-05 renders immediately on the checkout tab (Ref: UC_2.7.1 §5 Group C).
  5. Frontend starts a WebSocket listener for PAYMENT_RESULT. Ref: OV-05 holds until PAYMENT_RESULT is received or a 10-minute timeout occurs (Ref: UC_2.8.1 §5 step 8.1).
  6. User completes payment on the Dusupay hosted page, then manually returns to the checkout tab.
  7. Payment execution and result handling follow UC_2.8.1 §5 (outcome 7c). Same as all other methods, the failure banner displays the raw response returned by Dusupay for this attempt — the frontend does not rewrite or generalize it.

Historical note: UC_2.7.7 previously covered a bundled "Regional Payment Aggregators" flow (Nomupay, Nuvei, Dusupay via vendor-hosted popup + WebSocket). Nomupay and Nuvei are removed from checkout entirely; Dusupay is retained under this same UC_ID but rebuilt on the redirect-tab pattern described above.


UC_2.7.9 — T365

1. Overview

FieldContent
IDUC_2.7.9
Use CaseT365
3rd PartyT365 — Hosted Page redirect

2. Display Rules

  • T365's corporate logo must not appear in the payment method list radio group.
  • icon_tags[] strings are mapped to local SVG payment method icons.

3. Basic Flow

  1. User clicks CTA button.
  2. Frontend fires POST /capture-lead (Ref: BR_2.7.2.2).
  3. Frontend calls POST /execute-checkout with payment_method = "T365".
  4. Backend returns T365's hosted payment link. Frontend opens this link in a new browser tab. Ref: OV-05 renders immediately on the checkout tab (Ref: UC_2.7.1 §5 Group C).
  5. Frontend starts a WebSocket listener for PAYMENT_RESULT. Ref: OV-05 holds until PAYMENT_RESULT is received or a 10-minute timeout occurs (Ref: UC_2.8.1 §5 step 8.1).
  6. User completes payment on the T365 hosted page, then manually returns to the checkout tab.
  7. Payment execution and result handling follow UC_2.8.1 §5 (outcome 7c). Same as all other methods, the failure banner displays the raw response returned by T365 for this attempt — the frontend does not rewrite or generalize it.

Source: Client supplement (chat), 2026-08-10.


UC_2.7.8 — Apply Promo Code

1. Overview

FieldContent
IDUC_2.7.8
Use CaseApply Promo Code
DescriptionThe user clicks "Have a promo code?" to expand the promo input, enters a code, and clicks Apply. The frontend callsPOST /calculate-cart with the code. On success, the Order Summary refreshes with the discounted total. Includes: [Remove] button swap to clear an applied code.
Zapier Flow
Zapier Table
3rd Party

2. Trigger

  • User clicks "Have a promo code?" or the expand button.

3. Basic Flow — Promo Code

  1. User clicks "Have a promo code?" → promo input field and [Apply] button expand.
  2. [Apply] button is disabled until at least one character is entered.
  3. User enters a code and clicks [Apply] → button enters loading state (spinner) and is disabled → frontend calls POST /calculate-cart with promo_code.
  4. On HTTP 200 (discount_amount > 0):
    • Inline success text renders below the input field. Ref: IN-PROMO-01.
    • Discount line item appears in Order Summary between base price and Tax, labeled Promo code (<code>)<code> is the applied code as entered by the user (case as typed, not uppercased/lowercased). Example: code xZugg17839 → label renders "Promo code (xZugg17839)".
    • Tax and Total update to reflect post-discount amounts. Ref: BR_2.7.8.5.
    • Promo input field LOCKS (read-only, not editable). [Apply] button swaps to [Remove].
  5. On HTTP 422 (invalid / expired / limit reached): inline error renders. Ref: IN-PROMO-02 or IN-PROMO-03 for per-user limit. [Apply] re-enables; input remains editable.
  6. On HTTP 5xx: Ref: TE-SYS-01.
  7. If user clicks [Remove] (only available after a successful apply — the input is locked and cannot be edited directly):
    • Button enters loading state (spinner) and is disabled → frontend calls POST /calculate-cart (without promo_code) to recalculate Tax and Total → removes discount line item → reverts Tax/Total to recalculated base-price values → unlocks and clears the input field → button reverts to [Apply] → clears inline success text.
  8. User may then enter a new code and click [Apply] again → repeat from step 3.

Note: Once a promo code is successfully applied, the input field is locked — the user cannot edit it directly. [Remove] is the only available action to change or clear the applied code. There is no "edit while applied" state; blank input + clicking [Apply] is also not a supported trigger for removal.

4. Business Rules

BR_2.7.8.1: Apply Trigger — Click Only

POST /calculate-cart with a promo code is triggered only on explicit [Apply] click. Not triggered on onBlur.

A user may apply and re-apply different promo codes multiple times before payment — each [Apply] click calls POST /calculate-cart with the new code. The previously applied discount is replaced by the newly returned values.

Mark-as-used timing (Pessimistic Locking, Ref: [CHR-20]): The system deducts -1 from the usage limit (increments current_usage_count) AFTER the server-side price/tax check passes at POST /execute-checkout time — see UC_2.8.1 §5 step 6.1.b for the exact backend sequencing. If the price/tax check fails (PRICE_CHANGED), no reservation occurs. This ordering ensures no slot is wasted if the user's price is already stale before the gateway is even called.

  • If the payment is a genuine decline, the backend restores +1 to the usage limit (current_usage_count -= 1) immediately — see UC_2.8.1 §5 step 8.
  • If the payment session times out (10-minute frontend rule, Ref: UC_2.8.1 §5 step 8.5) without a definitive gateway/webhook result yet, the reservation stays reserved — current_usage_count is NOT restored at timeout. It remains reserved indefinitely until the backend's webhook later confirms an explicit failure, at which point +1 is restored. There is no time-based auto-rollback for this case.
  • If the payment succeeds, the deduction is kept permanently, and a new record is inserted into the promo_code_usage_log table for audit trails.

Clicking [Apply] at the promo code input does not reserve the code — reservation only happens at step 6.1.b within POST /execute-checkout, after the price/tax check passes.

BR_2.7.8.2: One Promo Code Per Transaction

Only one promo code may be applied per checkout transaction.

BR_2.7.8.3: Subtotal Field Displayed Below Applied Promo Code [CHR-51]

Status: Wait for BA update

Before: No dedicated "Subtotal" field renders in the promo code panel — the Order Summary jumps directly from the discount line to Tax/Total (see §5 Screen Description Row 5).

After (per CR_ID=51): When a promo code has been successfully applied, a Subtotal field renders directly below the promo code field/discount line, showing the running total after the discount is applied but before Tax. Exact field position relative to the existing Discount row (§5 Row 5) and exact formatting are not yet specified in this document — needs BA confirmation against the linked Figma frame before implementation.

BR_2.7.8.4: Promo Code Types, Discount Display, and Usage Rules

Two code types are supported. All validation and calculation are performed server-side via POST /calculate-cart; the frontend only reads the returned discount_amount:

TypeHow server appliesdiscount_amount returnedDisplay in Order Summary
Percentage discountdiscount_amount = Base_Price × rateSubtotal = (Base_Price + Addon_Prices) − discount_amountPositive decimal (absolute value)Promo code (<code>): −$XX.XX (Ref: CR-11)
Flat-rate deductiondiscount_amount = promo_code_valueSubtotal = (Base_Price + Addon_Prices) − discount_amountPositive decimal (absolute value)Promo code (<code>): −$XX.XX (Ref: CR-11)

The frontend does not receive or render the code type. It only reads discount_amount from the response. The label's <code> portion is the promo code the user applied (echoed from the input field, not from the server response).

Input format: Promo code input is case-insensitive — both frontend normalization and backend validation treat codes case-insensitively.

DB schema — promo_codes table: [CHR-7]

ColumnTypeNotes
codeStringCase-insensitive; alphanumeric
discount_amountFloatAbsolute dollar value
discount_percentageFloatNullable — used for percentage-type codes
expiration_dateISO 8601 UTCFailure condition: date exceeded
usage_limitIntegerTotal uses across all users (e.g. 1 = personal, 500 = campaign)
current_usage_countIntegerIncremented after server-side price/tax check passes atPOST /execute-checkout time (step 6.1.b). Decremented on payment failure. No increment on PRICE_CHANGED. Ref: [CHR-20].
max_uses_per_userInteger, NullablePer-user cap. NULL = unlimited. Default at launch:1.

Failure conditions: Code is invalid if expiration_date exceeded OR current_usage_count >= usage_limit OR this user's personal count >= max_uses_per_user (when not NULL).

promo_code_usage_log Table Schema:

ColumnTypeNotes
log_idUUID PRIMARY KEY DEFAULT gen_random_uuid()Unique identifier for each log entry.
user_idUUID NOT NULL, FK →users.user_idID of the user who applied the promo code.
promo_code_idUUID NOT NULL, FK →promo_codes.promo_code_idForeign key referencing thepromo_codes table.
time_useTIMESTAMP NOT NULL DEFAULT NOW()Timestamp of when the deduction was reserved (i.e., when the user clicked the [Pay] CTA — see mark-as-used timing above).

BR_2.7.8.5: Tax Is Calculated on Post-Discount Amount

The tax engine (Quaderno) receives Final_Amount — which is already net of any promo discount — not the base price.

discount_amount derivation (server-side, POST /calculate-cart):

Promo code typeFormula
Absolute amount (discount_amount populated)discount_amount = promo_code.discount_amount
Percentage (discount_percentage populated)discount_amount = Base_Price × promo_code.discount_percentage

Note: Percentage discount applies to Base_Price only — add-on prices are not discounted.

Order total (server-side):

Final_Amount = (Base_Price + Addon_Prices) − discount_amount
tax_amount   = Quaderno(Final_Amount, billing_country, user_ip)
Total        = Final_Amount + tax_amount

Implication: if a user applies a promo code, tax is calculated on the reduced price, not the original price.

BR_2.7.8.6: Promo Code Re-Validation at Execute-Checkout

At POST /execute-checkout time (specifically step 6.1.b, Ref: UC_2.8.1 §5 step 6.1), the backend re-validates AND reserves the applied promo code in one atomic operation (e.g. code may have been fully consumed by another user's reservation between this user's Apply and Pay clicks). This step only runs after the price/tax check (step 6.1.a) has passed — if PRICE_CHANGED is returned, neither re-validation nor reservation occurs for this attempt.

Priority vs. price-change check: If the Step 6 price/tax re-check (Ref: OV-08) fails on this same click, that takes priority — no promo reservation or re-validation runs at all for this attempt, and no promo-related error is shown. After the user retries payment with the refreshed price, this promo re-validation runs normally on that next attempt and will surface its own error then if the code is still invalid.

If the code is now invalid or the reservation fails (limit reached):

  • Return HTTP 422 with promo error signal.
  • Frontend renders a banner (not an overlay) below the promo input field. Ref: IN-PROMO-04.
  • Discount line item is removed from Order Summary.
  • Tax and Total revert to non-discounted values.
  • Promo input field stays locked and retains the now-invalid code — it is not auto-cleared. A manual [Remove] button is shown so the user can explicitly clear it and enter a new code, or re-click [Apply] to retry the same code.

BR_2.7.8.7: Promo Code Mapped 1-to-1 to a Specific Product ID [CHR-55]

Status: Wait for BA update

Before: A promo code is not mapped to any specific product ID or transaction type — a code applies uniformly regardless of what is being purchased (e.g. a code created to waive the $650 Associate Track evaluation fee could, as currently documented, be applied against an unrelated $2,500 Level 5 reset fee).

After (per CR_ID=55): Each promo code maps 1-to-1 to exactly one product ID / transaction type. At validation (both POST /calculate-cart and the POST /execute-checkout re-validation in BR_2.7.8.6 above), the backend must additionally check that the code's mapped product ID matches the product ID currently in the cart; a mismatch is rejected the same way as an already-invalid code (Ref: IN-PROMO-02 family). Recognized product IDs per the CR: EVAL_L1, EVAL_L2, EVAL_L5, RESET, REBUY, EXTENSION, MARKET_DATA.

Open items (flagged, not resolved by this stub): exact promo_codes schema change (new product_id column) is not yet reflected in the DB schema table in BR_2.7.8.4; exact error copy for a product-ID mismatch is not yet specified. (Source: CR_ID=55, References/CR/CR-index/1-58 [BA Internal] Stacktrading - Change requirement.csv; no matching References/CR/ dated folder exists for this topic.)

5. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Promo code inputSearch FieldValidation: Max 100 characters (block input at 100). Behaviour: Expands on "Have a promo code?" click. On successful apply, field locks (read-only) and retains the applied code. Unlocks only when [Remove] is clicked. If re-validation fails at POST /execute-checkout time, field stays locked and retains the now-invalid code (not auto-cleared) until [Remove] is clicked.
2[Apply] / [Remove]Button (Secondary)Validation:- [Apply]: disabled when input is empty.- [Remove]: always enabled after a code has been applied.Behaviour — [Apply]: On click → loading state (spinner) + disabled → calls POST /calculate-cart. On HTTP 200 → swaps to [Remove] and locks input.Behaviour — [Remove]: On click → loading state (spinner) + disabled → calls POST /calculate-cart (without promo_code) → recalculates totals → removes discount → unlocks input and clears it → swaps back to [Apply].
3Inline success textStatic TextDisplay rule: Visible below promo input immediately after HTTP 200 success. Ref: IN-PROMO-01. Persists while the code remains applied. Cleared only when [Remove] is clicked.
4Inline error textStatic TextDisplay rule: Visible below promo input on HTTP 422.- Invalid / expired / usage_limit exceeded: Ref: IN-PROMO-02.- Per-user max_uses_per_user exceeded: Ref: IN-PROMO-03.- Re-validation failure at POST /execute-checkout time: Ref: IN-PROMO-04. Manual [Remove] button shown; input not auto-cleared.Cleared when user edits the input field (except IN-PROMO-04, which requires [Remove]).
5DiscountStatic TextDisplay rule: Visible when discount_amount > 0. Position: between "Associate Track Evaluation Price" row and "Tax" row in Order Summary. Label: Promo code (<code>)<code> is the applied promo code as entered by the user (e.g. code xZugg17839 → label "Promo code (xZugg17839)"). Amount: negative format −$XX.XX. Ref: CR-11. Persistence: Line item remains visible for as long as the code stays applied (input locked). Removed only via [Remove].

Step 7: Order Processing & Provisioning

UC_2.8.1 — Phase 1 — Payment Execution

1. Overview

FieldContent
IDUC_2.8.1
Use CasePhase 1 - Payment Execution
DescriptionUser clicks [Pay]. Frontend renders the Orizon dark overlay + gold spinner. Backend runs pre-gateway dedup check (provider_event_id), then executesPOST /execute-checkout. Covers: synchronous CC success path, async WebSocket path (Crypto/Bank/Wallets), payment failure + retry, 5-failure Email lock (Ref: [CHR-9]), post-payment restricted region refund (fail-safe for async/off-site payments).
Zapier FlowFlow 1 (New Trader Onboarding) — triggered by payment success webhook
Zapier Table
3rd PartyNMI, Triple-A (pending), Dusupay, T365, Quaderno

References/Wireframe/Stage 1/Step 7 Phase 1 - Processing Payment.png Phase 1 — Processing overlay (spinner state) References/Wireframe/Stage 1/Step 7 Phase 1 - Payment complete.png Phase 1 — "Payment Successful" overlay References/Wireframe/Stage 1/Step 7 Phase 1 - Payment failed.png Phase 1 — Failure banner state References/Wireframe/Stage 1/Checkout Flow - Step 6 - payment lock (failed _ 5 times within 10 minutes).png Email lock overlay (wireframe filename unchanged; lock is now keyed by email, Ref: [CHR-9]) References/Wireframe/Stage 1/User has paid --_ 403 or blacklist --_ refunding.png Post-payment Restricted Region fail-safe — refund in progress state (Ref: BN-03a) References/Wireframe/Stage 1/User has paid --_ 403 or blacklist - refunded.png Post-payment Restricted Region fail-safe — refund completed state (Ref: BN-03b)

2. Trigger

User clicks the CTA button on Step 6.

3. Pre-conditions

  • User has selected a payment method and completed all required fields.
  • POST /calculate-cart has been called (pricing, tax, and totals are up to date in session state).
  • No active lock for the email address currently entered in the checkout form (Ref: [CHR-9]).

4. Post-conditions

  • Success: Payment executed. Ref: OV-06 → checkout transitions to confirmation state. Zapier Flow 1 triggered via payment success webhook.
  • Failure: Ref: OV-05 dismissed → failure banner renders. User may retry; clicking the CTA button again dismisses the current failure banner (whether or not the new attempt succeeds).
  • Email Lock: 5+ failures for this email → Ref: OV-03 renders. All inputs locked for 15 minutes (Ref: [CHR-9]).
  • Post-Payment 403: Full-page Service Unavailable screen renders. Automated refund initiated (where possible).

5. Basic Flow

The steps below run in strict sequence. Steps 6–7 (server-side) both happen inside the single POST /execute-checkout call triggered at step 5 — they are not separate round-trips. Step 7 then branches into exactly one of three mutually exclusive outcomes depending on payment_method and the gateway's response.

Payment Submission

  1. User clicks CTA button.
  2. CTA button disabled immediately (Ref: BR_2.8.1.1).
  3. Ref: OV-05 renders.
  4. (Promo code reservation, if a code is applied, occurs server-side inside POST /execute-checkout at step 6.1.b — only after the price/tax check at step 6.1.a passes. See backend flow below.)
  5. Frontend calls POST /execute-checkout with the following payload:
ParameterSource
product_idSession state
addon_idsSession state
promo_codeSession state (if applied)
billing_countrySession state (Step 5) — forwarded to gateway for AVS (Address Verification System) check.
billing_regionSession state (Step 5) — forwarded to gateway for AVS check.
billing_addressSession state (Step 5) — forwarded to gateway for AVS check.
zip_codeSession state (Step 5), dynamically required percountry_zip_requirements — forwarded to gateway for AVS check.
payment_methode.g.,"CC", "CRYPTO", "GOOGLE_PAY"
payment_tokenGateway specific token (e.g., NMI Collect.js response) (Optional)
utm_source, utm_medium, utm_campaign, utm_term, utm_contentRead fromlocalStorage at submit time, per CR-12 (Optional)
user_ipBackend detect (silent)
timestamp_utcFE-generated at submit (silent) (Optional)
everflow_idEverflow SDK cookie (silent) (Optional)
user_idCurrent logged in user (Optional)

Backend server-side logic (within execute-checkout — continues the same numbered flow, steps 6–7)

  1. Backend POST /execute-checkout Workflow:

    1. Dedup Check (runs first, before any gateway is pinged): Query Users table for user_id.

      provider_event_id is only written when Flow 1 confirms a successful payment (Ref: UC_2.8.2 §2 Step 4).

      • IF provider_event_id IS NOT NULL (a previous payment for this user_id already succeeded) → return HTTP 409 (Duplicate Payment). Ref: §6 Exceptional Flow — HTTP 409. No charge created. No gateway called. No promo code reservation occurs. No audit PDF generated. Frontend dismisses Ref: OV-05 and renders Ref: BN-07.
      • IF provider_event_id IS NULL → no successful payment exists yet for this user_id — this covers a brand-new attempt, a previous failed attempt, or a previous attempt that is still in progress (e.g. after a 10-minute timeout, Ref: BN-01). In all of these cases, the check does not block — proceed to step 6.1 and let the user pay normally. See BR_2.8.1.5.
    2. Sanctions & Pricing: Re-run Sanctions, Pricing, and Tax calculation server-side to ensure integrity, in this order:

      1. Price/tax check (runs first — before any promo reservation): Compare the server's current price and tax for the selected tier against the values the client submitted. Triggers: Founder cohort slot filled up between page load and Pay click (reverts to Standard pricing), or tax rate changed server-side since POST /calculate-cart was last called. If either is detected, the backend returns a distinct PRICE_CHANGED reason — no charge is created, no promo reservation occurs. Frontend dismisses Ref: OV-05 and shows Ref: OV-08. The promo code reservation and re-validation below are both skipped entirely for this attempt.

        Accepted race condition at the Founder cohort boundary (Ref: STAGE1-005): If multiple users hit POST /execute-checkout at the exact same instant the Founder cohort limit (500) is being reached, the first request to complete wins and the outcome for any near-simultaneous request(s) around that boundary is accepted as-is — including a possible extra Founder-priced slot slipping through. The backend does not implement queuing, distributed locks, or rollback logic to guarantee an exact 500-slot cutoff; this edge case is intentionally out of scope.

      2. Promo code re-validation and reservation (runs only after step (a) passes): Backend re-validates AND reserves the applied promo code in one atomic operation — current_usage_count += 1 on the promo_codes table (Ref: [CHR-20]). Ref: BR_2.7.8.6. This is the first point at which the promo slot is reserved — no reservation occurs if step (a) fails.

      3. Sanctions and remaining integrity checks: Any other mismatch (sanctions match not caught client-side) rejects the request and routes through step 8 (Payment Failure & Retry) below like any other decline.

    3. Audit: Generate a static PDF transaction receipt stamping the user_ip, timestamp_utc, and Payment Gateway Transaction ID. Upload the PDF to the AWS S3 Immutable storage (WORM compliant) and write the S3 URI to the user record. Ref: PDF-01 for the full receipt template specification.

    4. Routing Execution:

      • IF "CC", "APPLE_PAY", or "GOOGLE_PAY", query active MIDs in Merchant_Routing_Config. Apply Weighted Round-Robin algorithm to select target MID. If payment_token is provided, ping NMI API to execute charge and extract customer_vault_id to save to PostgreSQL. If payment_token is omitted, retrieve existing customer_vault_id using user_id to execute vaulted charge. Update MID volume.
      • IF "CRYPTO" (pending, Ref: CR-20260810-001), ping Triple-A API to generate a secure session payload.
      • IF payment_method contains "DUSUPAY", ping Dusupay API to initialize a checkout session for the selected sub-method (Mobile_money / Bank / Card) and return the hosted-page redirect link.
      • IF payment_method contains "T365", ping T365 API to initialize a checkout session and return the hosted-page redirect link.
    5. Attribution: Write the incoming utm_source, utm_medium, utm_campaign, utm_term, utm_content (received from localStorage per CR-12 — see input table above) and the everflow_id directly into the core user database record.

    6. Post-Success DB Write: On confirmed payment success (outcome 7a or 7b below), the backend writes Users.last_purchase_date = NOW(). For synchronous methods (7a), this occurs within the same POST /execute-checkout request before HTTP 200 is returned. For asynchronous methods (7b), this occurs when the backend's webhook listener confirms PAYMENT_RESULT = success.

  2. The gateway's response resolves into exactly one of the three outcomes below:

7a. Success — Synchronous (CC / Apple Pay / Google Pay via NMI)

  • POST /execute-checkout returns HTTP 200.
  • Ref: OV-05 transitions to Ref: OV-06 → auto-dismisses.
  • Ref: SES-01 dispatched immediately (Welcome email, pre-provisioning).
  • After overlay, checkout transitions directly into Phase 2 (Account Claim) — Ref: UC_2.8.3.

7b. Success — Asynchronous, overlay shown (Crypto (pending, Ref: CR-20260810-001), Bank Transfer)

  • Ref: OV-05 holds. Frontend maintains WebSocket listener for PAYMENT_RESULT event tied to the active checkout session.
  • On PAYMENT_RESULT = success → same Ref: OV-06 as step 7a, followed by the same SES-01 dispatch and direct transition into Phase 2.

7c. Success — Asynchronous, external tab redirect, overlay shown (Dusupay, T365)

  • Ref: OV-05 renders on the checkout tab immediately after the external tab opens and holds. Frontend maintains a WebSocket listener for PAYMENT_RESULT while the user is on the provider's hosted page in a separate tab.
  • On PAYMENT_RESULT = success, received either while the user is still away or on return to the checkout tab → Ref: OV-05 transitions to Ref: OV-06 → same SES-01 dispatch and direct transition into Phase 2.
  • On PAYMENT_RESULT = failure → Ref: OV-05 dissolves → error banner renders on the checkout page (see step 8 below).
  • If neither result arrives within 10 minutes of the external tab opening, the general 10-minute frontend timeout rule (step 8.1 below) applies the same as any other method.

8. Payment Failure & Retry (reached from either 7a's synchronous decline or 7b's PAYMENT_RESULT = failure, or a gateway timeout)

8.1. Gateway returns a decline, OR the frontend's own 10-minute session timer expires while waiting for a result — either path routes here.

10-minute frontend timeout rule: If the gateway/webhook has not resolved (success or failure) within 10 minutes of the POST /execute-checkout call, the frontend independently treats the session as expired and proceeds to step 8.2, regardless of what the third-party gateway is still doing in the background.

Exception — Triple-A Crypto (pending, Ref: CR-20260810-001): This 10-minute timeout rule does NOT apply to Triple-A crypto payments. Triple-A guarantees a live exchange rate for exactly 25 minutes (Ref: UC_2.7.5 §3 Screen Description, Modal 2 State B — 25-minute countdown timer). The frontend timeout for Triple-A follows the 25-minute rate lock window, not the general 10-minute rule.

8.2. Ref: OV-05 (or the active third-party widget/modal — Triple-A) dissolves. This now applies uniformly across all outcomes, including the outcome-7c redirect pattern (Dusupay, T365), which also renders and dissolves OV-05 like the other methods.

  • If the failure is a genuine gateway decline (synchronous NMI response) → raw decline banner (step 8.3) renders as before.
  • If the failure is the 10-minute frontend timeout → Ref: BN-01 (revised) renders instead: "Payment session expired. If you already submitted your payment, please check your email for confirmation. If you have not paid yet, please try again." 8.3. Orizon red failure banner renders at the top of the checkout screen. The banner text is the raw decline reason returned by whichever gateway processed this specific attempt — the frontend does not rewrite or generalize it. (Applies only to the genuine synchronous decline path — not the 10-minute timeout path, which uses BN-01 per step 8.2.)

8.4. CTA button re-enables. User may edit payment details and retry (same method or a different one). Clicking the CTA button again dismisses the current failure banner.

8.5. If a promo code was applied and reserved in step 4 (Ref: [CHR-20]):

  • On a 10-minute frontend timeout specifically, the backend does NOT roll back the reservation immediately — the reservation stays because the third-party gateway/webhook may still resolve the payment as successful after the frontend has already given up. The backend server-side webhook listener remains the source of truth:
    • If the delayed webhook later confirms success → reservation is kept permanently, promo_code_usage_log record inserted at that time (unchanged from success path).
    • If the delayed webhook later confirms failure → backend rolls back current_usage_count -= 1 at that point.
  • For a genuine synchronous decline (non-timeout), rollback still happens immediately as before — no record is written to promo_code_usage_log for that attempt (log is only written on success — see BR_2.7.8.1).

6. Exceptional Flow

[POST /execute-checkout has not resolved within 10 minutes (frontend session timeout)]

The frontend does not wait on the third-party gateway indefinitely — this protects users who simply walked away mid-payment from being permanently blocked, while still letting the backend catch a delayed confirmation. Ref: OV-05 / active payment widget dissolves. Ref: BN-01 (revised copy) renders: "Payment session expired. If you already submitted your payment, please check your email for confirmation. If you have not paid yet, please try again." CTA button re-enables; clicking it again dismisses the banner and starts a new attempt. Server-side webhooks remain listening in the background to catch a delayed gateway confirmation independently of what the frontend now shows — see §5 step 8.5 for promo reservation handling in this case.

[HTTP 409 — Duplicate Payment]

Triggered by the Dedup Check at §5 step 6.0: provider_event_id already exists for this user_id — a previous payment for this order already succeeded.

Ref: OV-05 dismissed. Ref: BN-07 renders at the top of the checkout screen. No new charge created. CTA button re-enables. User is advised to check their email inbox (and spam folder) for the Claim Account link (Ref: UC_2.8.3).

5-Failure Email Lock (Ref: [CHR-9])

Trigger condition: 5 or more consecutive payment declines within a rolling 10-minute window, calculated cross-method (e.g. 3 Credit Card + 2 Crypto = 5 → lock triggered), tracked against the email address entered in the checkout form. Ref: BR_2.8.1.2.

PhaseAction
Lock applied15-minute Email-based lock. Backend counter stored server-side (Redis, keyed by email).
Async response orderingCounted by the order gateway/webhook responses are received, not the order requests were sent (Ref: BR_2.8.1.2). An earlier-sent attempt still pending does not delay the lock from triggering on the 5th failed response.
Late in-flight resolutionAn earlier-sent attempt still pending when the lock triggers keeps its PAYMENT_RESULT listener open under the lock overlay (Ref: BR_2.8.1.6). Resolves success → lock dismissed, Ref:OV-06 renders. Resolves failure → counted normally, with no effect while the lock is still active.
Frontend displayRef:OV-03.
Page reload while lockedFrontend checks lock status for the current email on load → Step 6 renders with split-panel, Ref:OV-03 overlay renders on top.
Navigate to Step 6 while lockedOn forward navigation from Step 5 → Step 6, frontend checks lock status for the current email on mount. If locked → Step 6 split-panel renders, and Ref:OV-03 overlay renders on top.
Direct API bypass attemptBackend rejects with HTTP 429. Frontend catches 429 → re-renders Ref:OV-03. Ref:BR_2.8.1.2.
Counter resetAfter 15-minute penalty expires, backend counter resets to 0.

Note: NMI gateway independently handles its own IP-based velocity blocking; this Email-based lock is StackTrading's own layer and does not replace or depend on NMI's.

7. Business Rules

BR_2.8.1.1: CTA Button Disabled During Payment Processing

The CTA button must be disabled from the moment the user clicks it until the API call resolves (or the frontend's 10-minute session timer expires). This prevents duplicate charges. Overlay Ref: OV-05 must cover the UI during this window.

BR_2.8.1.2: 5-Failure Lock Is Cross-Method

The system counts consecutive failed payment attempts across ALL payment methods within a rolling 10-minute window for a specific Email Address (Ref: [CHR-9]). For example, failing 3 Credit Card attempts and then 2 Crypto attempts triggers a global block for that Email. Switching methods does not reset the counter.

Counting order is by response arrival, not request dispatch: Payment methods resolve asynchronously at different speeds, so a later-submitted attempt can return its result before an earlier-submitted attempt still in flight. The failure counter increments strictly in the order responses are received by the backend, not the order POST /execute-checkout calls were made. If the 5th consecutive failure by response order is reached, the lock triggers immediately — even if an earlier-sent attempt has not resolved yet. See BR_2.8.1.6 for how that still-pending earlier attempt is handled once it eventually resolves.

If a locked user bypasses the UI and calls POST /execute-checkout directly with the locked email, the backend returns HTTP 429. Frontend must catch this specific response and re-render the Ref: OV-03 overlay.

BR_2.8.1.3: CTA Button Re-enable on Failure Only

The CTA button is disabled immediately after the first click and is not re-enabled until a failure response is received (including the 10-minute frontend timeout expiring). It must not be re-enabled on overlay dismissal or back navigation while a payment is in progress.

BR_2.8.1.4: Accidental Double-Payment After Timeout Retry [CHR-21]

If a user ignores the 10-minute timeout warning and successfully submits a second payment after their original (delayed) payment also clears via webhook, the system always routes this through the irreversible-refund handling path — Ref: BR_2.7.5.2 ([CHR-8]) — regardless of the payment method's own reversibility: it logs the transaction exception to the financial ledger and immediately generates a high-priority Freshdesk compliance ticket for treasury desk restitution. There is no separate "Ops manual refund" branch for reversible methods (CC/Apple Pay/Google Pay) — once flagged as a duplicate under this rule, it is always ticketed, not auto- or manually refunded on the spot.

This is distinct from the existing HTTP 409 same-session duplicate check in §6, which blocks a duplicate submission before it reaches the gateway. This rule only applies to the edge case where two independent successful charges occur because the user retried after a 10-minute frontend timeout that later turned out to be a false negative.

BR_2.8.1.5: Retry Is Allowed When the Prior Payment Failed or Is Still In Progress

After a 10-minute timeout (Ref: BN-01) or a genuine decline, the user can click [Pay] again. The Dedup Check at §5 step 6.0 only blocks a retry if the prior payment already succeeded. It does NOT block when the prior payment:

  • Failed, or
  • Is still in progress (webhook confirmation not received yet).

In both cases provider_event_id stays NULL, so the retry proceeds through step 6.1 like a normal new attempt.

This also applies if the user navigates back to Step 5 and then forward to Step 6 again — that navigation does not add any extra block. Ref: BR_2.1.1.3 already allows normal Step 6 rendering whenever no payment is currently in progress.

BR_2.8.1.6: Late-Resolving In-Flight Attempt During an Active Email Lock

An attempt submitted before the 5th consecutive failure (by response order, per BR_2.8.1.2) can still be in flight — awaiting its gateway/webhook result — at the moment the lock triggers and Ref: OV-03 renders. The frontend's WebSocket listener for that specific attempt's PAYMENT_RESULT stays open underneath the lock overlay; the lock does not cancel or ignore it.

  • If that attempt later resolves as SUCCESS: The lock overlay is dismissed immediately, regardless of how much of the 15-minute lock window remains. Ref: OV-06 renders and checkout transitions into Phase 2 (Account Claim) exactly as in the normal success path — Ref: UC_2.8.1 §5 steps 7a/7b. If the user is no longer on that screen when the result arrives (e.g. closed the tab before it resolved), two re-entry cases apply depending on how the user comes back:
    • Re-enters from Step 1 with the same email: Step 5 Email onBlur runs the Duplicate Account Check (Ref: UC_2.6.2 §5) — status = 'Guest' AND provider_event_id IS NOT NULL matches, so the flow HALTs at Step 5 itself: [Next] disabled, Ref: IN-04 renders ("You have an unclaimed account. Please check your email for your activation link."). The user never reaches Step 6 again.
    • Resumes directly at Step 6 in the same session (reload or deep-link while local storage still holds the Step 6 position, per BR_2.1.1.3): Step 5 is not re-visited, so the Dedup Check at §5 step 6.0 is what catches it — provider_event_id IS NOT NULL → HTTP 409 → Ref: BN-07 (claim-account flow).
  • If that attempt later resolves as FAILURE: Whether it is added to the failure counter depends on whether the 15-minute lock is still active when the response arrives:
    • Lock still active: The Redis counter increments as normal, but this has no visible effect — the email is already locked, and the increment does not extend or shorten the remaining lock time.
    • Lock already expired (counter already reset to 0, per §6 Exceptional Flow — Counter reset): The failure is counted as the first failure of a new rolling 10-minute window. It does not retrigger the lock on its own — only reaching 5 consecutive failures within that new window does.

UC_2.8.2 — Flow 1 — Provisioning Pipeline

1. Overview

FieldContent
IDUC_2.8.2
Use CaseFlow 1 - New Trader Onboarding (Simulated & Live Prep)
DescriptionBackend-only. Zapier receives the payment success webhook from NMI or Triple-A and runs Flow 1 sequentially: Dedup check → Restricted region check → Global Entity & Archive check → Data/Platform parsing → SIM provisioning → Database write → Everflow registration → Discord token → Perk check → Email dispatch. No user action required.
Zapier FlowFlow 1: New Trader Onboarding
Zapier TableTable C, Table H, Table J
3rd PartyNMI, Triple-A (pending), Rithmic, MT5, TraderEvolution, Everflow, AWS SES

2. Flow 1 Logic

Trigger: Webhook from Middleware Hub Event: NEW_PAYMENT_SUCCESS

Step 1: Gatekeeping Checks

  • Deduplication: Query Users table for provider_event_id = Event_ID. IF Match Found -> HALT.
  • Restricted Region Check (Fail-Safe): Note - Primary blocking occurs at the API level Pre-Payment. This Zapier step is a secondary fail-safe to catch any bypass attempts.
    • Query Billing_Country from NMI/Triple-A Payload.
    • Query the Compliance_geo_restrictions PostgreSQL database table.
    • IF a match is found for Country AND (Region is 'All' or matches user Region): Issue Refund (see Refund Mechanism below). Send Ref: SES-05 Email. HALT.

Refund Mechanism (applies to the Restricted Region fail-safe above):

  • Reversible payment (Credit Card, Apple Pay, Google Pay — all via NMI; or Dusupay/T365 — hosted-page redirect rails): system triggers an automated reverse charge, using the original payment gateway reference, via the POST /issue-refund endpoint. Backend queries the database to find the original transaction ID and Gateway_Source, pings the matching gateway's refund API (NMI for CC/Apple Pay/Google Pay, Dusupay/T365 for their respective rails), then updates the invoice record's status to 'refunded'.
  • Irreversible payment (Crypto/Triple-A (pending, Ref: CR-20260810-001)): no automated refund is possible on-chain. Per [CHR-8], the system instead logs the transaction exception to the financial ledger database and immediately generates a high-priority Freshdesk compliance review ticket for manual treasury desk restitution. Freshdesk action is an admin workflow outside checkout scope.

Frontend display after refund signal (403):

Refer design:

  • References/Wireframe/Stage 1/User has paid --_ 403 or blacklist --_ refunding.png — refund in progress state
  • References/Wireframe/Stage 1/User has paid --_ 403 or blacklist - refunded.png — refund completed state
ElementContent
Screen shownRef:FP-02. Fires after the post-payment region re-check above finds a violation.
Alert bannerRef:BN-03a while refund is processing (maps to the "refunding" wireframe), then BN-03b once confirmed (maps to the "refunded" wireframe).
Button[Return to Homepage] → homepage
  • Global Entity & Archive Check: Query SQL Database for existing user_email to identify existing user_id. IF none exists, generate user_id.

    • IF Status == 'Failed' OR 'Terminated':
      • Archive old record to user_account_history table (schema below).
      • Set Current_Level to 1, 2, or 5 based on the product_id purchased. Clear Pod_Leader_ID. Reset Defense_Attempt_Count to 0. Reset Defense_Used to False. Set status = 'Active_SIM'

Existing-Account Cases:

CaseMeaningAction
Not foundNew user, no prior accountGenerate newuser_id → Allow purchase
Status ='Failed'User failed the Sim evaluation (Max Drawdown / Level Stop hit, hard breach — both converge via Flow 2 →POST /fail-challenge. Note: Daily loss limit does NOT fail them, it's a cooldown period)Archive old record touser_account_history. Reset sequence (Current_Level, Pod_Leader_ID, Defense_Attempt_Count, Defense_Used, status) as above. → Allow re-entry via POST /purchase-challenge-reset: IF is_founder = TRUE, charge the grandfathered locked_reset_price; IF FALSE, charge the standard Reset Price for the user's current_level + asset_class (Futures/Forex Reset Price column, Table J) — NOT the full Challenge Price. Platform retains original profile ID / dashboard credentials (identity retained) but archives all historical trade logs (user cannot see old trades on active dashboard); provisions a NEW sub-account on broker with new terminal credentials.
Status ='Terminated'User was terminated on a Live/DMA account — always hard breach (Level Stop/Max Drawdown), set by Flow 7 →POST /system/set-account-state with state="Terminated"Archive old record touser_account_history. Same reset sequence as Failed. → Allow re-entry via POST /initiate-rebuy: IF is_founder = TRUE, charge the grandfathered locked_rebuy_price; IF FALSE, charge Challenge_Price × (1 − Retry_Discount) for the user's tier (Table J).

user_account_history Table Schema:

ColumnTypeNotes
history_idUUIDPrimary Key
user_idUUIDFK to Users.user_id, Indexed.
emailVARCHAR
previous_levelINTEGER
final_statusVARCHARE.g., 'Failed', 'Terminated'
failure_reasonVARCHARE.g., 'Drawdown', 'Daily Loss', 'Stagnation', 'Resigned'
archived_atTIMESTAMPDefault: NOW()
user_snapshotJSONBFull JSON payload of the Users row state prior to reset

Step 2: Data & Platform Parsing

  • Read metadata.front_end_platform, metadata.addon_ids, and metadata.shirt_size from the standardized Webhook Payload.
  • Market Data Analysis: Parse addon_ids array.
    • IF CME_Bundle in array -> Set Var_Data_CME = True.
    • IF NYMEX_Bundle in array -> Set Var_Data_NYMEX = True.
    • IF CBOT_Bundle in array -> Set Var_Data_CBOT = True.
    • IF COMEX_Bundle in array -> Set Var_Data_COMEX = True.
  • Logic: If (CME + NYMEX + ...) > 1 Feed -> Set Market_Data_Billable = True. (These variables will be written to DB in Step 4).

Step 3: Provisioning Branch - Simulation Mode

  • Action: Call Middleware Hub Endpoint (POST /provision-sim-user).
  • Input: platform_selection, Asset_Class, user_email.
  • Internal Logic (Handled by Node.js):
    • IF Ninja/Quantower/ATAS/Motivewave/Sierra: Before calling the Rithmic API, the user's country must be mapped to Rithmic's exact accepted country value. If no valid mapping is found for the entered country, the system must not default to "United States" or any other country under any circumstance — it must raise a hard failure and halt provisioning (see Step 3 Exception below). Once mapped, creates Rithmic User via API, Enables Non-Pro Data.
    • IF MT5: Creates Account via Manager API, assigns to respective Group Template.
    • IF TraderEvolution OR TradingView: Creates Account via TraderEvolution Admin API, assigns to respective Group Template.
  • Output API returns JSON Object:
    {
      "platform_username": "...",
      "platform_password_ciphertext": "...",
      "license_key_ciphertext": "...",
      "cipher_version": "v1"
    }

Notes: Middleware must never return plaintext platform passwords or license keys to Zapier. Ciphertext is KMS-envelope encrypted (or equivalent field-level encryption) and can only be decrypted by Middleware under explicit authorization + audit logging.

Step 3 Exception: SIM Provisioning Failure Handling

If POST /provision-sim-user (Step 3) returns an error instead of the JSON object above, Node.js middleware classifies and handles the error before Flow 1 is allowed to continue:

  1. Classify the error against a known error/response-code translation table (broker API error codes → internal error type).
  2. IF error type = "Country Mismatch" (Rithmic-routed platforms only — the entered country has no valid mapping to a Rithmic-accepted value):
    • Hard failure — HALT immediately. The system must never substitute a default or guessed country to let provisioning proceed; defaulting to a false country is a regulatory/compliance risk and is explicitly forbidden regardless of implementation approach.
    • Same escalation treatment as "Manual" below (Freshdesk ticket, SES-23, no automated retry — retrying an unmapped value would only reproduce the same failure).
  3. IF error type = "Account Exists":
    • Rithmic (Futures): Rithmic uses a global user ID shared across firms (many-to-one — one user can map to multiple firms, Stack Trading being one). Middleware links the existing global user ID to the Stack Trading firm record instead of creating a new one.
    • MT5 / TraderEvolution (Forex): These are walled-garden servers hosted in NY4. A user's external MT5/TraderEvolution account on another firm's server cannot be linked to a Stack Trading account — "Account Exists" here means a username/email collision on our own server only. Middleware appends a unique suffix to the username and retries creation immediately (no manual escalation needed for this case).
  4. IF error type = "Manual" (resolvable but requires human intervention) or retries are exhausted (see below):
    • Middleware returns a specific error code to Zapier Flow 1.
    • Zapier Flow 1 must catch this error code and HALT immediately. It must NOT proceed to Step 8 (Communication) — sending Welcome_Sim_Challenge (Email 2) with a blank/null platform_username is a defect and must be prevented.
    • Middleware fires a Freshdesk ticket for manual account creation and dispatches Ref: SES-23 — a client-facing email stating an issue occurred during provisioning and tech support has been notified and will contact them. (Ref: CR-20260728-004)
    • Ops resolves via the Break-Glass Manual Account Provisioning Runbook (Ref: below).
  5. IF error type is automation-manageable (transient/retryable, excludes "Country Mismatch" per step 2): backoff retry at 5 s, then 30 s, up to 3 attempts total. If all 3 attempts fail, treat as "Manual" (step 4 above).

[ADMIN FUNCTION - NOT IMPLEMENT IN THIS MILESTONE YET]

Break-Glass Runbook — Manual Account Provisioning [CHR-12]

Invoked when Zapier Flow 1 halts on a provisioning failure (SIM provisioning above, or Auth0 account creation failure — Ref: UC_2.8.3 §5) and a Freshdesk ticket has been raised for manual intervention.

  1. Ops receives the Freshdesk ticket and opens the authenticated Ops Console.
  2. Ops manually creates the account via the relevant broker admin portal (Rithmic / MT5 Manager / TraderEvolution Admin), or — for an Auth0 failure — manually invokes Auth0 user creation from the Ops Console.
  3. Ops uses an internal Ops API endpoint to securely inject the ciphertext credentials back into the PostgreSQL Users table (platform_password_ciphertext, license_key_ciphertext), so the user can still complete the standard "Claim Account" flow (Ref: UC_2.8.3) securely.
  4. Ops sends the user a manual activation link (Auth0 case) or confirms the standard Email 2 dispatch resumes (SIM provisioning case).
  5. All Ops Console actions are RBAC-gated and immutably audit-logged (who / why / when).

SIM Account Architecture

Level 0 corresponds to the Paper Trading / Simulation state before a user passes the evaluation and transitions to a live funded status. At initial provisioning, Current_Level is set to **1, 2, or 5 based on the product_id purchased — it is not a fixed value. Provisioning architecture differs by asset class (Live architecture out of scope for this section):

  • Futures (Rithmic): SIM accounts follow a parent-and-child model. Each SIM user is provisioned as a sub-account under the master administrative account. These sub-accounts are created exclusively on the separate Dedicated Rithmic Paper Trading Gateway, keeping them completely isolated from the Live Ironbeam FCM environment.
  • Forex (MT5 & TraderEvolution): In the SIM environment, each user's account is a fully standalone, independent account on the platform server — not a sub-account under the Stack Trading Omnibus master account during the evaluation phase. The system creates the user account and assigns it to an evaluation Risk Group Template (e.g. Sim_MT5_Default) to govern trading parameters. Since MT5 and TraderEvolution operate on a single server environment, Sim vs. Live is just an internal configuration — when a trader passes, their discrete Account ID is reassigned to a live risk group and mapped to the corporate Omnibus account for real execution routing.

Step 4: Database Write (Users Table)

Note: This is a separate DB write from the Attribution sub-step inside POST /execute-checkout (UC_2.8.1 §5 step 6.4) — that write persists utm_source/utm_medium/utm_campaign/utm_term/utm_content synchronously at Step 6, BEFORE Flow 1 ever runs. This Flow 1 write happens later (async, post-webhook) and does NOT repeat the UTM fields — per source (Zapier Integration V7.pdf, Flow 1 Step 4), only everflow_id is stored here, no UTM columns.

Create Record in Users Table:

FieldValue / SourceNotes
user_idFrom Step 1 Global Entity & Archive Check (newly generated, or existing ID if a reset case)Primary Key. Missing from the source bullet list — audited in againstUsers table schema §3.1.6.
emailuser_email from Webhook PayloadMissing from the source bullet list — required for the Global Entity & Archive Check lookup itself to function on future purchases.
zip_codezip_code from Webhook Payload (Step 5 session state)VARCHAR, nullable — persisted only whenrequires_zip = TRUE for the user's country per country_zip_requirements (UC_2.1.1); NULL otherwise.
status'Guest'Initial state. Transitions to'Active_Sim' only after the trader completes the Claim Account flow (Ref: UC_2.8.3).
provider_event_idMapped from WebhookEvent_IDUnique index — used by Step 1 Deduplication check.
platform_selectionFrom Webhook Payload
platform_usernameFrom Step 3 response
platform_password_ciphertextFrom Step 3 responseKMS-envelope encrypted, not plaintext. Must not be exposed via general user profile endpoints.
license_key_ciphertextFrom Step 3 responseKMS-envelope encrypted, not plaintext.
cipher_versionFrom Step 3 response
shirt_sizeFrom Step 2 metadata
connection_gatewayFrom Webhook Payload
Asset_ClassBased on platform selection
Is_ProfessionalFalse
Current_Level1, 2, or 5 based onproduct_id metadata
Market Data Flags (Data_CME, Data_NYMEX, etc.)From Step 2 variables
expiration_dateToday + 60 Days
everflow_idFrom metadata
pod_leader_idNULLNo Pod Leader is assigned at initial provisioning — assignment happens later in the evaluation lifecycle.
credentials_claimed_atNULLSet only when the user completes the Claim Account flow (Ref:UC_2.8.3); used as the guard at three call sites — Admin resend (POST /resend-welcome), the link-open pre-check redirect, and the public resend's silent-drop check (Ref: [CHR-10]).
is_founder, locked_rebuy_price, locked_reset_priceFounder Cohort check (below)
last_purchase_dateNOW() on successful checkout, reset, or re-buyTIMESTAMP, Nullable.

Founder Cohort check (Ref: [CHR-53]): Compare the price charged for this transaction against the Founder Price for the user's tier (Table J).

  • IF price charged == Founder Price for tier: Set is_founder = TRUE. Set locked_rebuy_price to the Founder Challenge Price for their specific tier. Set locked_reset_price to the Founder Reset Fee for their specific tier (Table J).
  • IF price charged != Founder Price for tier: Set is_founder = FALSE. Set locked_rebuy_price = NULL. Set locked_reset_price = NULL.

Step 5: Register Everflow Partner (Velocity Program)

  • Action: Call Middleware Hub (POST /everflow/register-partner).
  • Logic: Automatically registers the new user as a "Partner" in the internal Velocity Group to enable their unique referral link.
  • Output: Update Users table set is_everflow_partner = True.

Step 6: Discord Integration

  • Action: Generate a unique UUID (Discord Link Token).
  • Action: UPDATE Users table in PostgreSQL Database. Set discord_link_token = [Generated UUID].

Step 7: Perk Check - Generic

  • Query Table H: Perks_Registry for Level = 1.
  • IF Active == Yes AND Claimed < Limit:
    • Get Fulfillment_Zap ID from Table H.
    • Trigger Fulfillment Zap: Flow 25 — Fulfillment: Founding 500 Hoodie (Apliiq)
      • Trigger: Triggered by Flow 1 (Step 7) if conditions met.
      • Action: Call Apliiq API to place an order.
      • Product: "Founding 500 Heavyweight Hoodie" (SKU mapped in Apliiq).
      • Options: Map shirt_size from the Users table to the Apliiq size parameter.
      • Recipient: Trader Name/Address from SQL DB.
      • Database Update (Perk Logic):
        • Agency Note: Use database transaction or atomic increment for Claimed count to prevent race conditions during high volume.
        • Increment Claimed count for Level 1 in Table H.
        • Limit Check: IF Claimed >= Limit: Set Active = No.
        • Alert: IF Limit Reached → Notify Ops & Marketing via Slack: "Founding 500 Hoodie Limit Reached!"
      • Notify: Send Email to Trader (Shipping Confirmation template if available from Apliiq, or generic "Perk on the way").

Step 8: Communication

Send Ref: SES-02 Email (template Welcome_Sim_Challenge).


UC_2.8.3 — Phase 2 — Account Claim

1. Overview

FieldContent
IDUC_2.8.3
Use CasePhase 2 - Account Claim
DescriptionUser receives Email 2 ("Claim Your Account").Clicks the single-use 48-hour link → "Create an Account" screen → enters phone number → POST /claim-account → transitions to Phase 3. Includes: expired link flow — link expired (48 h) → "This link has Expired" screen → user clicks [Resend Activation Link] (no email input — user identity resolved from the expired token itself) → POST /resend-welcome → Email 3 dispatched. Also includes: link invalid (e.g. user changes the token) → "Invalid Link" screen, Ref: FP-05, no resend option.
Zapier FlowFlow 1 Step 8 (Email 2 dispatch — baseline, unaffected by [CHR-11]); Flow 41 (Resend Welcome — expired link — [CHR-11])
Zapier Table
3rd PartyAWS SES (email), Auth0 (account activation)
Endpoints [CHR-10]POST /resend-welcome (Admin-only, JWT auth required — used by Ops via BPS); POST /public/resend-activation-link (public, no auth — used by the end-user self-service "Link Expired" screen). Two distinct endpoints, do not conflate.

References/Wireframe/Stage 1/Step 7 Phase 2_ Create an Account.png Phase 2 — "Create an Account" screen (phone input) References/Wireframe/Stage 1/Step 7 Phase 2_ SIM-email - link expired.png Expired link screen References/Wireframe/Stage 1/Step 7 Phase 2_ SIM-email - resend email successfully.png Resend success confirmation

2. Email Flow

EmailTriggerCR StatusKey Content
Email 1: WelcomeImmediately after payment success[CHR-11] — new split-out email, did not exist pre-CRRef:SES-01.
Email 2: Claim AccountAfter SIM provisioning completes (POST /provision-sim-user returns HTTP 200) AND Zapier Flow 1 (Step 8 Communication) is completedNot CR — baseline content carried over from the original pre-splitWelcome_Sim_Challenge emailRef:SES-02.
Email 3: ResendUser-requested (link expired)[CHR-11] — new Flow 41 (Resend Welcome), did not exist pre-CRRef:SES-03.

PropertyValue
TypeSingle-use, time-limited JWT
Expiry48 hours from dispatch time
Env variableACTIVATION_TOKEN_EXP_HOURS=48 (Node.js middleware, AWS deployment)
InvalidationImmediately upon POST /claim-account returning HTTP 200

Single-use mechanics: The link is marked used (deleted/deactivated) only at the moment the phone form is submitted AND the backend returns HTTP 200 for POST /claim-account (Ref: BR_2.8.3.1). If the user closes the browser tab mid-entry (e.g. after opening the link but before submitting the phone number), the JWT token embedded in the link remains fully functional until its original 48-hour expiration timestamp — opening it again simply re-renders the "Create an Account" screen from step 2 of §4 below. Note: The Step 7 Claim Account screen itself (when reached organically from Step 6 in the browser without using the email link) is not time-limited and has no expiration timer running on that session.

Installing the platform software (e.g. R|Trader Pro) is not a prerequisite to claiming the account — the user can complete Claim Account and reach the Dashboard without having installed anything.

4. Basic Flow — Claim Account

  1. User clicks "Claim Your Account" link in Email 2.
  2. Frontend/backend runs checks, in order:
    • Link validity check: if the link is invalid (e.g. user changes the token) → Exceptional Flow, "Claim link invalid" (§5) — renders immediately. None of the checks below run.
    • Claimed-status check (only reached if the link is valid) — checked before JWT expiry (the backend resolves user_id from the token payload even when the JWT itself has expired, the same way Case 1's resend endpoint does):
      • credentials_claimed_at IS NOT NULL → Exceptional Flow (§5 Case 2) — frontend renders the Phase 3 "Setting up your trading floor" interstitial (Ref: UC_2.8.4, resumed-entry path). Do NOT render "Create an Account" and do NOT render "This link has Expired", even if the JWT is also expired. Already-claimed always wins over expired. See UC_2.8.4 §5 for how the interstitial resolves (redirect to login vs. Account Creation Failure popup).
      • credentials_claimed_at IS NULL AND JWT expired → Exceptional Flow, "Claim link expired — 48 h" (§5).
      • credentials_claimed_at IS NULL AND JWT valid → "Create an Account" screen renders.
  3. In the background, the system asynchronously initiates the SIM account creation (POST /provision-sim-user). In parallel on the UI, the user enters their phone number. This action does not wait for the SIM provisioning to finish. If it fails at this point, nothing is surfaced here — Ref: BR_2.8.3.5.
  4. User clicks [Activate Account].
  5. Frontend calls POST /claim-account. The Auth0 account creation is strictly gated and will only be executed AFTER the user submits their phone number. Input: email, transaction_id, phone_number.
  6. On HTTP 200 → transitions directly into the Provisioning screen (Ref: UC_2.8.4 — "Setting up your trading floor" animated interstitial). The user is held on this screen until BOTH the SIM provisioning and Auth0 account creation streams have completed successfully (Background Order: SIM Account → Gate → Auth0 Account). When the session token arrives, the frontend redirects the user to the Auth0 login screen (Ref: UC_3.1).
  7. Provisioning failure handling (SIM or Auth0) is delegated entirely to the backend auto-retry mechanism — Ref: UC_2.8.2 §Step 3 Exception and §5 Exceptional Flow below. No frontend-visible retry action exists on this screen; there is no user-facing timeout.

5. Exceptional Flow

  • [Claim link invalid]

    Trigger: the link is invalid (e.g. user changes the token). Distinct from "Claim link expired" below (a valid token that has simply passed its expiry timestamp).

    1. Frontend renders the full-page "Invalid Link" screen. Ref: FP-05.
    2. No [Resend Activation Link] option.
  • [Auth0 account creation failure]

    1. FE overlay renders: Ref: OV-07.
    2. Backend commits the transaction_id state so the failure is not silently lost, fires a Freshdesk ticket for manual account creation, and dispatches the same client-facing "tech support notified" email described in Ref: UC_2.8.2 §Step 3 Exception.
    3. Resolved via the Break-Glass Manual Account Provisioning Runbook (Ref: Break-Glass Manual Account Provisioning Runbook) — Ops manually invokes Auth0 user creation and sends the user a manual activation link.
  • [Clicking Activate Account twice]

    Trigger: the user clicks [Activate Account] twice in close succession — e.g. the same claim link is opened in two browser tabs and both submit before the first has finished. Distinct from BR_2.8.3.1 — that rule covers re-clicking the SAME link AFTER an earlier request already completed with HTTP 200 (sequential); this case covers two clicks racing while the first is still processing.

    1. The second click's request is rejected with HTTP 409 Conflict, even though the first click hasn't finished processing yet.
    2. The rejected tab does NOT transition into the Phase 3 "Setting up your trading floor" interstitial (Ref: UC_2.8.4) — it stays on the current Phase 2 "Create an Account" screen and renders banner Ref: BN-08.
    3. The first click's tab is unaffected — it continues processing normally and proceeds into the Phase 3 interstitial (Ref: UC_2.8.4) as soon as it resolves.
  • [Claim link expired — 48 h] [CHR-10]

    Precondition: this flow only triggers when credentials_claimed_at IS NULL (per the reordered checks in §4 step 2 above). If the account is already claimed, that check wins regardless of whether the JWT is expired or not — the user is redirected straight to Auth0 login + banner (§5 Case 2 / BR_2.8.3.3) instead, and this "This link has Expired" screen never renders.

    1. JWT expiry validation fails (and account not yet claimed) → "This link has Expired" screen renders.
    2. Screen: Headline "This link has Expired" + Alert "Activation links are valid for a limited time. Click below and we'll send you a new one instantly." + [Resend Activation Link].
    3. User clicks [Resend Activation Link] → frontend calls POST /public/resend-activation-link (public, no auth), passing the (expired) token so the backend can resolve user_id without a fresh email input.

    There are two situations where the end-user is the one acting (opening or resending the link), plus one where Ops is the one acting (resend via BPS). They hit different endpoints and behave differently once the account is already claimed:

    #CaseWho triggersEndpointAuthIf account NOT yet claimedIf account ALREADY claimed
    1End-user self-service resendEnd-user, clicking [Resend Activation Link] on the expired-link screenPOST /public/resend-activation-linkPublic, no authBackend asynchronously verifies the token-resolveduser_id still has credentials_claimed_at IS NULL, generates a fresh 48 h JWT, and dispatches the email via AWS SES (SES-03). No BPS_Audit_Log entry or RESEND_WELCOME webhook — those are Admin-flow-only (Case 3), since this endpoint has no admin_id/reason to log and no BPS caller to notify. Frontend already showed the generic success state in step 4 below.Anti-Enumeration: backend still returns the same generic HTTP 200 / success message — but silently drops the request. No email is sent, no error, and the frontend is never told the account is already claimed.
    2Link-open pre-check (not a resend — triggered by opening the link itself, not the [Resend] button)End-user, opening a "Claim Your Account" link, regardless of whether the JWT is still valid or already expiredN/A — detected when the link is opened, before any resend happensN/ANot exceptional in this state —credentials_claimed_at IS NULL means the link just proceeds through the normal Basic Flow (§4 above): valid JWT → "Create an Account" screen; expired JWT → "This link has Expired" screen (§5 first bullet).This is what makes the case exceptional:credentials_claimed_at IS NOT NULL — the account is no longer claimable, whether the JWT is still valid or has already expired. The browser does not redirect straight to login — it first renders the Phase 3 provisioning interstitial (Ref: UC_2.8.4 §5), which re-checks Auth0 + SIM provisioning status: if both are done, the interstitial dissolves and redirects to the Auth0 login screen with a persistent banner "You have already claimed this account, please login" (Ref: BN-05); if either is still pending, the interstitial keeps loading; if provisioning is detected as failed, overlay OV-07 ("Account Creation Failure") renders and the failure flow (Ref: UC_2.8.4 §6) takes over. Governed by BR_2.8.3.3.
    3[ADMIN FUNCTION - NOT IMPLEMENT IN THIS MILESTONE YET] Admin resend via BPSOps admin, via Back-office Portal (BPS)POST /resend-welcomeAdmin JWT requiredBackend generates a fresh 48 h JWT, dispatches Email, returnsJSON { "status": "success" }.Backendblocks execution and returns an explicit error: "Account is already active. Direct user to standard login screen — enter email to trigger Auth0 OTP." No anti-enumeration constraint on this endpoint — the caller is an authenticated Ops admin, not an anonymous end-user, so there's no email-guessing risk to protect against (BA rationale — not explicitly stated in the CR).

    Continuing the end-user self-service resend flow (Case 1 above), step by step:

    1. Anti-Enumeration: the backend ALWAYS returns an immediate HTTP 200 OK, regardless of whether the underlying account exists or is already claimed. On HTTP 200 → frontend renders the success state: "Activation Link Sent. Please check your email inbox (and spam folder) for your new secure link."
    2. Asynchronously (after the 200 has already been returned), the backend resolves the account and checks credentials_claimed_at — see the "NOT yet claimed" vs. "ALREADY claimed" columns of the Case 1 row above for the two outcomes.

6. Business Rules

The claim link is single-use. It is invalidated immediately when POST /claim-account returns HTTP 200. A second click on the same link renders the expired link screen.

Scope [[CHR-10], revised]: Covers only Case 2 in §5's table — opening a claim link that's already claimed (credentials_claimed_at IS NOT NULL), regardless of JWT expiry. Since already-claimed is checked before JWT expiry (§4 step 2), it takes priority in both sub-scenarios. Does not apply to Case 1 (public resend — always returns a generic HTTP 200, Anti-Enumeration) or Case 3 (Admin resend via BPS — returns an explicit error instead); see §5 for how those two handle it.

Rationale: credentials_claimed_at is set at the moment POST /claim-account returns HTTP 200 (Ref: §3 Invalidation row) — this happens BEFORE the SIM provisioning + Auth0 account creation streams are guaranteed to have finished (Ref: §4 step 6, Background Order: SIM Account → Gate → Auth0 Account). So "already claimed" does not by itself guarantee the account is fully provisioned yet.

Rule: On link-open, if credentials_claimed_at IS NOT NULL, the frontend does NOT redirect straight to the Auth0 login screen. It instead renders the Phase 3 "Setting up your trading floor" interstitial (Ref: UC_2.8.4, resumed-entry path) — instead of "Create an Account" or "This link has Expired". The interstitial re-checks Auth0 + SIM provisioning status:

  • Both done → interstitial dissolves → redirect to the Auth0 login screen with banner Ref: BN-05. Not a dead end: successful login lands directly on the Dashboard (Ref: UC_2.8.4).
  • Either still pending → interstitial keeps loading (same visual-mask behavior as the fresh-claim entry path).
  • Provisioning detected as failed → overlay OV-07 renders and the failure flow takes over (Ref: UC_2.8.4 §6).

BR_2.8.3.2: Account Verification Status

The system formally considers a user's account fully created, provisioned, and claimed only when BOTH of the following database conditions evaluate to true: platform_username IS NOT NULL AND credentials_claimed_at IS NOT NULL. Ref: STAGE1-102.

BR_2.8.3.4: Phone Number Uniqueness

The phone number entered must be unique across the system. If the user enters a phone number that is already associated with another account, the system rejects it.

  • Comparison basis: the full E.164 string — dial code plus number (Ref: CR-09 §9.2). Two numbers are only treated as a duplicate when BOTH the dial code AND the digits match — the check is scoped to the same country. Re-selecting a different dial code while keeping the same digits (e.g. +1 5551234567 vs. +44 5551234567) is NOT a duplicate.
  • Trigger: Phone Number field onBlur or on form submit, AND every time the Country Code dropdown is re-selected (Ref: §7 Screen Description row 4) — a dial code change re-runs this check against the newly combined value.
  • Error Display: Inline error Ref: IN-06 is displayed below the Phone Number field. The [Activate Account] button is disabled until a unique phone number is provided.

BR_2.8.3.5: SIM Provisioning Failure Is Silent on Phase 2

If the asynchronous SIM account creation (POST /provision-sim-user, kicked off in §5 step 3) fails while the user is still on this Phase 2 "Create an Account" screen, nothing is displayed here. The user proceeds through the Basic Flow exactly as normal — entering their phone number and clicking [Activate Account] regardless of the SIM outcome. The failure only becomes visible once the user reaches the Phase 3 provisioning interstitial, where overlay OV-07 renders (Ref: UC_2.8.4 §6).

7. Screen Description

No.Field NameField TypeValidation Rule / Behaviour
1Asset Class badgeBadgeDisplay rule: "Futures" or "Forex" per user selection. Read-only.
2Headline (H2)Static Text"Create an Account"
3Sub-headlineStatic Text"Enter your phone number to activate."
4Phone — Country CodeDropdown (Single-selection)Ref:CR-09 §9.2. Default: +1. Behaviour: On change → re-runs the Duplicate Phone Number Check against the newly combined dial code + number (Ref: BR_2.8.3.4). Same-country scope: a different dial code with identical digits is not a duplicate.
5Phone — NumberText InputRef:CR-09 §9.2. Validation: Must be unique. If duplicate, display Ref: IN-06. Trigger + same-country scope: Ref: BR_2.8.3.4.
6Micro-copyStatic Text"We require your phone number to enable Two-Factor Authentication (2FA) and coordinate emergency Drawdown Defense protocols as you scale."
7Body copyStatic Text"Once submitted, your StackTrading account and trading credentials will be set up automatically. You'll be on the trading floor in moments."
8[Activate Account]Button (Primary)Validation: Disabled until phone number filled. Behaviour: On click → POST /claim-account.
9[Resend Activation Link]Button (Primary)Display rule: Visible on expired link screen only. No email input on this screen — the expired token itself identifies the account. Behaviour: On click → Ref: Exceptional Flow: Claim link expired — 48 h.
10Resend success stateStatic Text + ButtonDisplay rule: Replaces form after HTTP 200. Copy: "Resend email successfully". Button: [Return to Homepage] → homepage.
11Invalid Link screenFull-page blockDisplay rule: Renders when the link is invalid (e.g. user changes the token). No resend option. Ref: FP-05.

UC_2.8.4 — Phase 3 — Provisioning & Redirect to Login

1. Overview

FieldContent
IDUC_2.8.4
Use CasePhase 3 - Provisioning & Redirect to Login
DescriptionReached via two entry paths: (1) fresh entry — after POST /claim-account returns HTTP 200 (Ref: UC_2.8.3 §4); (2) resumed entry — opening an already-claimed "Claim Your Account" link (Ref: UC_2.8.3 BR_2.8.3.3). In both cases the "Setting up your trading floor" animated interstitial (4-step sequence) plays as a visual mask while the frontend checks Auth0 + SIM provisioning status. When both are confirmed done, the interstitial dissolves and the frontend redirects the user to the Auth0 login screen (Ref:UC_3.1). If provisioning is detected as failed, overlay OV-07 renders instead (Ref: §6).
Zapier Flow
Zapier Table
3rd PartyAuth0

References/Wireframe/Stage 1/Step 7 Phase 3_ provisioning.png Phase 3 — Provisioning interstitial (in-progress)

2. Trigger

Either of two triggers:

  1. POST /claim-account returns HTTP 200 (fresh entry, from UC_2.8.3 §4).
  2. User opens a "Claim Your Account" link where credentials_claimed_at IS NOT NULL (resumed entry, Ref: UC_2.8.3 BR_2.8.3.3).

3. Pre-conditions

  • Either: POST /claim-account returned HTTP 200 (fresh entry); or: the opened claim link resolves to an account with credentials_claimed_at IS NOT NULL (resumed entry).
  • Session token is being provisioned by the backend (fresh entry), or Auth0/SIM provisioning status is being re-checked (resumed entry).

4. Post-conditions

  • Session token received → interstitial dissolves → user is redirected to the Auth0 login screen (Ref: UC_3.1).
  • If provisioning genuinely fails after the backend auto-retry mechanism is exhausted (Ref: UC_2.8.2 §Step 3 Exception), overlay OV-07 renders on the interstitial screen and the SES-23 email is dispatched (Ref: §6 Exceptional Flow). No user-facing retry action exists.

5. Basic Flow

  1. Entry: either POST /claim-account returns HTTP 200 (fresh entry), or the user opens an already-claimed claim link (resumed entry, Ref: UC_2.8.3 BR_2.8.3.3).
  2. Full-screen interstitial renders: "Setting up your trading floor" + "Initialising secure infrastructure and cryptographic protocols".
  3. Four-step animated sequence plays (see §3).
  4. Frontend holds on this screen until Auth0 account creation AND SIM provisioning are both confirmed complete:
    • Both done → session token received → interstitial dissolves → frontend redirects to the Auth0 login screen (Ref: UC_3.1).
    • Either still pending → interstitial keeps loading; no timeout (Ref: BR_2.8.4.1).
    • Provisioning detected as failed → see §6 Exceptional Flow.

4-Step Animated Sequence

#Step LabelInitial Visual State
Cryptographic Hash terminal blockAnimated fake terminal output (hash strings)
1Connecting to execution gateway✓ Completed (gold check)
2Verifying cryptographic signatures· · · In progress (animated, gold)
3Deploying risk management protocolsPending (greyed)
4Account activated, entering the trading floor.Pending (greyed)

6. Exceptional Flow

  • [Provisioning fails after backend auto-retry exhausted]

    The frontend has no timeout on the interstitial's wait for Auth0 + SIM completion (whether entered fresh via POST /claim-account or resumed via an already-claimed link, Ref: UC_2.8.3 BR_2.8.3.3) — it holds indefinitely until both are confirmed done. There is no user-facing [Retry] button and no idempotent re-fire triggered from this screen. This applies even if the SIM failure originated earlier, back on Phase 2 (Ref: BR_2.8.3.5) — it stays silent until surfaced here.

    If SIM provisioning or Auth0 account creation genuinely fails, failure handling is delegated entirely to the existing backend auto-retry mechanism:

    In both cases, once the backend exhausts its retries and treats the failure as "Manual": it fires a Freshdesk ticket for manual account creation and dispatches Ref: SES-23 to the user. The frontend, still holding the interstitial, then renders overlay Ref: OV-07Header: "Account Creation Failure" Body: "Payment Successful. We are experiencing a temporary delay generating your secure dashboard login. Our Operations team has been alerted and will email your access link shortly." — with no retry action available on screen. Resolution proceeds via the Break-Glass Manual Account Provisioning Runbook (Ref: Break-Glass Manual Account Provisioning Runbook).

7. Business Rules

BR_2.8.4.1: Interstitial Is Visual Mask Only

If the session token arrives before the 4-step animation completes, the interstitial dissolves immediately. It does not block the transition.

BR_2.8.4.2: Redirect to Login After Provisioning

When the session token arrives and the interstitial dissolves, the frontend redirects the user to the Auth0 login screen (Ref: UC_3.1). The system does NOT auto-login the user directly into the Dashboard.


Changelog

DateVersionUpdated itemBeforeAfterNotes
2026-08-27v4.2UC_2.8.2 §2 Step 3 (Provisioning Branch) + Step 3 Exception — Rithmic country mapping, no US defaultStep 3's Rithmic-routed branch had no country mapping or validation logic.Added rule: country must be mapped to Rithmic's exact accepted value before the Rithmic API is called. Added new error type "Country Mismatch" to Step 3 Exception: hard failure, HALT, no automated retry — system must never default to "United States" or any other country.Client directive (chat), 2026-08-27 — client explicitly rejected any country-to-US fallback as a compliance/regulatory risk. ⚠️ Rithmic's exact accepted-country-value list is not present in any repository source — flagged for Ops/Dev to obtain directly from Rithmic.
2026-08-27v4.1UC_2.8.3 BR_2.8.3.4, §7 Screen Description rows 4/5 — Phone Uniqueness re-check on dial code change, same-country scope clarifiedTrigger only covered Phone Number field onBlur/submit; no rule fired the duplicate check when the user re-selected the Country Code dropdown. Scope of "duplicate" was not explicitly tied to the E.164 dial-code+number pair, leaving ambiguity about cross-country digit collisions.Added trigger: Country Code dropdown change now re-runs the Duplicate Phone Number Check. Clarified comparison basis: check is scoped to the same country — both dial code AND digits must match to count as duplicate; a different dial code with identical digits is not a duplicate. Screen Description rows 4/5 cross-referenced.Client directive (chat), 2026-08-27.
2026-08-24v4.0UC_2.7.1 §3 Pre-conditions, §5 Basic Flow step 1 — F5 reload during payment processing no longer renders OV-05§3 Pre-conditions cross-referenced BR_2.1.1.3 to indicate OV-05 renders on payment-in-progress reload. §5 step 1 listed two independent overlay conditions: (1) payment in progress → OV-05; (2) email locked → OV-03.Pre-condition bullet removed. §5 step 1 now evaluates one overlay condition only: email locked → OV-03. An explicit note added: F5 reload during payment processing routes directly to the correct final state — no OV-05 re-rendered. Rationale: payment is a single continuous backend flow; backend idempotency enforced via provider_event_id (Ref: UC_2.8.1 §5 step 6.0); NMI transactions normally < 30 s. Companion change: UC_2.1-2.6_v1.md v19 (BR_2.1.1.3 Table 1/2/3 Step 6 updated).Client directive (chat), 2026-08-24.
2026-08-20v3.9UC_2.8.2 §2 Step 4 Founder Cohort check, UC Index — is_founder derivation changedis_founder set by reading Global_Var_Founder_Cohort_Open at Flow 1 Step 4.is_founder now set by comparing the price charged for the transaction against the Founder Price for the user's tier (Table J).Client decision (relayed by user, BA session), 2026-08-20. Ref: [CHR-53].
2026-08-20v3.8CR note (top), §5 input table (UC_2.8.1), §5 step 6.4 (UC_2.8.1), UC_2.8.2 §Step 4 Database Write — everflow_click_id renamed; note added distinguishing this write from the Step 6 Attribution writeField name everflow_click_id used inconsistently with UC_2.1-2.6_v1.md (everflow_transaction_id). Flow 1 Step 4 DB Write table (no UTM columns) sat right after the input table with no note explaining why UTM isn't repeated there.Renamed everflow_click_ideverflow_id (4 locations) for consistency with UC_2.1-2.6_v1.md. Added a note above the Flow 1 Step 4 table clarifying that UTM (utm_source/utm_medium/utm_campaign/utm_term/utm_content) is persisted separately, synchronously, inside POST /execute-checkout (UC_2.8.1 §5 step 6.4) — Flow 1 Step 4 is a later, post-payment write and does not repeat it, per source (Zapier Integration V7.pdf, Flow 1 Step 4).Audit finding — BA session, 2026-08-20. Field rename per user directive; deviates from QnA STAGE1-061's exact wording (everflow_transaction_id) — flagged, not silently applied.
2026-08-19v3.7UC_2.8.3 §4 step 2, §5 Case 2 table, BR_2.8.3.3; UC_2.8.4 §1/§2/§3/§5/§6, UC Index — already-claimed link now re-enters provisioning interstitial before loginOpening an already-claimed claim link (credentials_claimed_at IS NOT NULL) redirected straight to the Auth0 login screen with BN-05 banner, regardless of whether Auth0/SIM provisioning had actually finished — a gap, since credentials_claimed_at is set at POST /claim-account HTTP 200, before the SIM+Auth0 streams are guaranteed complete.Already-claimed link-open now renders the Phase 3 interstitial first (resumed entry path). Interstitial re-checks Auth0 + SIM status: both done → redirect to login + BN-05; either pending → keep loading; provisioning failed → OV-07 "Account Creation Failure" popup + failure flow. BR_2.8.3.3 rewritten with rationale. UC_2.8.4 widened to document both entry paths.Client directive (chat), 2026-08-19.
2026-08-18v3.6UC_2.8.3 §5 (new exceptional flow case) — clicking Activate Account twiceNo rule covered clicking [Activate Account] twice before the first click's request finished (e.g. same link opened in two tabs); only BR_2.8.3.1 (sequential re-click AFTER success) existed.The second click is now rejected with HTTP 409. That tab does not render the Phase 3 interstitial — it stays on Phase 2 and shows banner BN-08. The first click's tab is unaffected.Client supplement (chat), 2026-08-18.
2026-08-17v3.5UC_2.7.1 §5 Group C, UC_2.7.7, UC_2.7.9, UC_2.8.1 §5 outcome 7c/step 8.2, UC Index — Dusupay/T365 redirect flow now shows OV-05Redirect flow (Dusupay, T365) explicitly showed no OV-05 processing overlay on the checkout tab while the external tab was open — only a background WebSocket listener ran silently.OV-05 now renders on the checkout tab immediately after the CTA opens the external tab, and holds until PAYMENT_RESULT (success/failure) or the general 10-minute frontend timeout (Ref: UC_2.8.1 §5 step 8.1) — same processing-overlay treatment as Group B methods. Step 8.2's "no-op for 7c" exception removed.Client supplement (chat), 2026-08-17.
2026-08-15v3.4UC_2.8.3 §5 step 3, new BR_2.8.3.5, UC_2.8.4 §6 — SIM provisioning failure occurring on Phase 2 is silentNo rule stated what happens if the asyncPOST /provision-sim-user (kicked off at §5 step 3, while the user is still entering their phone number on Phase 2) fails before the user even submits the form — gap flagged during OV-07/BN-06 consolidation.AddedBR_2.8.3.5: a SIM provisioning failure on Phase 2 shows nothing; the user proceeds through the Basic Flow normally. The failure only surfaces once the user reaches the Phase 3 interstitial, where overlay OV-07 renders (Ref: UC_2.8.4 §6). §5 step 3 and §6 Exceptional Flow cross-referenced to the new rule.Client directive (chat), 2026-08-15.
2026-08-15v3.3UC_2.8.4 §4/§6 — provisioning-failure UI consolidated to OV-07 (BN-06 removed)UC_2.8.4 §4/§6 pointed provisioning failure (SIM or Auth0, post-retry-exhaustion) at banner BN-06, while UC_2.8.3 §5 pointed Auth0 failure at overlay OV-07 for the same underlying failure — two different UI treatments for one case. OV-07 header was also*"Payment Successful"* (the body's own opening line promoted to the header), misleading for a failure state (QC-flagged, GAP-P2-05).UC_2.8.4 §4/§6 changed to render OV-07 (not BN-06) on the interstitial.list-toast-popup.md: OV-07 header changed to "Account Creation Failure" (body unchanged — still opens with "Payment Successful."); trigger description widened to explicitly cover both UC_2.8.3 §5 (Auth0 failure) and UC_2.8.4 §6 (Phase 3 interstitial SIM/Auth0 failure) as one case; BN-06 row removed.Client directive (chat), 2026-08-15.
2026-08-14v3.2UC_2.8.3 §1/§4/§5/§7, list-toast-popup.md FP-05 — new "Invalid Link" caseOnly "This link has Expired" (JWT time-expiry) and BN-05 "already claimed" were covered; a link with an invalid/unresolvable token (e.g. tampered) had no defined screen.Added §4 step 2 link-validity check (runs before claimed-status/expiry checks); new §5 "[Claim link invalid]" case; new §7 Screen Description row 11; new FP-05 full-page block ("Invalid Link", no resend option).BA gap analysis, 2026-08-14.
2026-08-09v3.1UC_2.7.1 §5 step 1, UC_2.8.1 §6 5-Failure Email Lock table — email lock overlay on Step 6 navigateStep 6 mount only checked payment-in-progress (BR_2.1.1.3); split-panel did not render while check was pending. Lock table documented "Page reload while locked" but NOT "navigate from Step 5 to Step 6 while locked."Step 6 always renders split-panel on every mount. Two overlay conditions evaluated independently after render: email locked → OV-03 on top; payment in progress → OV-05 on top. Lock table: added "Navigate to Step 6 while locked" row; revised "Page reload" wording to reflect split-panel renders first. UC_2.7.1 §5 step 1 rewritten accordingly.BA gap analysis, 2026-08-09.
2026-08-08v3.0UC_2.8.1 §5 step 6.0, §6 HTTP 409, new BR_2.8.1.5 — Dedup Check rewritten to clearly separate "payment succeeded" from "payment failed / still in progress"Dedup Check and BN-07 text saidprovider_event_id could mean the payment was "in-flight," which contradicts the schema (it's only written on confirmed success, Ref: UC_2.8.2 §2 Step 4). No rule explicitly said a failed or still-in-progress duplicate is allowed to retry.Rewrote step 6.0 and §6 HTTP 409 in plain terms:provider_event_id IS NOT NULL = payment already succeeded. Added BR_2.8.1.5: a failed or still-in-progress prior payment does NOT block retry (including after a 10-min timeout or Step 5→Step 6 back-navigation); only a succeeded prior payment blocks via HTTP 409/BN-07.Client supplement (chat), 2026-08-08.
2026-08-08v2.9UC_2.8.1 §5 step 4/6.1.a/6.1.b, BR_2.7.8.1, BR_2.7.8.6 — Promo reservation moved to AFTER price/tax check; tax change added as OV-08 trigger; [Refresh now] behavior spec'dStep 4 reserved promo slot BEFORE price/tax check — risked wasting a slot if price was stale. OV-08/BR_2.1.5.2 only listed Founder cohort sold out as trigger; tax rate change not covered. [Refresh now] button behavior not spec'd.Promo reservation (pessimistic lock) moved to step 6.1.b — only after price/tax check (step 6.1.a) passes.PRICE_CHANGED now triggers on both Founder cohort sold out AND tax rate change. [Refresh now]: closes OV-08 → refreshes Order Summary with updated price/tax (no page reload). BR_2.7.8.1, BR_2.7.8.6, step 6.1.a, step 6.1.b updated accordingly.BA directive 2026-08-08.
2026-08-07v2.8Phase 3 changed from "Dashboard Transition" (auto session-token → Dashboard reveal) to "Provisioning & Redirect to Login": provisioning interstitial unchanged, but session token arrival now redirects to Auth0 login (Ref:UC_3.1) instead of auto-revealing Dashboard. UC_2.8.3 §4 step 6, UC_2.8.4 §1–§5, §7 (BR_2.8.4.1 removed, BR_2.8.4.2 → BR_2.8.4.1, new BR_2.8.4.2), BR_2.1.1.3 Table 1 Step 7.3 updated.Session token → interstitial dissolves → Dashboard renders. BR_2.8.4.1: URL bar immediately updates to dashboard domain. BR_2.8.4.2: Interstitial dissolves if session token arrives before animation completes.Session token → interstitial dissolves → redirect to Auth0 login. BR_2.8.4.1 (new): Interstitial dissolves immediately if session token arrives before animation completes (unchanged logic, renumbered from.2). BR_2.8.4.2 (new): Redirect to login after provisioning — system does NOT auto-login. BR_2.8.4.1 (old, URL update) removed.Client supplement, 2026-08-07.
2026-07-26v1Initial documentUC_2.7.1–UC_2.7.8, UC_2.8.1–UC_2.8.4Init Flow — Architect (Agent 3) output. UC IDs aligned to WBS.
2026-08-10v3.1Triple-A pending-removal annotationNo annotation presentAdded "(pending)" at UC_2.7.1, UC_2.7.5, UC_2.8.1, UC_2.8.2 — no logic deletedCR-20260810-001
2026-08-10v3.2UC Index, TOC, UC_2.7.1 §5 (Group B/C), UC_2.7.7 (rebuilt), new UC_2.7.9, UC_2.8.1 §1/§5/§6, UC_2.8.2 §2 Refund Mechanism — Dusupay redirect flow + new T365 method; Nomupay/Nuvei removedUC_2.7.7 = "Regional Payment Aggregators" (Nomupay/Nuvei/Dusupay, vendor popup + WebSocket, Group B). No T365. Refund Mechanism listed Nomupay/Nuvei as Reversible.UC_2.7.7 renamed to Dusupay-only, rebuilt on new external-tab-redirect pattern (3 sub-methods: Mobile_money/Bank/Card, Hosted Page). New UC_2.7.9 (T365) added, same redirect pattern. New Group C (external tab redirect, no OV-05, background WebSocket listener) added to UC_2.7.1 §5, distinct from Group B (3rd-party modal). New outcome 7c added to UC_2.8.1 §5 for the redirect pattern. Nomupay and Nuvei removed from all lists (UC Index, TOC, Group B, 3rd Party fields, Routing Execution, 7b heading, step 8.2, Refund Mechanism).Client supplement (chat), 2026-08-10. WBS intentionally NOT updated per client instruction — BA to add UC_2.7.7/UC_2.7.9 rows to WBS separately.
2026-07-28v1.1UC_2.8.2 §2 — Blacklist Check & Duplicate/Existing-Account rejection removedZapier Flow 1 ran a "Blacklist Check (Fail-Safe)" (query Blacklist table, refund on match) and a "Duplicate / Existing-Account Cases" table that rejectedActive_Challenge/Active_DMA/Guest via SES-21/SES-22.Removed both. Blacklist and Duplicate Account checks are hard-blocked pre-payment at Step 5onBlur (Ref: CR-20260727-003, UC_2.6.2 §5); client confirmed the payment gateway is never pinged for these cases, so the post-payment refund/reject logic is dead code. Failed/Terminated archive-and-reset logic (re-purchase eligibility) is unrelated and retained.Client confirmation, 2026-07-28: "you can safely remove the Zapier refund/rejection flows for these cases since we are hard-blocking them before the payment gateway is ever pinged."
2026-07-28v1.2UC_2.8.2 §2 — Existing-Account Cases table, Failed/Terminated re-entry pricing correctedFailed/Terminated rows said re-entry is "Allow purchase at standard Challenge Price (Table J)" — did not reflect the founder-lock pricing branch.Corrected to matchPOST /purchase-challenge-reset and POST /initiate-rebuy logic (Source: RFQ_ Stack Trading Prop Tech V7.pdf, pp.25-27): Failed → Reset Price (Table J Futures/Forex Reset Price column, or grandfathered locked_reset_price if is_founder = TRUE) — not full Challenge Price. Terminated → Challenge_Price × (1 − Retry_Discount), or grandfathered locked_rebuy_price if is_founder = TRUE.BA correction — pricing branch was missing from the Flow 1 table despiteis_founder/locked_rebuy_price/locked_reset_price already being set at Step 4 (line ~893).
2026-07-28v1.3UC_2.8.2 §2 Email Flow table — Email 2 trigger; UC_2.8.3 §3 Link SpecificationEmail 2 trigger listed only "After SIM provisioning completes." Link spec did not distinguish JWT expiry from the Step 7 screen's own (non-)expiry.Email 2 trigger now explicit: "After SIM provisioning completes (POST /provision-sim-user returns HTTP 200) AND Zapier Flow 1 (Step 8 Communication) is completed." Link Specification clarified: the JWT stays valid until its 48h expiry even if the tab is closed mid-entry; the Step 7 Claim Account screen itself (reached organically, not via email link) has no expiration timer.Source: QnA STAGE1-091 (confirmed).
2026-07-28v1.3UC_2.8.3 §4 Basic Flow — provisioning step orderSteps 3-6 implied a fully sequential flow: enter phone → Activate Account →POST /claim-account → Provisioning screen, with no stated relationship between SIM provisioning and Auth0 account creation timing.Rewrote steps 3-6: SIM account creation (POST /provision-sim-user) starts asynchronously in the background as soon as the screen renders, in parallel with the user entering their phone number; Auth0 account creation is strictly gated to run only after phone number submission; the Provisioning screen holds the user until both streams complete (Background Order: SIM Account → Gate → Auth0 Account).Source: QnA STAGE1-092 (confirmed).
2026-07-28v1.4[CHR-20] — Promo Code Pessimistic Locking & Usage LogBR_2.7.8.1 described marking a promo code "used" without specifying exact timing or rollback behavior; no audit table existed for successful promo usage.Added Pessimistic Locking:current_usage_count is incremented on [Pay] CTA click (before the gateway call) and rolled back on payment failure; new promo_code_usage_log table (log_id, user_id, promo_code_id, transaction_id, time_used) is written only on payment success. Updated BR_2.7.8.1, the promo_codes schema Notes cell, BR_2.7.8.6, and inserted the reservation step (step 4) and rollback sub-step (8.5) into UC_2.8.1 §5 Basic Flow.Source: [CHR-20] (Promo Code Pessimistic Locking & Usage Log Table).
2026-07-28v1.5[CHR-9] — Failed Payment Lockout changed from IP to EmailUC Index,UC_2.8.1 §3/§4/§6, BR_2.8.1.2, and UC_2.7.5 §4 all keyed the 5-failure/10-min payment lockout off the user's IP address ("IP block", "global IP block").Reworded all of the above to key the lockout off theemail address entered in the checkout form instead of IP, since IP-based blocking unfairly punishes users on shared networks; added a note that the NMI gateway independently handles its own IP-based velocity blocking regardless of this rule. Section renamed "5-Failure Email Lock."Source: [CHR-9] (Failed Payment Lockout — Block by Email instead of IP), formalized from QnA STAGE1-100.
2026-07-28v1.5UC_2.8.3 §5 "Claim link already claimed" — banner copyReferenced [BN-05] with no copy discrepancy called out at this call site.No change needed at this call site — copy source-of-truth fix applied directly at [BN-05] inlist-toast-popup.md (see that file's changelog): "You have claimed this account, please login.""You have already claimed this account, please login."Source: QnA STAGE1-101 (confirmed).
2026-07-28v1.5UC_2.8.3 §6 Business Rules — newBR_2.8.3.XNo explicit technical definition existed for when an account is considered fully created, provisioned, and claimed.AddedBR_2.8.3.X: Account Verification Status — an account is fully provisioned and claimed only when both platform_username IS NOT NULL AND credentials_claimed_at IS NOT NULL.Source: QnA STAGE1-102 (confirmed).
2026-07-28v1.5UC_2.8.1 §5 "Sanctions & Pricing" sub-step — price/cohort check ordering; BR_2.7.8.6 priority noteSanctions/Pricing/Tax re-check was a single unordered bullet; no defined precedence between a Step 6 price change (Founder cohort sold out) and promo code re-validation failing on the same click.Split into an explicit 3-step order: (1) price/cohort check first — on mismatch, shows Ref: OV-08 and skips promo re-validation entirely for that attempt; (2) promo code re-validation; (3) remaining sanctions/tax checks. Added a "Priority vs. price-change check" note toBR_2.7.8.6 confirming the price-change rejection takes priority and the promo error will surface on the next attempt if still invalid.BA feedback — resolves an ambiguous precedence gap found during audit; new popup added as OV-08 (seelist-toast-popup.md changelog) since OV-07 was already in use for the Auth0 account-creation-failure case.
2026-07-28v1.6Post-Payment Restricted Region refund logic — consolidated to one location;BR_2.7.5.2, UC_2.8.2 §2 Refund MechanismRefund logic was duplicated:BR_2.7.5.2 (UC_2.7.5) restated the full Reversible/Irreversible backend refund rule, while UC_2.8.2 §2 (the canonical backend-only Flow 1 location) held a separate, out-of-date copy still listing Skrill (removed by [CHR-23]) and EBANX (replaced by Nuvei per CR-20260720-002) as Reversible methods, and omitting Apple Pay / Google Pay even though both route through NMI like CC.BR_2.7.5.2 now only cross-references UC_2.8.2 §2 instead of restating the rule. UC_2.8.2 §2 Refund Mechanism corrected to list Reversible methods as CC/Apple Pay/Google Pay (via NMI) and Nomupay/Nuvei/Dusupay (regional aggregators) — Skrill and EBANX removed; refund lookup mechanics (Gateway_Source, POST /issue-refund, invoice status = 'refunded') added per v7_full.txt Issue Refund endpoint spec. Irreversible (Crypto) path unchanged, still Ref: [CHR-8].BA audit — cross-checked against_SOURCE_MANIFEST.yaml, zapier_v7_full.txt, v7_full.txt (Issue Refund spec, p.23), [CHR-23] (Skrill removed), CR-20260720-002 (Nuvei replaces EBANX), [CHR-8].
2026-07-28v1.7[CHR-10] — Resend Activation Link split into Admin vs Public endpointUC_2.8.3 §5 "Claim link expired" had the end-user's [Resend Activation Link] button calling POST /resend-welcome — an Admin-only endpoint (per [CHR-11]) — with no email input; backend response differed depending on credentials_claimed_at (redirect-to-login banner if already claimed), leaking claim status to an anonymous caller.Split into two endpoints:POST /resend-welcome (Admin-only, JWT auth, via BPS — unchanged, now explicit) and new POST /public/resend-activation-link (public, no auth) for the end-user self-service flow. The public endpoint always returns HTTP 200 immediately (Anti-Enumeration) regardless of account existence/claim status; verification and email dispatch happen asynchronously and silently drop on invalid/already-claimed. BR_2.8.3.3 scope narrowed to the "link-open pre-check" case only (no longer applies to the resend path). Updated UC_2.8.3 §1 Overview, §5 Exceptional Flow, BR_2.8.3.3.Source: [CHR-10] (client chat, 2026-07-28). Open question: whether the public endpoint's input is the resolved token (current doc) or a user-typed email (per CR wording) — left as-is (token-based) per BA decision pending client confirmation; seeReferences/CR/2026-07-28_CR10-2_resend-activation-link-split/CR_summary.md Open Questions.
2026-07-29v1.8UC_2.8.1 §5 step 6.1 — accepted race condition at the Founder cohort boundaryNo explicit statement of expected behavior when multiple users click [Pay] at the exact moment the Founder cohort's 500th slot fills; only the generalPRICE_CHANGED rejection path was documented.Added a note under the price/cohort check confirming the race condition at the 500-slot boundary is accepted as-is (first request to complete wins; no queuing, distributed locking, or rollback logic required to guarantee an exact cutoff).Source: STAGE1-005 (Adrian Stack QnA, confirmed 2026-07-20) — client: "the first API call to execute the checkout wins... do not invest engineering time into building complex queuing or rollback logic for this specific edge case."
2026-07-29v1.9Full-document link/anchor audit — 5 defects fixed: staleCR-11 anchor (§6 Screen Description), missing cross-file path on UC_2.1.1 link (Users table Create Record), dangling BR_2.8.4.3 reference (UC_2.8.4 Post-conditions), credentials_claimed_at Notes typo + outdated single-endpoint guard note, Case 1 resend audit-trail wording(1)#cr-11-usd-currency-display slug did not match the actual common_rules.md heading. (2) [UC_2.1.1](#UC_2.1.1) was a same-file link but UC_2.1.1 is defined in UC_2.1-2.6_v1.md. (3) BR_2.8.4.3 was referenced but never defined — only BR_2.8.4.1/.2 exist. (4) Notes cell read "Aet only when..." (typo for "Set") and cited only POST /resend-welcome as the guard consumer. (5) Case 1 (public self-service resend) described logging BPS_Audit_Log and firing RESEND_WELCOME webhook — those are Admin-flow-only per [CHR-11]/[CHR-10] (no admin_id/reason to log, no BPS caller).(1) Corrected slug to#cr-11-numeric-monetary-value-display. (2) Added cross-file path: ../UC_2.1-2.6_v1.md#UC_2.1.1. (3) Removed the dead reference (no BR_2.8.4.3 exists; behaviour stands undocumented-as-rule). (4) Fixed typo; expanded Notes to list all three guard call sites. (5) Reworded Case 1 to describe async token-based verification + SES-03 dispatch only, with an explicit note on why no audit-log/webhook applies.BA self-audit — systematic anchor/link integrity sweep + Case 1/Case 3 cross-check against [CHR-11], [CHR-10].
2026-07-29v2.0UC_2.7.1 §3 Pre-conditions, §5 Basic Flow step 1 — payment-in-progress check on Step 6 mountPre-conditions did not account for an in-progress payment; step 1 rendered the split-panel layout unconditionally on mount.Added a pre-condition and rewrote step 1 to check for an in-progress payment on every Step 6 mount (arrival, reload, deep-link), cross-referencingBR_2.1.1.3 (UC_2.1-2.6) for the loading/success/fail handling — logic lives centrally in the Session Recovery Matrix, not duplicated here.CR-20260729-002; Client (chat), 2026-07-29. Mechanism for detecting an in-progress payment on remount is still pending confirmation (see CR_summary.md Open Questions).
2026-07-30v2.1UC_2.8.3 §6 Business Rules, §7 Screen Description — Phone Number UniquenessNo uniqueness check specified for phone number on Claim Account screen.AddedBR_2.8.3.4: Phone Number Uniqueness. Added Ref: IN-06 and updated Phone Number field validation in Screen Description to require uniqueness.Phase 2 Account Claim requirement update.
2026-07-30v2.2UC_2.8.3 §4 Basic Flow step 7-8, §5 Exceptional Flow; UC_2.8.4 §1 Overview, §4 Post-conditions, §6 Exceptional Flow — removed 60s provisioning timeout/retryFrontend enforced a max 60-second timeout onPOST /claim-account; on timeout, dropped the loading overlay and rendered a [Retry] button + error banner (wireframe: Step 7 Phase 3_ provisioning - Retry (_60 seconds).png). Retry re-fired the idempotent request, branching on 3 backend outcomes (A — already finished, B — still processing/409, C — actual failure/retry from scratch), with no retry-attempt cap.Removed the 60-second timeout, the [Retry] button, and the wireframe reference entirely. The interstitial now holds indefinitely until the session token arrives. Provisioning failure handling (SIM or Auth0) is delegated to the existing backend auto-retry mechanism (Ref:UC_2.8.2 §Step 3 Exception — 5s/30s backoff, 3 attempts; and UC_2.8.3 §5 Auth0 failure). If all backend attempts fail, the Freshdesk ticket + SES-23 email dispatch is unchanged, and the frontend now shows new banner BN-06  Client confirmed (chat), 2026-07-30 — remove the 60s provisioning timeout/retry mechanism; use existing backend auto-retry as the sole failure-handling path.
2026-07-30v2.3UC_2.7.8 §3 Basic Flow step 4, BR_2.7.8.4 display table, §5 Screen Description Row 5 — Discount line item label now includes the applied codeDiscount line item in Order Summary showed only a generic "Discount" label with the negative dollar amount (−$XX.XX), not which code was applied.Label changed to Promo code (<code>) where <code> is the code as typed by the user (echoed from the input field, not the server response) — e.g. code xZugg17839 renders "Promo code (xZugg17839): −$XX.XX". Does not conflict with STAGE1-070 (frontend still does not receive/render discount type — percentage vs. flat — only the discount_amount value; the code text is already known client-side from the input, no new API field needed).BA update per direct client instruction, 2026-07-30. Not yet backed by a formal CR/QnA entry — recommend logging a CR if this label change needs explicit client sign-off before dev handoff.
2026-07-30v2.4[CHR-21] —UC_2.8.1 §5/§6/BR_2.8.1.1/.3, UC_2.7.8 BR_2.7.8.1 — Payment frontend timeout extended, no permanent lockFrontend gateway timeout was 2 minutes; timeout and genuine decline shared the same failure banner; promo code reservation rolled back immediately on either case.Timeout extended to10 minutes, unified across sync (CC/Apple Pay/Google Pay) and async (Crypto/Bank/Regional Aggregators) methods; no permanent lock — user may retry immediately. Timeout now shows a distinct banner ([BN-01], revised copy) separate from genuine decline. Promo code reservation is not rolled back on timeout (only on confirmed webhook failure). Added new BR_2.8.1.4 for double-payment refund handling by method reversibility.Source: [CHR-21] (client chat, 2026-07-30). SeeReferences/CR/2026-07-30_CR21_payment-timeout-10min/CR_summary.md.
2026-08-02v2.5UC_2.8.2 §Step 4 Users Table, UC_2.8.1 §5 step 6.5 — new last_purchase_date fieldlast_purchase_date did not exist in the Users table schema or POST /execute-checkout workflow.Addedlast_purchase_date (TIMESTAMP, Nullable) to the Users table field list in Step 4. Added new step 6.5 (Post-Success DB Write) to the POST /execute-checkout backend workflow: on confirmed payment success, backend writes Users.last_purchase_date = NOW(). Also written by POST /purchase-challenge-reset and POST /initiate-rebuy (outside this doc).Client supplement, 2026-08-02. Source: Zapier Integration V7.pdf Flow 7B Action 2.
2026-08-02v2.6BR_2.7.8.4 discount types table, BR_2.7.8.5 math block — explicit promo code pricing formulasBR_2.7.8.4 table "How server applies" column used shorthand (Base_Price × rate, Fixed amount subtracted). BR_2.7.8.5 math block did not show how discount_amount is derived from the two promo code types.BR_2.7.8.4: each type now shows the full two-step formula — discount_amount computation + Subtotal = (Base_Price + Addon_Prices) − discount_amount. BR_2.7.8.5: added explicit discount_amount derivation table (absolute amount → promo_code.discount_amount; percentage → Base_Price × promo_code.discount_percentage) before the existing order-total math block. Percentage discount applies to Base_Price only (add-ons not discounted).User supplement, 2026-08-02.
2026-08-06v2.7UC_2.8.1 §1 Overview, §5 step 6.0 (new), §6 HTTP 409 — pre-gateway dedup checkPOST /execute-checkout server-side workflow pinged gateway immediately after Sanctions & Pricing with no idempotency guard — the only dedup was frontend UI-level (CTA disable, BR_2.8.1.1) and post-payment webhook-level (Flow 1 Step 1, UC_2.8.2). HTTP 409 exceptional flow at §6 was dangling — described what happens on 409 but never triggered from any step in §5 Basic Flow; banner copy was vague ("indicating duplicate detected").Added new step6.0 Dedup Check — runs first, before any gateway is pinged. Query Users table for user_id: IF provider_event_id IS NOT NULL → return HTTP 409. HALT. No charge, no gateway call, no promo reservation, no audit PDF. §6 HTTP 409 updated: triggered by step 6.0, now references Ref: BN-07. §1 Overview updated to mention dedup step.Source:Zapier Integration V7.pdf — idempotency requirement ("provider_event_id/event_id enforced for payments", dòng 2700). BA gap analysis 2026-08-06.
2026-07-30v2.4POST /execute-checkout input table (utm_source, utm_medium, utm_campaign, utm_term, utm_content) and §6 step 6.4 "Attribution" — UTM source changed from session state to localStorage per CR-12Input table listed UTM params as "Session state (Step 5) (Optional)"; step 6.4 described writing them to the user record with no note on where the values originated.Input table now reads "Read fromlocalStorage at submit time, per CR-12 (Optional)"; step 6.4 now explicitly cross-references the input table and CR-12 as the source of the UTM values being written.CR-20260730-002 (Adrian Stack, client, Slack, relayed by Huyen/Hexie) — supersedes QnA Q9 first-seen-wins/session-state model; see newCR-12.

On this page

UC IndexStep 6: Checkout & PaymentUC_2.7.1 — Order Summary1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.7.1.1: Data Source at Step 6BR_2.7.1.3: Payment Method List Is DynamicBR_2.7.1.4: Apple Pay and Google Pay — Client-Side Environment Detection8. Screen DescriptionUC_2.7.2 — Credit Card (NMI Collect.js)1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Business RulesBR_2.7.2.1: NMI Collect.js Required — Custom Inputs ForbiddenBR_2.7.2.2: Lead Capture8. Screen DescriptionUC_2.7.3 — Apple Pay1. Overview2. Visibility Rule3. Basic FlowUC_2.7.4 — Google Pay1. Overview2. Visibility Rule3. Basic FlowUC_2.7.5 — Crypto (Triple-A)1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.7.5.1: Two Separate Modals — Currency Selection and Payment DetailsBR_2.7.5.2: Crypto Payment IrreversibilityBR_2.7.5.3: WebSocket Listener Survival Across Modal CloseBR_2.7.5.4: Crypto Token List Is Dynamic8. Screen DescriptionUC_2.7.6 — Skrill1. OverviewUC_2.7.7 — Dusupay1. Overview2. Display Rules3. Basic FlowUC_2.7.9 — T3651. Overview2. Display Rules3. Basic FlowUC_2.7.8 — Apply Promo Code1. Overview2. Trigger3. Basic Flow — Promo Code4. Business RulesBR_2.7.8.1: Apply Trigger — Click OnlyBR_2.7.8.2: One Promo Code Per TransactionBR_2.7.8.3: Subtotal Field Displayed Below Applied Promo Code [CHR-51]BR_2.7.8.4: Promo Code Types, Discount Display, and Usage RulesBR_2.7.8.5: Tax Is Calculated on Post-Discount AmountBR_2.7.8.6: Promo Code Re-Validation at Execute-CheckoutBR_2.7.8.7: Promo Code Mapped 1-to-1 to a Specific Product ID [CHR-55]5. Screen DescriptionStep 7: Order Processing & ProvisioningUC_2.8.1 — Phase 1 — Payment Execution1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow6. Exceptional Flow7. Business RulesBR_2.8.1.1: CTA Button Disabled During Payment ProcessingBR_2.8.1.2: 5-Failure Lock Is Cross-MethodBR_2.8.1.3: CTA Button Re-enable on Failure OnlyBR_2.8.1.4: Accidental Double-Payment After Timeout Retry [CHR-21]BR_2.8.1.5: Retry Is Allowed When the Prior Payment Failed or Is Still In ProgressBR_2.8.1.6: Late-Resolving In-Flight Attempt During an Active Email LockUC_2.8.2 — Flow 1 — Provisioning Pipeline1. Overview2. Flow 1 LogicUC_2.8.3 — Phase 2 — Account Claim1. Overview2. Email Flow3. "Claim Your Account" Link Specification4. Basic Flow — Claim Account5. Exceptional Flow6. Business RulesBR_2.8.3.1: Single-Use Claim LinkBR_2.8.3.3: Already-Claimed Link Re-enters the Provisioning Interstitial Before LoginBR_2.8.3.2: Account Verification StatusBR_2.8.3.4: Phone Number UniquenessBR_2.8.3.5: SIM Provisioning Failure Is Silent on Phase 27. Screen DescriptionUC_2.8.4 — Phase 3 — Provisioning & Redirect to Login1. Overview2. Trigger3. Pre-conditions4. Post-conditions5. Basic Flow4-Step Animated Sequence6. Exceptional Flow7. Business RulesBR_2.8.4.1: Interstitial Is Visual Mask OnlyBR_2.8.4.2: Redirect to Login After ProvisioningChangelog