StackTrading Docs

SRS: UC_4.6.3 — Settings: Billing

UC_4.6.3: Settings — Billing

FieldValue
BA in ChargeAnh Hoang
Date Created2026-08-12
Versionv2.0
Document ReferencesBA/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)

Document References

#Original DocumentKey Sections Used
1Broadcast, Settings_Market Data.docxPrimary 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)
2RFQ_ Stack Trading Prop Tech V7.pdf§Page 36 — Get Billing History · §3.7 Payment, Tax and Billing Architecture (NMI, Triple-A, Quaderno)
3Zapier Integration V7.pdf§3.1.2 Table invoices (Financial Audit Trail) · Flow 22 — Monthly Data Fee Billing
4RFQ_ Website and Dashboard Implementation V7.pdf§Part C — Settings & Profile Module
5QnA from clients — STAGE 2: Dashboard & Evaluation (Settings)Client answers 2026-08-10, 2026-08-28, 2026-09-04
6References/WBS/[BA Internal] Stacktrading.csvLine 175 — UC_4.6.3 = Billing, category = STAGE 2: DASHBOARD - COMMON, module = Settings, BA PiC = anh.hoang12
7BE spec — Dat Nguyen, 2026-09-04Response shape {items, page}, line items, query params, parent invoice_id column in Table invoices

1. Overview

FieldContent
IDUC_4.6.3
Use CaseSettings — Billing
DescriptionInside 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 TableTable invoices (read-only)
3rd Party— None. The Dashboard stores and renders the invoices itself.

Design: Figma — Billing, node 1-15026

2. Trigger

3. Pre-conditions

  • 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).

4. Post-conditions

  • 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).

5. Basic Flow

  1. The trader lands on the Account screen and selects the Billing tab.
  2. The table enters its loading state — column headers visible, 5 skeleton rows, pagination bar and footer note hidden (Ref: BR_4.6.3.11).
  3. The frontend calls GET /billing-history with limit = 15, page = 1, sort_by = created_at, sort_dir = desc (Ref: BR_4.6.3.2).
  4. The backend groups the line items by invoice_id, sorts, slices the page, and returns {items, page} (Ref: BR_4.6.3.3).
  5. 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).
  6. The pagination bar renders when page.total > 15; the footer note renders below it (Ref: BR_4.6.3.10).
  7. The trader reads the table, and may sort it (Ref: §6 Alternative Flow A) or page through it (Ref: §6 Alternative Flow B).

6. Alternative Flows

Alternative Flow A — Trader sorts the table

  1. The trader clicks the Date or Amount column header. No other header is clickable (Ref: BR_4.6.3.9).
  2. The frontend re-calls GET /billing-history with the new sort_by and sort_dir, and page = 1.
  3. The table enters its loading state, then renders page 1 of the new order and updates the sort indicator on the header.

Alternative Flow B — Trader pages through the table

  1. The trader clicks a page number, ‹ Prev, or Next › (Ref: BR_4.6.3.10).
  2. The frontend re-calls GET /billing-history — a page number sends page, ‹ Prev / Next › send cursor — keeping sort_by / sort_dir unchanged.
  3. The table enters its loading state, then renders the new page and updates the Showing [start]–[end] of [total] invoices counter.

7. Exceptional Flow

#ConditionSystem behaviour
1GET /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.
2The trader clicks [Try again] in the error stateThe 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.
3GET /billing-history succeeds but items is emptyThe empty state is shown — Ref: BR_4.6.3.11.
4A line_item carries a fee_type outside the ENUM list in BR_4.6.3.5The row is still rendered; that value is still concatenated into the Description string verbatim. The Description cell is never left blank.
5An item carries a status outside paid / void / refundedThe row is still rendered; the Status cell shows the raw status string as plain text instead of a badge.
6The trader's session token expires → the API returns HTTP 401Applies 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.

8. Business Rules

BR_4.6.3.1: Tab scope

  • 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.

BR_4.6.3.2: Data contract — Get Billing History

AttributeValue
EndpointGET /billing-history
Inputuser_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 }
CalledOn 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).

BR_4.6.3.3: Invoice grouping — one transaction, one row

  • 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.

BR_4.6.3.4: Column set and field mapping

The table renders exactly the five columns below, in this order. No column may be added, removed or re-ordered.

#Column headerSource fieldRule
1Invoice IDinvoice_idRendered verbatim as returned by the backend. No prefix added, no transformation.
2Datecreated_atRef: BR_4.6.3.8
3Amounttotal_amount + currencyRef: BR_4.6.3.7
4StatusstatusRef: BR_4.6.3.6
5Descriptionline_items[].fee_typeRef: BR_4.6.3.5

BR_4.6.3.5: Description column

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 droppedTAX 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).

BR_4.6.3.6: Status column — badges

statusBadge
paidPaid
voidVoid
refundedRefunded
  • 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.

BR_4.6.3.7: Amount column

  • 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.

BR_4.6.3.8: Date column

AttributeRule
Sourcecreated_at — API and database use ISO 8601 UTC per Ref: CR-08
Rendered timezoneUTC
Rendered formatUS format — MMM DD, YYYY. Example: Mar 31, 2026
Time of dayNot displayed

BR_4.6.3.9: Trader sorts the table

AttributeRule
Sortable columnsDate and Amount
Non-sortable columnsInvoice ID, Status, Description — these headers carry no sort indicator and are not clickable
Default sortDate descending — newest first
Header indicator3 states: not sorted · ascending · descending
API parameterssort_by = created_at (Date column) or amount (Amount column); sort_dir = asc | desc
BehaviourEvery change of column or direction → re-call GET /billing-history, drop cursor, return to the first page
Simultaneous sort columns1 — selecting a new column clears the previous sort

BR_4.6.3.10: Pagination

AttributeRule
Rows per page15, fixed (limit = 15)
Page-size selectorNot rendered
MechanismBackend paging. ‹ Prev / Next › send cursor; a page-number click sends page
CounterShowing [start]–[end] of [total] invoices, with total taken from page.total
Page-number listCondensed 1 2 3 … N, where N = ceil(page.total ÷ 15). Clicking a page number → re-call the API with the matching page
‹ PrevDisabled 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 changeReturn to the first page (Ref: BR_4.6.3.9)
When total ≤ 15The whole pagination bar is hidden — no counter, no controls
Layout15 rows must fit the page height exactly. Every cell is single-line and truncates on overflow — Ref: BR_4.6.3.5

BR_4.6.3.11: Table states

StateWhenWhat rendersColumn headersPaginationFooter note
LoadingFrom the API call until the response arrives — including on sort and on page change5 skeleton rows in the table bodyVisibleHiddenHidden
Populateditems holds at least one elementThe data tableVisibleVisible when total > 15Visible
Emptyitems is emptyAn icon plus the line "Your payment history will appear here after your first charge"HiddenHiddenHidden
ErrorThe 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] buttonHiddenHiddenHidden

The section heading and the section subtitle stay visible in all four states.

BR_4.6.3.12: Records not displayed

  • 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.

BR_4.6.3.13: Read-only screen

  • 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.

BR_4.6.3.14: Availability

  • 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.

9. Screen Description

9.1 Billing tab

No.Field NameField TypeDisplaying rule / Behaviour rule
1Section headingLabelDisplaying rule:- Static text: "Invoice History". Visible in all four table states (Ref: BR_4.6.3.11).
2Section subtitleLabelDisplaying 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.
3Invoice History tableTableDisplaying 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).
4PaginationPaginationDisplaying 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.
5Footer noteLabelDisplaying 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.
6Empty stateLabelDisplaying 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.
7Error stateLabelDisplaying 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.

9.2 Invoice History table — column specification

No.ColumnField TypeSource fieldDisplaying rule / Behaviour rule
1Invoice IDLabelinvoice_idDisplaying 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.
2DateLabelcreated_atDisplaying 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.
3AmountLabeltotal_amount + currencyDisplaying 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.
4StatusBadgestatusDisplaying 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.
5DescriptionLabelline_items[].fee_typeDisplaying 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.

Changelog

DateVersionUpdated itemBeforeAfterNotes
2026-09-04v2.0BR_4.6.3.5fee_type ENUM5 values: CHALLENGE_FEE, RESET_FEE, MARKET_DATA_BASE, MARKET_DATA_ADDON, PLATFORM_FEE10 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_typeClient answer 2026-09-04
2026-09-04v2.0BR_4.6.3.5 — how Description is displayedEach fee_type mapped to a human-readable label (Evaluation Purchase, Challenge Reset…); TAX and PLATFORM_FEE were not displayedDescription 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, TAXBA request 2026-09-04
2026-09-04v2.0BR_4.6.3.3 — invoice grouping (new BR)Did not exist. Each invoices record was one row on the UIOne 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 splittingClient answer + BE spec 2026-09-04
2026-09-04v2.0BR_4.6.3.2 — data contractInput was user_id only; output was a flat array; no server-side paging and no server-side sortingInput 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 + pagesBE spec 2026-09-04
2026-09-04v2.0BR_4.6.3.9 — sort · BR_4.6.3.10 — paginationSorting and paging ran entirely on the frontend against the already-loaded payloadSorting 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 cursorBE spec 2026-09-04
2026-09-04v2.0BR_4.6.3.8 — date formatDD MMM YYYY (12 Aug 2026)US format MMM DD, YYYY (Mar 31, 2026)Client answer — standardised across the whole frontend
2026-09-04v2.0BR_4.6.3.12 — records not displayedPayouts were the only exclusionAdded: 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 recordClient answer 2026-09-04
2026-09-04v2.0BR_4.6.3.14 — availabilityStated 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_typeBE confirm 2026-09-04
2026-09-04v2.0BR_4.6.3.7 · §9.2 — column overflowThe Invoice ID and Amount columns carried tooltipsTooltips 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 layoutBA decision 2026-09-04
2026-09-04v2.0Document structure14 BRs, with BR_4.6.3.1 = "Screen Entry Point / Controls Panel"; §9.1 had 11 rowsDropped 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 onlyBA request 2026-09-04
2026-08-28v1.2(see git history)
2026-08-12v1Initial versionUC_4.6.3 createdInit Flow round 4

On this page