SRS: UC_4.6.3 — Settings: Billing
| Field | Value |
|---|
| BA in Charge | Anh Hoang |
| Date Created | 2026-08-12 |
| Version | v2.0 |
| Document References | BA/client answers 2026-09-04 (V7 fee_type ENUM · invoice grouping by invoice_id · BE aggregate + sort + paging server-side · truncate + tooltip on the Description column · PLATFORM_FEE is an internal tag · no $0 record for MARKET_DATA_BASE) · BE spec 2026-09-04 (response {items, page}, query params cursor/limit/sort_by/sort_dir) · Broadcast, Settings_Market Data.docx (§Settings - Billing → §2.3 Invoice History) · RFQ_ Stack Trading Prop Tech V7.pdf (§Page 36 — Get Billing History; §3.7 Payment, Tax and Billing Architecture) · Zapier Integration V7.pdf (§3.1.2 Table invoices; Flow 22 — Monthly Data Fee Billing) · RFQ_ Website and Dashboard Implementation V7.pdf (§Part C — Settings & Profile Module) |
| # | Original Document | Key Sections Used |
|---|
| 1 | Broadcast, Settings_Market Data.docx | Primary source. §Settings - Billing → §2.3 Invoice History + the Manage Billing block (storage format, filtering, display layout). Everything above the Settings - Billing heading belongs to the Market Data Management tab (UC_4.17.1) |
| 2 | RFQ_ Stack Trading Prop Tech V7.pdf | §Page 36 — Get Billing History · §3.7 Payment, Tax and Billing Architecture (NMI, Triple-A, Quaderno) |
| 3 | Zapier Integration V7.pdf | §3.1.2 Table invoices (Financial Audit Trail) · Flow 22 — Monthly Data Fee Billing |
| 4 | RFQ_ Website and Dashboard Implementation V7.pdf | §Part C — Settings & Profile Module |
| 5 | QnA from clients — STAGE 2: Dashboard & Evaluation (Settings) | Client answers 2026-08-10, 2026-08-28, 2026-09-04 |
| 6 | References/WBS/[BA Internal] Stacktrading.csv | Line 175 — UC_4.6.3 = Billing, category = STAGE 2: DASHBOARD - COMMON, module = Settings, BA PiC = anh.hoang12 |
| 7 | BE spec — Dat Nguyen, 2026-09-04 | Response shape {items, page}, line items, query params, parent invoice_id column in Table invoices |
| Field | Content |
|---|
| ID | UC_4.6.3 |
| Use Case | Settings — Billing |
| Description | Inside the Account screen, the trader opens the Billing tab to read the history of payments charged on their account. The tab holds exactly one block — the Invoice History table (Ref: BR_4.6.3.1). Each transaction renders as exactly one row, pre-grouped by the backend on invoice_id (Ref: BR_4.6.3.3), with five columns: Invoice ID, Date, Amount, Status, Description (Ref: BR_4.6.3.4). The backend owns grouping, sorting and paging; the frontend only renders (Ref: BR_4.6.3.2, BR_4.6.3.9, BR_4.6.3.10). The screen is read-only (Ref: BR_4.6.3.13) and available to every authenticated trader, SIM or LIVE (Ref: BR_4.6.3.14). |
| Zapier Flow | — This UC runs no flow. Flow 22 (Monthly Data Fee Billing) produces the MARKET_DATA_* records this screen reads. |
| Zapier Table | Table invoices (read-only) |
| 3rd Party | — None. The Dashboard stores and renders the invoices itself. |
Design: Figma — Billing, node 1-15026
- The trader is authenticated and inside the Dashboard shell (Ref: UC_4.1.1 §1 Overview).
- A Users record exists for the trader. No invoice is required — a trader with zero invoices reaches the empty state (Ref: BR_4.6.3.11).
- The trader has read their payment history. No system state changes.
- The active sort column and the current page are not persisted. The next time the trader opens the tab, the table returns to its defaults: sort
Date descending, page 1 (Ref: BR_4.6.3.9, BR_4.6.3.10).
- The trader lands on the
Account screen and selects the Billing tab.
- The table enters its loading state — column headers visible, 5 skeleton rows, pagination bar and footer note hidden (Ref: BR_4.6.3.11).
- The frontend calls
GET /billing-history with limit = 15, page = 1, sort_by = created_at, sort_dir = desc (Ref: BR_4.6.3.2).
- The backend groups the line items by
invoice_id, sorts, slices the page, and returns {items, page} (Ref: BR_4.6.3.3).
- The frontend renders each
item as one row of five columns — Invoice ID (Ref: BR_4.6.3.4), Date as MMM DD, YYYY in UTC (Ref: BR_4.6.3.8), Amount (Ref: BR_4.6.3.7), Status as a badge (Ref: BR_4.6.3.6), Description as the concatenated fee_type string of the line items (Ref: BR_4.6.3.5).
- The pagination bar renders when
page.total > 15; the footer note renders below it (Ref: BR_4.6.3.10).
- The trader reads the table, and may sort it (Ref: §6 Alternative Flow A) or page through it (Ref: §6 Alternative Flow B).
- The trader clicks the
Date or Amount column header. No other header is clickable (Ref: BR_4.6.3.9).
- The frontend re-calls
GET /billing-history with the new sort_by and sort_dir, and page = 1.
- The table enters its loading state, then renders page 1 of the new order and updates the sort indicator on the header.
- The trader clicks a page number,
‹ Prev, or Next › (Ref: BR_4.6.3.10).
- The frontend re-calls
GET /billing-history — a page number sends page, ‹ Prev / Next › send cursor — keeping sort_by / sort_dir unchanged.
- The table enters its loading state, then renders the new page and updates the
Showing [start]–[end] of [total] invoices counter.
| # | Condition | System behaviour |
|---|
| 1 | GET /billing-history fails (network error or HTTP 5xx) | The table body is replaced by the error state — Ref: BR_4.6.3.11. Column headers, pagination bar and footer note are hidden. The section heading and subtitle stay visible. Toast TE-SYS-01 is not raised. |
| 2 | The trader clicks [Try again] in the error state | The frontend re-issues GET /billing-history with the exact parameters of the failed call; the flow resumes at §5 step 2. The button is Disabled while the retry is in flight. There is no retry limit. |
| 3 | GET /billing-history succeeds but items is empty | The empty state is shown — Ref: BR_4.6.3.11. |
| 4 | A line_item carries a fee_type outside the ENUM list in BR_4.6.3.5 | The row is still rendered; that value is still concatenated into the Description string verbatim. The Description cell is never left blank. |
| 5 | An item carries a status outside paid / void / refunded | The row is still rendered; the Status cell shows the raw status string as plain text instead of a badge. |
| 6 | The trader's session token expires → the API returns HTTP 401 | Applies to all three API calls in this UC (initial load, sort, paging). Raise error toast TE-AUTH-01 — "Your session has expired. Please log in again" — then redirect the trader to the Auth0 login screen. The table does not show the error state in this case. |
- The
Billing tab holds one content block: the Invoice History table.
- No
[Manage Billing] button, no billing portal, no redirect off the Dashboard.
- No card management, no subscription list, no cancel action and no funding-source switch — all of it belongs to the
Market Data Management tab (Ref: UC_4.17.1).
- No PDF, no download, no print, no per-invoice detail view. A trader who needs a receipt uses the email receipt sent by the payment gateway.
| Attribute | Value |
|---|
| Endpoint | GET /billing-history |
| Input | user_id (required) · limit (default 15) · page (page number, 1-based, default 1) · cursor (used by ‹ Prev / Next ›) · sort_by (created_at | amount) · sort_dir (asc | desc) |
| Output | { items, page } |
items[] | { invoice_id, total_amount, currency, status, created_at, line_items[] } |
line_items[] | { fee_type, amount } |
page | { next_cursor, has_more, total } |
| Called | On every entry to the tab, on every sort, on every page change |
- The backend owns grouping, sorting and paging. The frontend does not group, does not sort and does not slice pages itself.
page.total is the total number of grouped invoices, not the total number of rows in the database.
- The UI renders a page-number list, so the backend must support jumping directly to any page via the
page parameter, not only sequential forward/backward movement via cursor (Ref: BR_4.6.3.10).
- One checkout produces exactly one parent
invoice_id. Every line item of that transaction — evaluation/reset/rebuy/extension, the market data feeds, and the TAX row — shares this invoice_id.
- Example: a trader buys 4 market data feeds in one transaction → the database holds 4 rows (
MARKET_DATA_BASE for CME, 3 × MARKET_DATA_ADDON for NYMEX / COMEX / CBOT) plus 1 TAX row, all under the same invoice_id.
- The backend groups these rows into one
item before responding. The UI renders one row per transaction.
total_amount = the sum of amount across all line items under that invoice_id, tax included. No proportional splitting, no tax allocation per feed.
invoice_id is a column generated by the system in Table invoices: each checkout produces exactly one value, and every line item of that checkout carries it. This is the grouping ID and the ID displayed on the UI.
checkout_id is returned by the payment gateway; the system only receives and stores it. One checkout has one checkout_id even when the trader buys several products. It is not used for grouping and is not displayed on the UI.
- The internal ID of each individual line item exists in the database only and is never displayed on the UI.
The table renders exactly the five columns below, in this order. No column may be added, removed or re-ordered.
| # | Column header | Source field | Rule |
|---|
| 1 | Invoice ID | invoice_id | Rendered verbatim as returned by the backend. No prefix added, no transformation. |
| 2 | Date | created_at | Ref: BR_4.6.3.8 |
| 3 | Amount | total_amount + currency | Ref: BR_4.6.3.7 |
| 4 | Status | status | Ref: BR_4.6.3.6 |
| 5 | Description | line_items[].fee_type | Ref: BR_4.6.3.5 |
The Description cell displays the raw fee_type value verbatim — it is never mapped to any other text.
fee_type ENUM of the invoices table — 10 values:
EVAL_L1 · EVAL_L2 · EVAL_L5 · RESET · REBUY · EXTENSION · PLATFORM_FEE · MARKET_DATA_BASE · MARKET_DATA_ADDON · TAX
How the Description string of a row is built:
- Concatenate the
fee_type of every line item under that invoice, separated by , .
- A repeated
fee_type is shown once. Example: a transaction buying 4 feeds (1 × MARKET_DATA_BASE + 3 × MARKET_DATA_ADDON) plus tax → Description is MARKET_DATA_BASE, MARKET_DATA_ADDON, TAX.
- No value is dropped —
TAX and PLATFORM_FEE are displayed verbatim too when the invoice carries such a line item.
- Display order follows the line-item order returned by the backend.
- A
fee_type outside the list above is still displayed verbatim. The Description cell is never left blank.
Rendering: single line, overflow truncated with CSS, hover shows a tooltip carrying the full Description string. No wrapping — wrapping would break the 15-rows-per-page layout (Ref: BR_4.6.3.10).
status | Badge |
|---|
paid | Paid |
void | Void |
refunded | Refunded |
status sits at the grouped-invoice level, not at the line-item level.
- A value outside these three renders as plain text instead of a badge (Ref: §7 row 5).
- The table does not filter on
status — all three badges are displayed.
total_amount and currency are rendered together in one column.
- Format: currency symbol + value to 2 decimal places + comma thousands separators + currency code. Example:
$49.00 USD, $1,250.00 USD. Ref: CR-11.
- Every invoice is denominated in USD.
- No wrapping, no truncation. No tooltip — this column does not apply the hover-tooltip rule of CR-11.
| Attribute | Rule |
|---|
| Source | created_at — API and database use ISO 8601 UTC per Ref: CR-08 |
| Rendered timezone | UTC |
| Rendered format | US format — MMM DD, YYYY. Example: Mar 31, 2026 |
| Time of day | Not displayed |
| Attribute | Rule |
|---|
| Sortable columns | Date and Amount |
| Non-sortable columns | Invoice ID, Status, Description — these headers carry no sort indicator and are not clickable |
| Default sort | Date descending — newest first |
| Header indicator | 3 states: not sorted · ascending · descending |
| API parameters | sort_by = created_at (Date column) or amount (Amount column); sort_dir = asc | desc |
| Behaviour | Every change of column or direction → re-call GET /billing-history, drop cursor, return to the first page |
| Simultaneous sort columns | 1 — selecting a new column clears the previous sort |
| Attribute | Rule |
|---|
| Rows per page | 15, fixed (limit = 15) |
| Page-size selector | Not rendered |
| Mechanism | Backend paging. ‹ Prev / Next › send cursor; a page-number click sends page |
| Counter | Showing [start]–[end] of [total] invoices, with total taken from page.total |
| Page-number list | Condensed 1 2 3 … N, where N = ceil(page.total ÷ 15). Clicking a page number → re-call the API with the matching page |
‹ Prev | Disabled on page 1. The frontend keeps a stack of the cursors of the pages already visited in order to move back |
Next › | Disabled on the last page (page.has_more = false) |
| After a sort change | Return to the first page (Ref: BR_4.6.3.9) |
When total ≤ 15 | The whole pagination bar is hidden — no counter, no controls |
| Layout | 15 rows must fit the page height exactly. Every cell is single-line and truncates on overflow — Ref: BR_4.6.3.5 |
| State | When | What renders | Column headers | Pagination | Footer note |
|---|
| Loading | From the API call until the response arrives — including on sort and on page change | 5 skeleton rows in the table body | Visible | Hidden | Hidden |
| Populated | items holds at least one element | The data table | Visible | Visible when total > 15 | Visible |
| Empty | items is empty | An icon plus the line "Your payment history will appear here after your first charge" | Hidden | Hidden | Hidden |
| Error | The API call failed (Ref: §7 row 1) | A warning icon, the title "Couldn't load your invoices", the line "Something went wrong loading your invoices. Your history is safe", and a [Try again] button | Hidden | Hidden | Hidden |
The section heading and the section subtitle stay visible in all four states.
- The table displays fees and refunds. It does not display payouts — the full payout history and its management live in a dedicated sub-tab, scoped to Day 2.
- A refund of a charge is not a payout: it keeps the original
invoice_id and carries status = refunded, so it stays in the table with the Refunded badge.
PLATFORM_FEE is an internal expense-tracking tag (Tradesea, Quantower, Sierra Chart, MotiveWave, ATAS…) — the trader does not pay it out of pocket, so it does not arise in a trader's billing history. If an invoice does carry a PLATFORM_FEE line item, the Description still displays it verbatim (Ref: BR_4.6.3.5).
- Level 3+ traders have their CME feed covered: the system does not create a
MARKET_DATA_BASE record with amount = $0. Feed entitlement is managed by market_data_flags and market_data_billable in the Users table.
- No form, no input field, no save action, no dirty state, no edit or delete action on any row.
- The unsaved-changes navigation warning (Ref: CR-05 / CF-01) never fires from this tab.
- No client-side cache: the API is called on every entry to the tab.
- No polling, no WebSocket, no auto-refresh. An invoice raised while the trader is sitting on the tab only appears after the trader navigates away and back.
- The tab renders and behaves identically for SIM and LIVE traders. No branching on account type.
- Only the data differs: a SIM trader has no
MARKET_DATA_BASE / MARKET_DATA_ADDON invoices; a LIVE trader may carry every fee_type (Ref: BR_4.6.3.5).
- The tab stays reachable in every account state, including Hard Breach, read-only after resignation, and an account paused by the Zombie protocol.
| No. | Field Name | Field Type | Displaying rule / Behaviour rule |
|---|
| 1 | Section heading | Label | Displaying rule:- Static text: "Invoice History". Visible in all four table states (Ref: BR_4.6.3.11). |
| 2 | Section subtitle | Label | Displaying rule:- Static text: "View invoices, update payment method, and manage your subscription through our secure billing portal". Display copy only; it exposes no action on this tab (Ref: BR_4.6.3.1). Visible in all four table states.- Overflow: wrap text. |
| 3 | Invoice History table | Table | Displaying rule:- Five fixed columns — specification in Ref: §9.2. Ref: BR_4.6.3.4.- One row per transaction, pre-grouped by the backend. Ref: BR_4.6.3.3.- Default sort: Date descending. Ref: BR_4.6.3.9.- 15 rows per page. Ref: BR_4.6.3.10.- Populated from GET /billing-history, called on every entry to the tab with no cache. Ref: BR_4.6.3.2, BR_4.6.3.13.- Loading / Empty / Error states: Ref: BR_4.6.3.11.Behaviour rule:- Read-only. Rows are not clickable, not selectable, not expandable; there is no row action, no download and no detail view.- The only interactions are the sort (Ref: §6 Alternative Flow A) and the pagination control (Ref: §6 Alternative Flow B). |
| 4 | Pagination | Pagination | Displaying rule:- 15 rows per page, no page-size selector. Ref: BR_4.6.3.10.- Left: "Showing [start]–[end] of [total] invoices". Right: ‹ Prev · condensed page-number list 1 2 3 … N · Next ›.- N = ceil(page.total ÷ 15).- ‹ Prev Disabled on page 1; Next › Disabled on the last page.- Hidden when total ≤ 15, and in the Loading / Empty / Error states.Behaviour rule:- On click: re-call GET /billing-history — a page number sends page, ‹ Prev / Next › send cursor — keeping sort_by / sort_dir unchanged. |
| 5 | Footer note | Label | Displaying rule:- Static text: "Need a receipt? Payment receipts are emailed to you automatically". Rendered below the pagination bar.- Shown only in the Populated state.- Not a link and not clickable. |
| 6 | Empty state | Label | Displaying rule:- Replaces the table body when items is empty. An icon plus a single line: "Your payment history will appear here after your first charge".- Column headers, pagination bar and footer note are hidden. Ref: BR_4.6.3.11.Behaviour rule:- No action available. |
| 7 | Error state | Label | Displaying rule:- Replaces the table body when the API call fails. A warning icon, the title "Couldn't load your invoices", the line "Something went wrong loading your invoices. Your history is safe".- Column headers, pagination bar and footer note are hidden. Toast TE-SYS-01 is not raised. Ref: BR_4.6.3.11, §7 row 1. |
| 8 | [Try again] | Button (Primary) | Displaying rule:- Rendered only inside the Error state.Behaviour rule:- On click: re-issues GET /billing-history with the exact parameters of the failed call; the table returns to its Loading state. Does not reload the page.- Disabled while the retry is in flight. Ref: §7 row 2. |
| No. | Column | Field Type | Source field | Displaying rule / Behaviour rule |
|---|
| 1 | Invoice ID | Label | invoice_id | Displaying rule:- Rendered verbatim as returned by the backend — the parent invoice_id of the whole transaction. Ref: BR_4.6.3.3, BR_4.6.3.4.- Overflow: truncate, no wrap.Behaviour rule:- Not sortable. Ref: BR_4.6.3.9. |
| 2 | Date | Label | created_at | Displaying rule:- Format MMM DD, YYYY (e.g. Mar 31, 2026), rendered in UTC, time of day not shown. Ref: BR_4.6.3.8.- Overflow: no wrap, no truncation.Behaviour rule:- Sortable — default sort column, descending. sort_by = created_at. 3 indicator states on the header. Changing the sort → re-call the API, return to the first page. Ref: BR_4.6.3.9. |
| 3 | Amount | Label | total_amount + currency | Displaying rule:- One combined column. Format: symbol + 2 decimal places + comma thousands separators + currency code — e.g. $49.00 USD, $1,250.00 USD. Ref: BR_4.6.3.7, CR-11.- Tax included. Ref: BR_4.6.3.3.- Overflow: no wrap. No tooltip — the hover-tooltip rule of CR-11 is not applied here.Behaviour rule:- Sortable. sort_by = amount. 3 indicator states. Changing the sort → re-call the API, return to the first page. Ref: BR_4.6.3.9. |
| 4 | Status | Badge | status | Displaying rule:- Paid (paid) · Void (void) · Refunded (refunded). Ref: BR_4.6.3.6.- A value outside these three renders as plain text instead of a badge. Ref: §7 row 5.- Overflow: no truncation.Behaviour rule:- Not sortable and not a filter control. Ref: BR_4.6.3.9. |
| 5 | Description | Label | line_items[].fee_type | Displaying rule:- Displays the raw fee_type value verbatim, never mapped to other text. The fee_type values of the line items concatenated with , ; a repeated value is shown once; no value is dropped (including TAX and PLATFORM_FEE). Example: MARKET_DATA_BASE, MARKET_DATA_ADDON, TAX. Ref: BR_4.6.3.5.- A fee_type outside the ENUM list is still displayed verbatim; the cell is never blank. Ref: §7 row 4.- Overflow: single line, truncated with CSS, hover shows a tooltip carrying the full string. No wrap. Ref: BR_4.6.3.5, CR-10.Behaviour rule:- Not sortable. Ref: BR_4.6.3.9. |
| Date | Version | Updated item | Before | After | Notes |
|---|
| 2026-09-04 | v2.0 | BR_4.6.3.5 — fee_type ENUM | 5 values: CHALLENGE_FEE, RESET_FEE, MARKET_DATA_BASE, MARKET_DATA_ADDON, PLATFORM_FEE | 10 values: EVAL_L1, EVAL_L2, EVAL_L5, RESET, REBUY, EXTENSION, PLATFORM_FEE, MARKET_DATA_BASE, MARKET_DATA_ADDON, TAX. The old generic labels are dropped; product_id is written straight into fee_type | Client answer 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.5 — how Description is displayed | Each fee_type mapped to a human-readable label (Evaluation Purchase, Challenge Reset…); TAX and PLATFORM_FEE were not displayed | Description displays the raw fee_type value verbatim, never mapped to other text. Concatenate the invoice's fee_type values with , , show a repeated value once, and drop nothing (including TAX and PLATFORM_FEE). Example: MARKET_DATA_BASE, MARKET_DATA_ADDON, TAX | BA request 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.3 — invoice grouping (new BR) | Did not exist. Each invoices record was one row on the UI | One checkout = one parent invoice_id. Every line item (feeds, add-ons, TAX) shares that invoice_id. invoice_id is a new column generated by the system; checkout_id is returned by the gateway, stored in the database only, not used for grouping and not displayed. The backend groups and returns one item per transaction; the UI renders one row per transaction. total_amount includes tax, with no proportional splitting | Client answer + BE spec 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.2 — data contract | Input was user_id only; output was a flat array; no server-side paging and no server-side sorting | Input adds limit, page, cursor, sort_by, sort_dir. Output {items, page} with line_items[] and page{next_cursor, has_more, total}. The backend groups + sorts + pages | BE spec 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.9 — sort · BR_4.6.3.10 — pagination | Sorting and paging ran entirely on the frontend against the already-loaded payload | Sorting and paging re-call the API. The 1 2 3 … N page-number list stays as designed: a page number sends page, ‹ Prev / Next › send cursor | BE spec 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.8 — date format | DD MMM YYYY (12 Aug 2026) | US format MMM DD, YYYY (Mar 31, 2026) | Client answer — standardised across the whole frontend |
| 2026-09-04 | v2.0 | BR_4.6.3.12 — records not displayed | Payouts were the only exclusion | Added: PLATFORM_FEE is an internal expense tag the trader does not pay, so it does not arise in a trader's billing history · Level 3+ traders produce no $0 MARKET_DATA_BASE record | Client answer 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.14 — availability | Stated only "no SIM/LIVE branching" | Added the data difference: a SIM trader has no MARKET_DATA_BASE / MARKET_DATA_ADDON invoices; a LIVE trader may carry every fee_type | BE confirm 2026-09-04 |
| 2026-09-04 | v2.0 | BR_4.6.3.7 · §9.2 — column overflow | The Invoice ID and Amount columns carried tooltips | Tooltips removed from Invoice ID and Amount — the Amount column does not apply the CR-11 hover tooltip (deliberate deviation). Only the Description column truncates with a tooltip, so a concatenated multi-line-item string cannot wrap and break the 15-rows-per-page layout | BA decision 2026-09-04 |
| 2026-09-04 | v2.0 | Document structure | 14 BRs, with BR_4.6.3.1 = "Screen Entry Point / Controls Panel"; §9.1 had 11 rows | Dropped the entry-point and Controls-panel BR (already specified in other UCs) and renumbered BR_4.6.3.1 → BR_4.6.3.14. §9.1 dropped the Screen title / Tab list / Controls panel rows, leaving 8. All BRs rewritten to carry decided information only | BA request 2026-09-04 |
| 2026-08-28 | v1.2 | — | — | — | (see git history) |
| 2026-08-12 | v1 | Initial version | — | UC_4.6.3 created | Init Flow round 4 |