SportRx Order Management — Admin & User Flow Wireframes
Wireframes for the back-office order-handling experience (optician portal + CS draft-order surface) and the customer-facing post-order journey across the six order lifecycle phases. Each frame is annotated with the system that owns the surface, how it participates, and the data that flows in and out. Built to inform story creation for development; visual style mirrors the attached SportRx Order Management systems diagram.
Source — ClickUp doc 8cqdwf6-31337 · Capability Set — Order Management (8cqdwf6-42617)
Architecture — SportRx Order Management systems diagram (attached PDF)
Date — 2026-05-20
Shopify — Storefront, Cart, Order, Customer Account UI
Middleware Lambdas — shopify-sync, lifecycle, prescriptions, netsuite-api, orders-portal
Optician Portal — portal.sportrx.com · Polaris UI · Cognito MFA
PHI sub-band — S3 (sportrx-rx-files, KMS, Object Lock 1y) + Aurora rx_* schema
External — Cognito, FittingBox
Celigo + NetSuite + iCode lab + ShipStation
Admin Flow — Optician portal & CS rep surfaces
All back-office work lives at portal.sportrx.com — a SnapStart SST Remix SPA, not iframed in Shopify Admin. Optician routes under /portal/*; CS rep thin surfaces under /portal/cs/*. Cognito MFA gates access; groups-as-roles determine surfaces. Frames below trace one optician approving one Rx end-to-end, then the CS rep surfaces.
A1
Optician sign-in (Cognito MFA)
Hosted UI · email + password + TOTP / WebAuthn · 8h idle timeout
Cognito
portal.sportrx.com🔒 secure
Sign in to the SportRx Portal
Email
Password
Continue
2FA required · TOTP or WebAuthn passkey
System
AWS Cognito User Pool (Hosted UI) → issues
id_token,
access_token,
refresh_token.
Data in
Email + password + 2FA factor.
Data out
JWT tokens → Remix callback →
optician-auth Lambda verifies via JWKS → sets httpOnly session cookie.
System calls
Cognito Hosted UI → JWKS verify → session cookie (SameSite=Strict, 8h idle).
↓ session valid · open queue
A2
Order Queue · /portal/queue
Live queue of orders pending optician approval. Filter by tag, fulfillment, date, customer, SKU, claim state, assignee.
Optician Portal
Queue · 47 pendingFilters · URL-persisted
Pending Rx reviewUnclaimedMineSLA > 4h
#SR-10847 · Huckson · S. Park claimed by J. Lin · 22m left
#SR-10846 · Costa · M. Chen unclaimed
#SR-10845 · Ray-Ban · L. Patel SLA 6h
#SR-10844 · Oakley · A. Reyes unclaimed
System
Optician portal Remix SPA →
orders-portal Lambda → Shopify Admin GraphQL
@inContext with middleware overlay.
Data in
Shopify orders tagged
pending-optician-approval + Aurora overlay (
order_claims,
rx_validation_state,
lifecycle_state).
Data out
Selected
order_id → opens claim flow A3.
System calls
Shopify Admin GraphQL
orders(query:"tag:pending-optician-approval") + Aurora join via
orders-portal.
↓ optician clicks "Claim"
A3
Claim order — 30-min TTL lock
Exclusive review window; other opticians see "in review by X" until release
orders-portal Lambda
Claim #SR-10846Aurora · order_claims
order_id: SR-10846
claimed_by: cognito-sub-J.Lin
claimed_at: 2026-05-20T14:02Z
expires_at: 2026-05-20T14:32Z · 30 min TTL
Open review →
System
orders-portal.claim writes
order_claims row in Aurora with 30-min TTL.
Data in
order_id + Cognito sub of the claiming optician.
Data out
Claim row + UI lock indicator visible to other opticians.
System calls
Single Aurora INSERT (idempotent on
order_id); released on approve/reject/edit OR after TTL expires.
↓ optician opens 3-pane Rx review
A4
Rx Review — three-pane layout
Sandboxed Rx file viewer · editable values form · order spec sidebar
PHI sub-band
Review · #SR-10846claimed · 27m left
Rx file
[ S3 iframe · presigned · 10-min expiry ]
Rx values
OD sph: −2.50
OD cyl: −0.75
OD axis: 090
OS sph: −2.25
OS cyl: −0.50
PD: 63
Order spec
Huckson · M · Matte Black
Single Vision · Poly · AR
_initial_rep: J.Lin
_channel: web
System
Rx file from S3 via
prescriptions Lambda (presigned URL, 10-min, single-verb). Values from Aurora
rx_* · prescriptions table.
Data in
prescription_id,
build_id,
order_id; CSP-locked iframe origin = S3 only.
Data out
Surfaced for optician decision; no writes until edit/approve/reject.
Audit
Opening triggers an
rx_access audit event (7-year retention, no field values logged).
↓ optician edits a value (e.g. PD)
A5
Edit Rx values + field-level audit history
Every edit writes a row to prescription_value_revisions; History tab shows diff sidebar
Aurora rx_*
Edit · OD sphereHistory (3)
New value
−2.75
Reason
correctionclarification
History
−2.50 → −2.75 · J. Lin · just now
PD 62 → 63 · J. Lin · 2m ago
created · OCR · 1h ago
System
Edit handler writes to
prescription_value_revisions (field_path, old, new, editor sub, reason).
Data in
Edited value + reason taxonomy (
correction ·
clarification ·
reject_then_revise).
Data out
Updated
prescriptions.values + new revision row;
rx_value_change audit event.
System calls
Aurora INSERT + UPDATE in one transaction; values_source flips to
manual.
↓ optician chooses Edit / Reject / Approve
A6
Decision: Edit · Reject · Approve
Approve fires the cascade — tag, audit, SNS event, workflow resume
Approve/Reject/Edit Lambda
Decision · #SR-10846values · saved
Approve — stamp approved_by + approved_at · resume workflow · tag order
Edit (save only) — keep in queue, release claim for next reviewer
Reject — set status rejected · send "clarification needed" email · keep file
ApproveEdit onlyReject
System
Single Lambda; transactional.
prescriptions.status = approved in Aurora.
Data in
order_id,
prescription_id, decision verb, Cognito sub of decider.
Data out (approve)
1. Aurora status flip · 2.
prescriptions.approved SNS event · 3.
SendTaskSuccess resumes lifecycle workflow · 4.
tagsAdd ready-for-processing on Shopify Order (5-attempt retry + DLQ → portal banner)
System calls
Aurora UPDATE · SNS publish · Step Functions
SendTaskSuccess (uses task token stored in
workflow_tokens) · Shopify Admin GraphQL
tagsAdd.
↓ separate surface · CS rep originates a phone order
A7
CS Draft-Order Console · /portal/cs/*
Phone orders (~20% of biz). Customer search · draft order · send invoice · complete. Auto-tag back_office, stamp _initial_rep.
CS thin surface
CS · New draft orderrep: M. Reyes
Customer
🔍 search customers… (Shopify GraphQL proxy)
Line items
Huckson · M · Matte Black — $299
Single Vision · Polycarbonate · AR — $124
Save draftSend invoice →
System
Portal CS surface wraps Shopify Admin GraphQL
draftOrderCreate ·
send-invoice ·
complete.
Data in
Customer search query + line item selections + rep's Cognito sub.
Data out
Shopify Order with
_channel = back_office,
_initial_rep = <cognito sub> (immutable).
System calls
GraphQL mutations + portal action that stamps attribution. Returns
409 initial_rep_immutable on any rewrite attempt.
↓ CS handles an in-flight edit (Klarna / Apple Pay / PayPal)
A8
Store Credit Wrapper — order edits without cancel-and-recreate
Composes storeCreditAccountCredit + storeCreditAccountDebit + Shopify Order Editing in one transactional flow
CS thin surface
Edit · #SR-10821Original: $500 · New: $600
1. Credit customer the prior $500 → store credit
2. Edit order to new $600 build
3. Debit $500 store credit toward new order
4. Capture remaining $100 on file
Apply edit
System
Single portal action; orchestrates 3 Shopify mutations transactionally.
Data in
Original order ref + new line items + rep's Cognito sub.
Data out
Edited Shopify Order +
_edited_by_rep +
_last_edited_at stamp + audit row.
Open Q12
Refund store credit → original card path on returns — Karan investigating; could grow wrapper sizing.
User Flow — Customer's order journey across the 6 lifecycle phases
From the customer's perspective, post-order touchpoints are Shopify-native (order page, customer account, status emails). Behind each touchpoint, the order moves through the six lifecycle phases. Each frame shows what the customer sees + what the system does. Phase labels mirror the SportRx Order Management diagram.
U1
Phase 1 Order placed — Shopify checkout completes
Customer sees standard Shopify order confirmation page + native confirmation email
Shopify Checkout + Order
Order confirmation#SR-10846
Thank you, Mara! Your order has been received.
Huckson · M · Matte Black · Single Vision · AR — $423
Status: Pending optician review
System
Native Shopify Checkout → Order created. Build stamped on metafields by Customizer-side
shopify-sync.
Data in
Cart (nested by
build_id), customer info, payment.
Data out
Shopify Order with metafields:
build_id,
prescription_id; attrs:
_initial_rep,
_current_rep,
_channel.
System calls
orders/create webhook fires (HMAC-verified) → consumed by
shopify-sync Lambda (Customizer-side, bridge into OM).
↓ webhook fires · lifecycle workflow starts
U2
Phase 2 Webhook intake — lifecycle workflow routes the order
Step Functions Standard workflow starts; routes to Rx / plano / fraud branch. Customer gets "received & pending Rx review" email.
shopify-sync + Step Functions
Lifecycle workflow · startedexecution_arn ...
Rx required → wait on task token for optician approval
Plano ≤3 items, no Rx → tag skip-optician-review → straight to fulfillment
Fraud rule match → tag flagged-fraud → CS review queue
System
shopify-sync Lambda flips
Build.status = "ordered"; starts Step Functions workflow shell.
Data in
Shopify Order payload + read of
build_id /
prescription_id from metafields.
Data out
Workflow execution ARN;
workflow_tokens row keyed by
order_id + step name (so approval Lambda can resume).
Notification
notification_outbox row →
notifications Cron Lambda → SES email:
"Order received, pending Rx review".
↓ for Rx-required branch · workflow waits
U3
Phase 3 Customer sees "In review" — order status in account
Native Shopify customer account order page reflects state from order tags + status emails
Shopify Customer Account
My account › Orderssportrx.com
#SR-10846 · $423 · placed 2 days ago
✓ Paid
⏳ In optician review
Approved & sent to lab
Fulfilled & shipped
System
Native Shopify customer account UI; status derived from order tags.
Data in
Order tags (
pending-optician-approval) + fulfillment state.
Data out
Read-only display; no actions except "Contact us".
Edge case — reject
If optician rejects (A6), customer receives "Clarification needed" SES email;
prescriptions.status = rejected. Customer either re-uploads via account or contacts CS.
↓ optician approves (A6) · workflow resumes
U4
Phase 4 Tag flip — order tagged ready-for-processing
The only outbound mutation from middleware to Shopify. Customer gets "approved & sent to lab" email.
Approve Lambda · lifecycle workflow
Sequence · approve cascade(backend)
1. Aurora: prescriptions.status = approved
2. SNS publish: prescriptions.approved
3. SendTaskSuccess resumes lifecycle workflow
4. tagsAdd → Shopify Order tag ready-for-processing (retry + DLQ)
System
Tag is the sole signal middleware writes to Shopify. Workflow task handlers run:
releaseClaim,
notifyOptician,
recordAudit.
Data in
Approval verb +
order_id +
prescription_id + Cognito sub.
Data out
Tag on Shopify Order; SES email
"Your order is approved and on its way to the lab".
Resilience
tagsAdd retries 5x with exponential backoff; failures → DLQ → ops alert + portal banner.
↓ Celigo polls Shopify for the tag
U5
Phase 5 NetSuite pull · DVI XML composed
Celigo detects the tag → calls middleware for Rx + build → NetSuite composes DVI internally
Celigo + NetSuite + netsuite-api
Sequence · NetSuite pullread-only middleware
1. Celigo polls Shopify for tag:ready-for-processing
2. NetSuite pulls the order via Celigo
3. NetSuite calls GET /netsuite/v1/orders/{n}/rx on middleware
4. netsuite-api returns Rx values + build SKUs + market (JSON)
5. NetSuite composes DVI XML from items + Rx values
System
Celigo orchestrator (Amanda's team); middleware's
netsuite-api Lambda is read-only.
Data in (to middleware)
API key (Secrets Manager · quarterly rotation) + IP allowlist + 1000 req/min throttle.
Data out (from middleware)
JSON: Rx values (sphere · cyl · axis · add · PD) + build SKUs (UPC) + market context.
Audit + open Qs
Every NetSuite call logged to
audit.netsuite_api_call (no Rx values logged · field-strip middleware). Open: Q2 — does NetSuite store Rx values or use once? Q4 — final payload contract pending Amanda.
↓ NetSuite ships DVI to the lab
U6
Phase 6 Lab transmission · iCode fulfills
DVI XML to iCode via SFTP. Out of SLTWTR scope; NetSuite-team owned. Customer sees "Sent to lab" status.
NetSuite SFTP → iCode → ShipStation
Lab handoffout of SLTWTR scope
DVI XML over SFTP → iCode lab
Lab fulfills (cut lenses · assemble · QA)
ShipStation generates label · captures shipment
NetSuite pushes order-status back to Shopify (post-fulfillment)
System
NetSuite-managed SFTP to iCode; ShipStation owns label + carrier.
Data in
DVI XML payload (items + Rx values + ship-to).
Data out
Shipment confirmation → NetSuite → Shopify order status updated.
Customer notification
SES email:
"Your glasses have shipped" with tracking number; native Shopify shipping notification reused.
↓ delivered · post-fulfillment surfaces
U7
Customer Account UI Extension — "My Prescriptions"
After delivery, customer can reuse Rx on a new order (auto-approve if < 1y old)
Shopify Customer Account Extension
My account › My Prescriptions2 saved
Rx · 2026-04-12 · status: approved · 11 mo old
Rx · 2025-02-01 · status: superseded
Use on a new orderView details
System
Shopify Customer Account UI Extension → Shopify App Proxy →
prescriptions Lambda.
Data in
Customer GID + selection of saved Rx.
Data out
POST /prescriptions/{id}/reuse — auto-approves if status = approved AND age < 1 year. Soft-delete sets status
superseded (file retained 7y).
Open Q
Returning-customer eligibility: auto-approve under 1y vs require re-review every time — pending.
↓ if problem · customer initiates return / warranty
U8
Returns · Edits · Remakes · Warranty
CS-routed (not optician portal). Suffix model on the new order: -1 edit · -R1 remake · -W1 warranty.
NetSuite RMA · ShipStation · CS
Edge-case routingCS team only · Phase 2 = self-serve
Return · customer → CS (chat/phone/email/Zendesk form) → RMA in NetSuite → ShipStation label
Edit / restyle (-1) · CS store credit wrapper (A8) — no cancel-and-recreate
Remake (-R1) · new order references original via NetSuite created_from → lab discount
Warranty (-W1) · new order; full warranty system out of OMS scope (see warranty ticket 86e124yxz)
System
NetSuite RMA (custom workflow, not native Celigo). ShipStation for return labels. CS portal for order edits.
Data in
Original order # + reason + customer contact (Zendesk intake form for digital channel).
Data out
RMA + new sales order (NetSuite type:
return /
edit /
remake) with reference to original.
Open decisions
A7 — confirm carry-forward of suffix model + NetSuite
created_from linkage. A8 — warranty fields in NetSuite (Amanda's accounting team). Q12 — store credit → original card refund path (Karan).