Hackorda Docs
Flows

F 12 Tester Balance

Status: 🟢 ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: —

Goal

A tester sees their earnings split into 4 financial buckets + a Reports counter, broken down per cycle and per individual issue. They can export their full issue history as a CSV for personal accounting.

Actors

  • Primary — Tester (any user with at least one filed issue)

Preconditions

  • Tester is signed in.
  • Has filed at least one issue across one or more cycles (F-08)

Trigger

Tester clicks "Balance" in the QA section sidebar, navigating to /app/test-cycles/balance.

Happy path — view balance

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1TestersidebarClick "Balance"GET /api/me/balance——balance.viewed
2Frontend/app/test-cycles/balancerenders 5-column header + by-cycle list + issue history table————

The 5 header columns are:

ColumnSourceNotes
Awaiting triageissues with payout_status='pending'Estimated using default rates per severity (forecast, not promise)
Pending verificationpayout_status='approved' AND gate active AND status != 'verified'Approved but waiting for fix re-test
Availablepayout_status='approved' AND (gate inactive OR status='verified')Will be paid in next batch
Total paidpayout_status='paid'Lifetime
Reportscounts only — issuesFiled, issuesApproved, issuesNoPayout, acceptanceRateCounters, not money

Happy path — export CSV

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Testerbalance pageClick "Export CSV"GET /api/me/balance/export.csv—downloads balance.csvbalance.csv_exported
2Browser(file save)—————

CSV columns: date, cycle, title, severity, status, payout_status, amount, currency, paid_at, payment_method, payment_reference.

Acceptance criteria

  • AC-1 — Given a tester with 5 filed issues across 2 cycles, when they open /app/test-cycles/balance, then Reports.issuesFiled = 5, byCycle.length = 2.
  • AC-2 — Given the gate is active and one of their approved issues is not verified, when balance loads, then that issue's amount appears in Pending verification, not in Available.
  • AC-3 — Given the gate is inactive on a cycle, when balance loads, then approved issues from that cycle appear directly in Available.
  • AC-4 — Given an issue is paid, when balance loads, then it appears in Total paid and the issue history row shows paid_at + payment method + reference (truncated link tooltip with reference).
  • AC-5 — Given the tester has issues in 3 currencies (KZT, USD, EUR), when balance loads, then each header column shows separate lines per currency.
  • AC-6 — Given acceptanceRate is computed as issuesApproved / (issuesApproved + issuesNoPayout), when the user has 4 approved + 1 rejected, then acceptanceRate = 0.8 (UI shows "80%").
  • AC-7 — Given the user clicks Export CSV, when the response arrives, then a text/csv blob downloads with column headers + one row per issue in their history.
  • AC-8 — Given an admin in impersonation mode (F-03), when they navigate to /app/test-cycles/balance, then they see the target's balance, not their own.

Test data / fixtures

  • A tester with at least:
    • 1 pending issue
    • 1 approved-but-unverified issue (gate active cycle)
    • 1 verified-and-paid issue
    • 1 rejected issue Across 2+ cycles, in 1+ currency.

Negative paths

#ScenarioExpected behavior
N-1Tester has filed zero issuesAll 5 columns show "—"; cycle list empty; history table empty
N-2Tester has filed only draftsSame as N-1 (drafts are excluded)
N-3Unauthenticated GETAPI 401
N-4CSV download with 0 rowsCSV with header row only

Manual QA checklist

  • As a tester with mixed-state issues, open /app/test-cycles/balance
  • Confirm 5 header tiles render
  • Confirm "Pending verification" + "Available" sum to legacy "approvedUnpaid" (the deprecated combined bucket — verify shape via DevTools network)
  • Confirm by-cycle list shows N rows, each linking to that cycle
  • Confirm issue history table shows all non-draft issues, with severity / status / payout-status badges, and paid-at + method where applicable
  • Click an issue title → navigates to issue detail
  • Click "Export CSV" → file downloads with N+1 lines (header + N rows)
  • Open CSV in spreadsheet → columns line up; payment method values use friendly labels (Bank not bank_transfer) — verify
  • As an admin, impersonate a tester (F-03) → balance shows target's data
  • Sign out, navigate to /app/test-cycles/balance → redirects to sign-in

Automated test outline

test.describe("F-12 tester balance", () => {
  test("buckets reflect gate state", async ({ request }) => { … });
  test("acceptance rate calc", async ({ request }) => { … });
  test("multi-currency totals", async ({ request }) => { … });
  test("csv export shape", async ({ request }) => { … });
  test("impersonation reflects target", async ({ page }) => { … });
});

Code references

  • Pages: src/app/app/test-cycles/balance/page.tsx
  • UI: src/components/test-cycles/BalanceSummary.tsx
  • API:
    • src/app/api/me/balance/route.ts
    • src/app/api/me/balance/export.csv/route.ts
  • Hooks: src/hooks/test-cycles/balance.ts
  • Lib: src/lib/issue-payout.ts → requiresVerification
  • Schema: issues, issue_payouts, test_cycles, products

Events emitted (proposed)

  • balance.viewed — { user_id, currencies: [...], total_paid_cents, total_available_cents }
  • balance.csv_exported — { user_id, row_count }

Open questions / known gaps

  • The deprecated approvedUnpaid bucket still ships in the API response for backward compat (@deprecated tag in the type). Plan a removal pass once the BalanceSummary UI is fully on the new split.
  • "Awaiting triage" is an estimate using default per-severity rates, not the per-cycle rates. Could refine later.
  • No "request payout" flow — payouts are admin-initiated only (F-11).
  • No filter / search inside the issue history table — gets long on prolific testers.

On this page