# Payment controls test pack

This pack contains synthetic cases for the 15 Simpa Labs payment-control guides published in September 2026. No row is a claim about a real provider or customer system.

## Use the cases

1. Copy `cases.csv` into a test plan. Keep the case ID.
2. Replace the synthetic account, payment, and amount with test records from your own staging system.
3. Run the action through the customer app and direct API where both paths exist.
4. Save the request, provider reference, stored state, ledger entries, and the final customer message. Mask secrets and personal data.
5. Compare the observed result with `expected_result`. Record any mismatch and retest after the fix.

The `expected_result` column is a control goal, not a claim that every provider uses the same state names or event format. Match the test to your provider's current docs. Do not run transfer or refund tests against live money without an approved scope.

## Field definitions

| Field | Meaning |
|---|---|
| `case_id` | Stable ID for the synthetic case |
| `control` | Security or accounting rule under test |
| `fixture` | Starting test state |
| `action` | One event or request to send |
| `expected_result` | State that should hold after the action |
| `evidence` | Records to inspect before closing the test |

Version 1.1, revised October 1, 2026. Authors: Simpa Labs. License: CC BY 4.0. Keep the source link and note any changes when reusing this pack.


## Run a valid baseline first

Prove one permitted action succeeds with the same actor, route, payload shape, and test account before testing a denial. A parser error, expired token, or broken provider connection can hide a missing control. Record those outcomes separately from an authorization or money rule failure.

All numeric amounts in the fixtures use integer minor units, not naira. For NGN, 10000 kobo equals ₦100. The concurrency case starts both workers at the same balance decision; sequential requests do not test that race. Use distinct business IDs for distinct withdrawals and the same ID only for a retry.

## Policy choices and injected faults

PAY-01 tests an internal capture command before local approval. A genuine provider capture is an external fact: preserve it and reconcile a policy conflict rather than rejecting its existence. PAY-09 chooses a hold-and-review rule after a beneficiary change. Record your chosen rule before running the case. PAY-10 assumes the new row matches payment ID, amount, and currency; a fee or amount mismatch stays open.

Use a provider stub or fault injection for unknown responses, failures, and reversals that a sandbox cannot produce. Keep those results labeled simulated. A simulated pass proves local handling for the injected response; it does not prove real provider delivery or bank movement. PAY-08B checks reversal as a separate event, and PAY-13B uses two distinct refund requests to test the shared budget.

## Keep a run record

For each case, save test date, app build, database isolation level where relevant, provider adapter version, fixture IDs, permitted baseline, action, response, stored-state readback, expected result, observed result, and reviewer. Use pass, fail, or blocked. A missing provider status or incomplete trace is blocked until resolved; it cannot count as pass. Read affected records through an authorized account or the test database. Logs support that readback but do not replace it.

The original PAY-01 through PAY-15 IDs remain stable. Added B, C, and D cases cover separate failure paths. Version 1.1 sharpens fixtures and expected results, so record the pack version when comparing old runs.
