Testing
Wallet integration cases to verify before production
Run these cases before enabling production traffic.
The contract lets you hold a separate reserved bucket, but it does not require one. Where a case below mentions reserved, it is observable only if your wallet keeps that bucket. The reference wallet in the demo operator does not: it collapses reserve_cash into an immediate debit, so available drops by the full amount and reserved always reports 0. Both shapes are valid; assert against available to stay portable.
Reservation Cases
| Case | Expected result |
|---|---|
Valid reserve_cash | available decreases by the reserved amount and Phoenix can open the order. reserved increases only if your wallet holds a reservation bucket (the reference wallet debits immediately and keeps reserved at 0) |
Duplicate reserve_cash idempotency key | Stored result is returned and the cash is not reserved again |
| Insufficient funds | HTTP 422 with code: "insufficient_funds" |
| Invalid signature | HTTP 401, no balance change |
| Malformed body | HTTP 400 or safe validation error, no balance change |
Capture and Release Cases
| Case | Expected result |
|---|---|
Partial capture_cash | Finalizes the captured cash. available does not change again (the player already lost it at reservation). If you hold a reservation bucket, reserved decreases by the captured amount; in the immediate-debit reference wallet, capture is a ledger no-op that returns the current balance |
Duplicate capture_cash idempotency key | Stored result is returned and cash is not captured again |
release_cash after cancellation | Unused cash returns to available. If you hold a reservation bucket, reserved decreases by the released amount; the reference wallet applies it as a plain credit to available |
Duplicate release_cash idempotency key | Stored result is returned and cash is not released again |
| Capture plus release exceeds reservation | HTTP 422, no balance change |
| Invalid signature | HTTP 401, no balance change |
Credit Cases
| Case | Expected result |
|---|---|
Sell proceeds credit_cash | available increases by the proceeds |
Settlement payout credit_cash | available increases by the payout |
Refund or void credit_cash | available increases by the refund |
Duplicate credit_cash idempotency key | Stored result is returned and cash is not credited again |
Retry Cases
Phoenix retries 408, 429, 503, transport errors, and timeouts with the same idempotency key, and also retries a 2xx body it cannot classify (no recognised status verb) because commit state is unknown. Idempotency keys make any already-applied move a no-op on the retry.
| Case | Expected result |
|---|---|
Temporary 503 | Phoenix retries with the same idempotency key and later succeeds |
Temporary 429 or 408 | Same: Phoenix retries with the same idempotency key and later succeeds |
Settlement move (capture_cash / release_cash / credit_cash) errors transiently | Phoenix retries the whole settlement batch until it succeeds; settlement moves are never declined, only retried |
reserve_cash business rejection (422) | Terminal: Phoenix declines the order and does not retry |
Status Cases
A status probe is the same signed envelope as the original move (full body, same idempotency-key header) sent to /wallet/transactions/status. Report the recorded outcome for that key without moving money.
| Case | Expected result |
|---|---|
| Completed operation status | Status returns the recorded accepted/rejected outcome and balance for that idempotency key |
| Unknown idempotency key | Return a safe response. Operators may report accepted, rejected, or an explicit unknown; the reference wallet returns accepted for both known and unknown keys, and Phoenix reconciles unknowns from its own event log on the next retry |
Launch Cases
Test wallet behavior together with iframe launch:
- Visitor can browse but cannot place an order.
- Player can place an order after token upgrade.
- Token refresh keeps the session alive.
- Deposit flow returns focus to the iframe.
- Phoenix signature verification passes in the
sandboxenvironment.
Automated self-service tester
Phoenix runs the reservation, capture/release, credit, status, and security cases above against your wallet for you, so you can confirm your integration before launch without hand-driving each call. It signs every probe with the platform key (exactly as production does), drives the cases against your wallet base URL, and returns a per-check verdict.
Authenticate with the same Ed25519 request signature as the rest of the operator API
(X-Operator-Code, X-Operator-Environment, X-Signature-Timestamp, X-Signature).
Target your sandbox environment: the run moves real balances and then restores them, so
it is safe to repeat.
POST /operator/api/wallet-test
Content-Type: application/json
{
"player_id": "<a sandbox player external id with funds>",
"player_id_b": "<optional second player, for the cross-player isolation check>",
"currency": "USD",
"amount": "1.00"
}The response carries the verdict and every check (passed, failed, skipped,
not_applicable), with the request/response transcript per check so a failure points at the
exact bytes. The launch gate cleareds only when every check that ran passed against the
current suite version.
GET /operator/api/wallet-test/runsreturns your recent runs and whether your latest run cleared the gate.
The test player needs at least 10x the test amount available. A check marked
not_applicable (for example the over-reservation case on an immediate-debit wallet) does
not count against the gate.