# Receipt UI contract for the future backend

User-approved direction for `admin-atelier-v2`; this document must be consulted when wiring the receipt UI to the bot. The current implementation is a local mock, not a live payment service.

## Existing bot behavior to preserve

Source: `C:/Projects/Bots/Abnabot/handlers/admin_handler.py`, `handle_get_payment_command`, `_process_payment_acceptance`, `handle_payment_rejection`, `handle_accept_payment_custom_prompt`, and `handle_accept_payment_cancel_custom`. Receipt buttons come from `handlers/base_handler.py::build_card_receipt_action_markup`.

| Payment state | Permitted UI operations |
| --- | --- |
| WaitingForAdmin (10), WaitingForUser (1), Expired (40) | Approve requested amount, approve actual amount, reject with reason |
| Rejected (30) | Approve requested amount or actual amount; no second rejection |
| Approved (20), or any linked WalletTransactionID | Read only; no undo, reopening, or second credit |
| CanceledByUser (50) | Read only; cannot approve |
| Awaiting custom amount, otherwise creditable | Enter actual amount or cancel custom mode; normal approval/rejection blocked |
| Not card-to-card (PaymentTypeID != 10) | Refuse this receipt workflow |

`/GetPayment_<id>` re-displays the payment and its permitted actions. It does **not** change a rejected payment back to Pending. The existing search field accepts a payment ID or an exact `/GetPayment_<id>` command; the command searches across archive statuses. Opening its result fetches current details and permitted actions. There is no separate lookup button or dialog. Closing a detail or custom-amount sheet is not an approval or a reversal.

There is no internal-note field, defer action, review undo, or invented per-receipt audit timeline in this UI. "History" means the paginated list of payment records. The custom-mode cancel button cancels only the pending amount-entry mode, never an approved payment.

## Pagination and image budget — required, not optional

1. Fetch aggregate counts separately, without downloading receipts/images. Card capsules contain `card_id`, display name, card suffix, and `pending_count`; return only cards with a positive Pending (10) count. Never compute those totals from the currently loaded page.
2. **List:** retrieve at most ten metadata records per request. Fetch the next ten when the end of the current page enters view, or on the accessible next-page button. Status filters (all/rejected/etc.) and search run on the backend. A query/filter change starts a new cursor and invalidates stale in-flight responses.
3. **Deck:** hold a rolling window of at most five receipt records and preload only their five images. After one review removes an item, request one replacement, keeping five ready while possible. With six remaining, initial load is five; the sixth is loaded after the first decision, not after the fifth. Keep the next card's receipts in this same window so automatic card handoff does not incur a fresh queue load.
4. Use a **stable cursor/keyset**, not a numerical offset into a changing Pending set: removing reviewed receipts must not skip the next row. The mock uses a frozen card order and `(card rank, payment ID)` cursor for the deck, descending payment ID for history. Cursors are opaque to the client, bound to status/card/search/order and independent of batch size.
5. A summary plus paginated records is mandatory even when the server has 100+ receipts. Never return the entire history for the client to slice. Server-side limits: ten for history, five for deck, including refill requests smaller than five.
6. List images load near the viewport, only for records already fetched. The mock reuses the six original JPEGs because there is no thumbnail service. A real response should provide a lightweight `thumbnail_url` plus the original `image_url`; full resolution is for the active receipt/viewer. Missing media must retain text details and permitted actions.
7. Cancel/disregard obsolete reads on card/tab/search changes and navigation. Prevent duplicate concurrent pagination calls. Keep the existing four ready deck receipts visible while a replacement loads. Provide retry for page/image failures and a keyboard-accessible load-more fallback.
8. Search updates must preserve the input DOM node, focus and selection. Replace only results, never the input or its parent toolbar on each response; repeatedly focusing a newly created input is not an adequate mobile-keyboard fix.

## Current adapter routes (proposed wire shape)

| Method / route | Response / purpose |
| --- | --- |
| `GET /receipts/summary` | `{cards:[{id,name,number,holder,pending_count}], counts:{pending,approved,rejected,expired,canceled,waiting_user,all}}` |
| `GET /receipts?mode=list&status=all&limit=10&cursor=…&q=…&card_id=…` | `{items, total, has_more, next_cursor}` |
| `GET /receipts?mode=deck&cards=1,2,3&limit=5&cursor=…` | Same envelope; pending only, ordered across the selected and following cards |
| `GET /receipts/:id` | Payment details + current server-derived action capabilities |
| `POST /receipts/:id/review` | `{action:approve\|reject, custom?, amount?, reason?, expected_version}` → `{receipt,summary}` |
| `POST /receipts/:id/custom-amount` | `{enabled:boolean, expected_version}` → updated receipt |

Each detail contains `actions:{approve,approve_custom,reject,cancel_custom,read_only_reason}` derived by the server from current payment status, linked credit, type and custom-mode flag. Controls must follow these flags; the server must re-check every write against the latest record. A stale approval must never double-credit a wallet. Requested Amount remains separate from ApprovedAmount. The existing bot's actual credit, cashback/referral effects and notifications must remain authoritative; the mock currently changes receipt metadata only.

## UI continuity

Capsules filter the pending queue. Archive tabs are global so cards whose queue has finished remain searchable in history. When a selected card reaches zero: complete its counter, shrink/fade its capsule, move the remaining capsules into place, select the next nonempty card, and reveal the prepared next receipt. Preserve a clear empty state after the final card. Respect reduced motion.

Reference timings: count feedback ~190 ms, receipt departure ~240 ms, capsule completion/collapse ~360 ms total, new card/position settling ~340 ms with overlap. Never animate unconfirmed success.

## Mock-only fixtures and reset

Six pending images were supplied by the user. Thirty historical demo records reuse those images to exercise pagination; no extra photos were invented. Fixture `4128` is an expired receipt awaiting custom amount; `4101` is approved, `4102` rejected, `4103` expired and `4104` canceled. Default pending IDs are `4201`–`4206`.

`POST /receipts/reset-preview`, demo fixture seeding, unrestricted preview access and all shared-world adapters must be removed from any eventual production integration. No backend work has been authorized or implemented by this design revision.
