# Token X Connect Cloud: Pre-Device Test Checklist

## Goal and scope

Prepare devices.apper for controlled physical tests with a Token ÖKC terminal. Complete the software, configuration, security, and isolated-test checks before sending a sale to the device. The connection is wireless via Token X Connect Cloud: the application calls Token's HTTPS API, and the terminal uses Wi-Fi/internet. No USB, serial, or direct LAN transport is used, and the application need not share the terminal's Wi-Fi network. The Wire reference below documents only the shared basket JSON schema linked from Cloud documentation.

Status (2026-09-23): Steps 1 and 3 passed for the local CLI runtime. The user confirmed test company `1` and team `1`; both database connections now work. Active local integration `1` stores the QR-derived terminal/branch/merchant identifiers, mode `1`, a six-section fiscal snapshot with fetch time, six category mappings, and ten explicit product mappings. All ten products pass fiscal validation. Step 2 still needs operator/provider confirmations. Step 4 pricing is verified offline: gross unit price uses stored order price plus per-unit VAT, and sales `109` and `114` match exactly at TRY 1,790.00 and TRY 575.00. Step 5 is now partially implemented in code: atomic per-sale duplicate prevention (unique `active_sale_id` slot, migrated on the local DB), completed-payment resubmission blocking, state-guarded outbound updates that cannot regress an early webhook, strengthened pre-send Get Terminal validation (identity/branch/mode), a `require_callback_auth` enforcement knob, explicit authorization (the authenticated route group requires the `check.user.access` posmanager gate; `sendSale()` requires company-admin `pos` access), and a conservative recovery/reconciliation procedure (open-basket `reconcileBasket()` + per-code `classifyProviderError()`, durable `reconciled_at`/`recovery_log`, two admin-gated recovery routes), plus a mandatory-fresh-fiscal-snapshot send gate (`assertFiscalSnapshotFresh()` behind the default-off `require_fresh_fiscal_snapshot` knob), a pre-send routing-identity gate (`assertRoutingIdentity()`: instant mode needs a nonempty `terminal_id`, list mode a nonempty `branch_id`), and a callback-clientId match gate (`verifyCallbackClient()` behind the default-off `require_callback_client_match` knob) — all covered by isolated tests. The focused suite passes 150 tests (461 assertions). Still open: enabling/confirming terminal verification, callback auth, and the fresh-fiscal-snapshot gate in the live runtime, provider unlock/delete operations (no confirmed endpoint), and a public HTTPS callback with verified synthetic delivery. Physical-test sale/amount/method/operator approval remains open. Payment testing is NOT ready.

Historical test result: `php artisan test --filter=TokenOkc` was previously recorded as 55 passed (106 assertions). The focused suite was not rerun in Step 1; fresh local boot, configuration, credential-guard, and route checks are recorded below. No Token API call, callback registration, physical payment, migration, or shared-POS write was performed in Step 1.

This checklist authorizes no implementation, credential changes, callback registration, or transactions by itself. The user authorized local setup, supplied the terminal QR identifiers, confirmed company/team ownership, and requested application of Step 4 configuration. The user explicitly declined an early sale/device test. Local credentials, scoped API discovery, basket storage, integration/fiscal configuration, and offline pricing reconciliation for unadjusted sales are complete; Steps 5–6 and the remaining operator/provider approvals stay open. No simulator/Postman basket bypass will be used. Keep credentials, access tokens, terminal/branch/merchant identifiers, customer data, and raw payment payloads out of this document and Git.

## Recommended execution order

Follow steps 1–6 in order. Authentication and read-only discovery may precede the payment gate. Callback changes require the explicit approval and safety checks in step 5; basket submission must wait until step 6. An unchecked item is still pending even when its supporting code already exists.

### Step 1 — Secure credentials and verify effective configuration

- [x] Verify Git tracking and ignore rules: `.env` and the local credential document are ignored and untracked; `git log --all` found no history at those paths in locally available refs. This does not establish whether the credentials were shared elsewhere; rotate them if exposure is discovered.
- [x] Configure `TOKEN_OKC_CLIENT_ID` and `TOKEN_OKC_CLIENT_SECRET` in the ignored local `.env`. The application does not read the credential Markdown document; `secret_id` is not a supported configuration key. No credentials were added to tracked files or frontend variables.
- [x] Verify effective `TOKEN_OKC_BASE_URL` and `TOKEN_OKC_AUTH_ENDPOINT` against the provider-supplied test URLs. Fresh CLI checks report both credentials present, matching test endpoints, `APP_ENV=local`, and configuration caching disabled; no cache clear was needed.

Verification record (2026-09-23):

- Laravel console bootstrap: passed before and after configuration; credential-presence flags changed from false to true. Runtime diagnostic output contained no credential values or access tokens.
- `assertCredentialsConfigured()`: passed with stray HTTP requests prohibited; `Http::assertNothingSent()` passed, and the connection manager reported zero opened database connections.
- `php artisan route:list --path=token-okc -v`: all eight routes resolve; browser-facing routes retain authentication middleware. Listing routes did not invoke their handlers.
- Scope: local CLI configuration only. No service was started/restarted, no token was requested, and no device, callback, or database schema operation was performed. Recheck effective configuration in any separate web/deployment runtime before its tests.

Exit condition: MET for the local CLI runtime. Laravel boots with the intended test endpoints and nonempty credentials; no payment or callback-setting request has been made. Provider acceptance was subsequently verified in Step 3 below.

### Step 2 — Identify the intended test terminal

- [x] Receive the device QR identifiers from the user. The terminal has the documented AV prefix; branch and merchant identifiers pass UUID-format validation. Values are intentionally excluded from this shared checklist.
- [x] Record the identifiers in the authorized terminal configuration. Company/team `1` were confirmed by the user; active local integration `1` now contains the supplied identifiers.
- [x] Verify through the test API that the returned terminal and branch match the supplied identifiers and the reported mode is `1`, the intended first instant-payment mode.
- [x] Confirm the application test company/team. Both database records identify Local Hotel; the team owner's current team is `1`, and a team-scoped service lookup returns the saved terminal configuration.
- [ ] Confirm device internet access with TokenX Connect open. API lookup success alone does not prove current device connectivity.
- [ ] Confirm with Token that the supplied merchant/account assignment and any required payment instruments belong to the test environment. The test API recognizes the terminal/branch, but the merchant association and payment-instrument setup are not independently verified.

Exit condition: PARTIAL. The API terminal and application ownership/configuration are established; no LAN IP or fallback terminal was used. Operator/provider confirmations remain pending.

### Step 3 — Authenticate and perform read-only discovery

- [x] Run **Authenticate → Get Terminal → Get Fiscal Parameters**, using the confirmed test URLs. A fresh authentication request returned a nonempty access token; subsequent reads succeeded with provider status `0`.
- [x] Verify the configured terminal and fiscal endpoint paths through actual successful test responses: `GET /v1/terminal` and `GET /v1/fiscal-parameters`, both using the `terminal-id` header.
- [x] Inspect Get Terminal's `result` array: one returned entry matched the supplied identity in `id`, matched `branchId`, and reported `mode: 1`. The fiscal response's `result.terminal` matched the same terminal.
- [x] Inspect all six returned fiscal sections and record only sanitized outcomes. No basket was sent and no callback settings were read or changed.

Verification record (2026-09-23):

- Discovery was performed before company/team assignment because these API reads need only the supplied terminal identity; no application/POS database was queried or modified.
- One fresh authentication call and four GET calls were made: terminal and fiscal responses were each queried twice to inspect their shape and selected fields. No credentials or access tokens were printed. The existing service cached the token using the configured local file cache.
- Returned fiscal section/tax pairs (`taxPercent` uses percentage × 100): `1` YİYECEK → `1000` (10%); `2` ALKOL → `2000` (20%); `3` TEKEL → `0` (0%); `4` İÇECEK → `1000` (10%); `5` EKMEK → `100` (1%); `6` GİYİM → `2000` (20%). These are provider-returned configuration values, not independent tax advice or an automatically applied product mapping.
- No application code, `.env`, integration record, fiscal mapping, schema, or callback configuration was changed. The focused automated suite was not rerun. Only this checklist was edited, apart from the service's normal token-cache write.
- Merchant association, current physical-device connectivity, fiscal snapshot freshness, and payment readiness were not established by these reads. Refresh fiscal data before applying mappings or running an approved payment test.

Exit condition: MET for scoped read-only API discovery. Authentication and terminal/fiscal reads succeed for the intended device; Step 2 application setup and all remaining payment gates stay open.

### Step 4 — Prepare local persistence and fiscal mapping

- [x] Verify the effective application connection and basket schema. The default and basket-model connection are `mysql`, configured on a loopback host with a database name different from `mysql-remote`. Local schema metadata reads succeeded; the basket table was initially absent.
- [x] Apply only `2026_09_22_000000_create_token_okc_baskets_table.php` to the confirmed local `mysql` application database, after inspecting its `--pretend` output. The migration completed and was recorded; no migration or fixture was run on `mysql-remote`.
- [x] Restore authorized read access to `mysql-remote`. The user corrected local database configuration; both connection targets and the required POS schema/data were verified. The app's default connection remains `mysql`.
- [x] Configure an active integration for test company/team `1`, with explicit terminal identity, branch, and mode. Integration `1` was created only in the local application database, with the team owner as its required `user_id`.
- [x] Persist the selected terminal's fiscal response and validate product/category-to-section and tax mappings. Six category mappings and ten product mappings pass against the refreshed six-section response. `fiscal_parameters_fetched_at` is stored, but freshness enforcement remains a Step 5 requirement.
- [x] Complete offline pricing reconciliation for supported unadjusted sales. Stored per-unit `price + vat` supplies the gross unit price; each line and the sale total are checked. Sales `109` and `114` now match exactly. No `reconcile=false` bypass, invented sale, POS mutation, or device request was used.
- [ ] Select and approve the separate low-value physical-test sale, amount, method, and operator after the safety gates pass. The two existing offline examples are not approved device tests. Automatic paid-state updates to the shared POS sale remain outside this workflow.

Verification record (2026-09-23, following the user's decision to continue the plan without an early device test):

- `php artisan migrate --database=mysql --path=database/migrations/2026_09_22_000000_create_token_okc_baskets_table.php --pretend --no-interaction` listed only the basket table and its indexes; the same targeted command without `--pretend` completed successfully.
- Post-migration metadata reads confirmed all 27 expected columns, the primary key, the unique `basket_uuid` index, and nonunique indexes on `sale_id`, `company_id`, `current_team_id`, and `state`. The table is empty and the migration ledger contains the migration. UUID uniqueness alone still does not solve concurrent submissions for the same sale.
- `mysql-remote` is also configured with a loopback host and has no password in its effective connection configuration. Authentication was denied; this does not establish the correct host, account, or required authentication method. No credential values or database names were printed.
- Database writes were limited to creating the local basket table/indexes and its migration-ledger entry. No integration assignment, test fixture, sale write, new Token API request, callback change, or device operation occurred. Runtime inspection commands prohibited stray Laravel HTTP requests; the final check asserted none were sent. The focused automated suite was not rerun.

Follow-up verification (2026-09-23, after the user confirmed connectivity/company/team and requested application):

- Refreshed Get Terminal and Get Fiscal Parameters twice (dry run, then apply): four allowlisted GET requests total, using the cached token. Terminal identity/branch/mode and all six fiscal section/tax pairs matched. No basket or callback endpoint was permitted by the setup runner; redirects were disabled.
- POS category assignments are in `category_product`; order/product `category_id` values are null. Explicit local `product_section_map` entries override existing POS tracking sections without rewriting POS records. Nonalcoholic drinks map to section `4`, food to `1`, alcohol to `2`; all ten products' VAT rates match. The model's separate `categories()` relation names `category_products`, so setup used the verified actual pivot table directly; that unrelated relation was not changed.
- Saved `fiscal_parameters` (the provider's terminal-scoped result) and its fetch timestamp inside `data.terminals[]`, plus both mapping dictionaries. Creation refused existing/soft-deleted company integration records, used a local transaction, and verified the JSON round trip. A subsequent service lookup under the confirmed team-owner context found the configuration.
- Fiscal checks validate snapshot identity, nonempty/unique/integer sections, tax range, section existence, and exact tax compatibility. Legacy configurations without `fiscal_parameters` retain prior behavior; requiring a fresh snapshot for every send remains open.
- All ten products passed unsaved in-memory mapper checks. Existing sales failed strict total reconciliation: sale `109`, item sum `162727` versus net `179000` kuruş; sale `114`, item sum `52273` versus net `57500` kuruş. In these rows, `orders.price + orders.vat = orders.total_price` at quantity `1`, and summed `total_price` matches the sale total. At that stage, the mapper ignored order VAT; the later pricing follow-up below resolves this for supported unadjusted sales without using a guessed tax factor. Fiscal adjustment support remains pending.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **75 passed, 187 assertions**. Mapper tests assert no HTTP or opened PDO connections. Send-flow tests use isolated SQLite for both connections and prohibit stray HTTP; mocked cases verify stored mappings and rejection before basket persistence/submission. This does not close the remaining Step 5 scenarios.
- Writes were limited to the local integration and source/test/checklist edits. Setup POS reads ran inside a read-only transaction. The local basket table remains empty; no payment request, POS write, migration, callback change, or web-service startup occurred. The temporary ignored setup runner was removed after use.

Pricing follow-up verification (2026-09-23, supersedes the pricing failures and 75-test baseline above):

- POS history confirms `orders.price` is tax-exclusive per unit, `orders.vat` is the VAT amount per unit, and `orders.total_price` is the gross line total. Multi-unit and fractional examples establish `(price + vat) × quantity`; current product catalog prices are not substituted for stored historical amounts.
- The mapper requires finite, nonnegative stored price/VAT/line-total amounts. Stored VAT must agree with the fiscal rate within one kuruş per unit, with no nonzero VAT at a zero rate. Reconstructed line totals must match within one kuruş even when sale-level reconciliation is disabled. Sale-level tolerance counts only eligible lines; a missing sale total still skips that aggregate check, so line checks do not prove an absent sale total.
- Quantities must be representable in Token thousandths. Nonzero sale discounts, discount percentages, service fees, and complimentary-item indicators, as well as line service fees or complimentary statuses, are explicitly rejected. These guards are not fiscal support for adjustments; that remains pending.
- Offline mapper checks with the stored integration and default reconciliation passed exactly: sale `109`, 5 items, `179000` kuruş (TRY 1,790.00); sale `114`, 3 items, `57500` kuruş (TRY 575.00). Both omitted `paymentItems`. Both database connections ran in read-only transactions; zero HTTP requests were recorded, and the local basket table had zero rows.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **107 passed, 301 assertions**. Added coverage includes gross pricing, stored VAT, fractional/multi-unit quantities, rounding, invalid fields, unsupported adjustments, independent line checks, and eligible-line tolerance. Mocked send-flow coverage verifies gross prices and unchanged POS amounts. PHP lint and `git diff --check` passed.
- No Token API call, basket submission, callback change, write/migration to the actual application or POS databases, or web-service startup occurred during this pricing follow-up. Automated fixtures used isolated in-memory SQLite databases. Cloud/wireless transport is unchanged. The temporary read-only pricing runner was removed after validation.

Exit condition: PARTIAL only for physical-test sale/amount/method/operator approval. Local integration, fiscal mapping, storage, and supported unadjusted real-sale pricing are verified. Step 5 safety work remains required; no device submission is permitted.

### Step 5 — Complete safety fixes and verify the callback path

- [x] Make duplicate-submission prevention atomic per sale and prevent accidental resubmission of already completed payments. A nullable UNIQUE `active_sale_id` slot (migration `2026_09_23_000000_...`) holds the sale id while a basket is live and is released to NULL on terminal states, so two concurrent live attempts collide at the database level; a completed payment for the sale is rejected before submission. Uncertain attempts keep the slot and reuse their identity on retry rather than minting a new UUID.
- [x] Make outbound response updates conditional so a late acceptance/error response cannot overwrite an earlier webhook's completed or otherwise advanced state. Submission outcomes now re-read the row under a lock and apply only while it is still `submitted`; the returned state reflects the actual (possibly webhook-advanced) state.
- [~] Validate the real Get Terminal result, including requested identity, branch, and mode. `assertTerminalUsable()` now rejects an empty result, a missing/mismatched terminal identity, and branch/mode mismatches. Enabling `TOKEN_OKC_VERIFY_TERMINAL` in the live runtime and rejecting incompatible fiscal mappings at send time remain open.
- [~] Require configured callback authentication before exposing the payment callback. A `require_callback_auth` knob now makes an unconfigured secret a hard 401 instead of an open callback. Verifying Token's actual `callbackAuth` header/format in the live runtime remains open.
- [x] Enforce explicit authorization for payment submission and client-level callback changes (a logged-in session is not sufficient). The authenticated `token-okc` route group now also requires the existing `check.user.access` posmanager gate (covering `set-webhook`, `send-sale`, and the read endpoints; the public webhook is unaffected), and `sendSale()` additionally requires an admin-level `pos` access for the sale's own company via `authorizeCompanyAdmin()`, so a user can only submit payments for companies they administer.
- [~] Extend isolated tests for completed-sale resubmission, database-level duplicate prevention, early webhooks versus a late response, runtime terminal verification (mode mismatch, missing terminal, matching identity), and authorization (posmanager route gate present/absent, company-admin allow/deny across company, team, level, and unauthenticated callers). Simultaneous in-process submissions and real end-to-end HTTP middleware/CSRF behavior remain open. Every test still isolates both connections and prohibits stray HTTP.
- [x] Run `php artisan test --filter=TokenOkc` and record fresh results: 134 passed (424 assertions) after the Step 5 code-level safety fixes plus the recovery procedure (124/345 before recovery).
- [x] Prepare a recovery procedure using the confirmed open-basket query for duplicate, locked, open, wrong-mode, and uncertain outcomes before any payment test. `reconcileBasket()` + `classifyProviderError()` (Section 5) reconcile conservatively and never resubmit blindly; direct provider unlock/delete operations stay open because no such endpoint is confirmed.
- [ ] Prepare a public HTTPS callback with authentication. Verify synthetic delivery against a known local test basket, including invalid-auth rejection and duplicate-event handling, without creating a device payment.
- [ ] Inspect the current client callback through a confirmed provider endpoint before changing it. Register/replace it only with explicit approval; the client-level URL is shared and can affect other integrations.

Verification record (2026-09-23, Step 5 code-level safety fixes):

- `app/Services/TokenOkc/TokenOkcService.php`: added a completed-payment resubmission block, wrapped the pre-send persist in a unique-violation guard (`isUniqueViolation()`), routed all submission outcomes through a state-guarded `transitionFromSubmitted()` (only `submitted` may advance; failed releases the slot, uncertain holds it), and strengthened `assertTerminalUsable()` to require identity plus branch/mode. `authenticateCallback()` now honors `require_callback_auth`. Completion webhooks release `active_sale_id`.
- `app/Models/TokenOkcBasket.php`: added `active_sale_id` cast plus `liveStates()`/`stateHoldsActiveSlot()` helpers. `config/token_okc.php`: added `require_callback_auth` (default false, preserving existing behavior).
- Migration `2026_09_23_000000_add_active_sale_id_to_token_okc_baskets_table.php` was applied ONLY to the local `mysql` (`apper`) database after `--pretend`; post-migration reads confirm the column, the `token_okc_baskets_active_sale_id_unique` index, and `basket_rows: 0`. No `mysql-remote` (POS) schema or data was touched; no basket row was created.
- `routes/web.php` + `app/Http/Controllers/TokenOkcController.php`: the authenticated `token-okc` group now carries the existing `check.user.access` posmanager middleware; `sendSale()` loads the sale and calls a new `authorizeCompanyAdmin()` helper (admin-level `pos` access scoped to the sale's `company_id` and current team) before any provider call. The helper bypasses the global team scope in favour of an explicit `current_team_id` filter and aborts 403/422 outside the try/catch so the status is not masked as a 500. The public webhook is unchanged.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **124 passed, 345 assertions**. New coverage: completed-payment resubmission block, DB-level unique slot, released slots do not collide, early-webhook completion not regressed by a late response, terminal verification (mode mismatch/missing/matching), callback rejection when auth is required but unconfigured, and authorization (posmanager gate present on authenticated routes / absent on webhook; company-admin allow, and deny for non-admin level, other company, other team, invalid company, and unauthenticated callers). `php -l` and `git diff --check` are clean.
- No Token API call, basket submission, callback registration, or POS write occurred. Terminal verification and callback-auth enforcement remain OFF by default in config; they are code-ready but not yet enabled/confirmed for the live runtime.

Verification record (2026-09-23, Step 5 recovery procedure):

- `app/Services/TokenOkc/TokenOkcService.php` (Section 5): added `classifyProviderError()` (1007/1018/1100/1103/1104/1106 → non-retryable guidance; unknown/null → manual review), `getOpenBasketsForCompany()` + `extractOpenBasketIds()` (read-only open-basket query with defensive id normalization/dedup), `reconcileBasket()` (conservative; provider call outside the row lock, state change under `lockForUpdate` with a finality re-check), and `recoveryReport()`. Reconciliation never marks a basket completed/paid and never resubmits; it upgrades only `uncertain`/`submitted` → `accepted` on positive open-basket evidence.
- `app/Models/TokenOkcBasket.php`: added `recovery_log` (array) + `reconciled_at` (datetime) casts and an append-only `appendRecoveryLog()` helper. `app/Http/Controllers/TokenOkcController.php`: added read-only `recoveryOpenBaskets()` (base POS access) and `reconcile()` (company-admin via `authorizeCompanyAccess($id, true)`), generalized `authorizeCompanyAdmin()` into it, and enriched `sendSale()` 502/504 responses with `classifyProviderError()`/reconcile guidance. `routes/web.php`: two recovery routes inside the posmanager-gated group.
- Migration `2026_09_23_000001_add_recovery_fields_to_token_okc_baskets_table.php` (idempotent) was applied ONLY to the local `mysql` (`apper`) database after `--pretend`; post-migration tinker reads confirm `db=apper`, `has_reconciled_at=yes`, `has_recovery_log=yes`, and `basket_rows=0`. No `mysql-remote` (POS) schema or data was touched; no basket row was created.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **134 passed, 424 assertions**. New `TokenOkcRecoveryTest` (10 tests, isolated SQLite + `Http::preventStrayRequests`, provider reached only via faked `/v1/baskets`) covers code classification, final short-circuit with `Http::assertNothingSent()`, uncertain/submitted still-open → accepted (with `recovery_log`/`reconciled_at`/`accepted_at` set and `isPaid()` false), not-open leaving state unchanged, accepted not regressed, unknown-basket `ValidationException`, id normalization/dedup, and missing-integration rejection. The two recovery routes were added to the authorization posmanager-gate assertion. `php -l` on all edited files, `git diff --check`, and `route:list -v` (both recovery routes carry `CheckUserAccess`) are clean.
- No Token API call, basket submission, callback registration, provider unlock/delete, or POS write occurred. Recovery is code-complete and tested but unexercised against the live provider.

Verification record (2026-09-23, Step 5 mandatory fresh fiscal snapshot):

- `config/token_okc.php`: added `require_fresh_fiscal_snapshot` (env `TOKEN_OKC_REQUIRE_FRESH_FISCAL`, default false) and `fiscal_snapshot_max_age` (env `TOKEN_OKC_FISCAL_MAX_AGE`, default 86400s). Default-off preserves existing behavior, matching the `verify_terminal_before_send` / `require_callback_auth` pattern.
- `app/Services/TokenOkc/TokenOkcService.php`: added `assertFiscalSnapshotFresh(array $terminal)`, called in `sendSaleToOkc()` immediately after `resolveTerminal()` and BEFORE the optional Get Terminal HTTP call, the completed/duplicate checks, token fetch, mapping, and persistence — so a stale/missing snapshot fails fast with no I/O and no basket row. When the knob is on it rejects: a missing/empty `fiscal_parameters.sections`, a missing/empty `fiscal_parameters_fetched_at`, an unparseable timestamp (`Carbon::parse` guarded by `catch (Throwable)`), and an age greater than `fiscal_snapshot_max_age`. Added `use Illuminate\Support\Carbon;`.
- The live Integration `1` already stores `fiscal_parameters_fetched_at` (ISO8601 with offset), confirmed by a read-only tinker key inspection; no integration record, schema, or POS data was modified, and no migration was required for this change.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **140 passed, 439 assertions** (up from 134/424). `TokenOkcSendFlowTest` adds 6 cases (fresh allows submission; stale, missing snapshot, missing timestamp, and invalid timestamp each block with `ValidationException`, zero basket rows, and `Http::assertNothingSent()`; stale allowed when enforcement is disabled). `php -l` and `git diff --check` are clean.
- No Token API call, basket submission, callback registration, or POS write occurred. The gate is code-ready and tested but stays OFF by default; enabling it live is a separate gated step.

Verification record (2026-09-23, Step 5 routing-identity gate + callback clientId match):

- `config/token_okc.php`: added `require_callback_client_match` (env `TOKEN_OKC_REQUIRE_CALLBACK_CLIENT_MATCH`, default false) and `callback_client_id` (env `TOKEN_OKC_CALLBACK_CLIENT_ID`, default empty, falls back to `client_id`). Default-off preserves behavior, matching the existing knob pattern.
- `app/Services/TokenOkc/TokenOkcService.php`: added `assertRoutingIdentity(array $terminal)`, called in `sendSaleToOkc()` right after `assertFiscalSnapshotFresh()` and before any HTTP/token/persistence. Instant mode (1) requires a nonempty `terminal_id`; list mode (0) requires a nonempty `branch_id` (whitespace-only rejected) so a list-mode basket can never be sent with a blank `branch-id` header. Added `verifyCallbackClient(mixed $clientId)`, called in `processWebhook()` after envelope-field validation: when `require_callback_client_match` is on it compares the envelope `clientId` to the expected identity with `hash_equals`, rejecting (401) a mismatch or an unconfigured identity; when off it is a no-op.
- Focused tests: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **150 passed, 461 assertions** (up from 140/439). `TokenOkcSendFlowTest` adds 5 routing cases (list mode without/with blank `branch_id` blocked; instant mode without `terminal_id` blocked — each with zero rows and `Http::assertNothingSent()`; instant sends `terminal-id` and excludes `branch-id`; list sends `branch-id` and excludes `terminal-id`). `TokenOkcWebhookTest` adds 5 clientId cases (mismatch rejected, match accepted, required-but-unconfigured rejected, fallback to `client_id`, mismatch ignored when disabled). `php -l` and `git diff --check` are clean.
- No Token API call, basket submission, callback registration, or POS write occurred. Both gates are code-ready and tested; the clientId match stays OFF by default pending confirmation of the exact `clientId` Token echoes back.

Exit condition: PARTIAL. Atomic duplicate prevention, completed-resubmission blocking, state-guarded outbound updates, terminal-result validation, a callback-auth enforcement knob, a callback-clientId match knob, a pre-send routing-identity gate, explicit controller/route authorization (posmanager gate + company-admin payment check), a conservative open-basket recovery/reconciliation procedure with per-code provider guidance, and a mandatory-fresh-fiscal-snapshot send gate are implemented and tested. Remaining: enable/confirm terminal verification, callback auth, callback clientId match, and the fresh-fiscal-snapshot gate live, provider unlock/delete operations (no confirmed endpoint), and an authenticated public HTTPS callback with verified synthetic delivery.

### Step 6 — Run the separately approved physical payment test

- [ ] Pass the physical-test entry gate below and agree on the operator, low test amount, payment method, and expected receipt outcome. Start with one item and payment selection on the terminal; use test cash first if approved, then Token-approved card scenarios.
- [ ] Submit one instant basket to the confirmed terminal in mode `1`, with `terminal-id` and no `branch-id`. Preserve its UUID and do not submit another basket if the outcome is uncertain.
- [ ] Reconcile the API response, device amount/receipt, `BASKET_COMPLETED` webhook, and local basket state. API acceptance alone is not payment completion, and the shared POS sale is not automatically marked paid.
- [ ] After the first success, run abandonment, receipt-cancellation, duplicate-delivery, wrong-mode, and existing-open-basket scenarios; then test list mode separately.

Exit condition: Device, callback, and local application results agree for each approved scenario, with sanitized evidence recorded.

## Implementation baseline and remaining limitations

- `App\Models\Scopes\CurrentTeamScope` / `CurrentUserScope` class names now match their file names (PSR-4); `Integration` import corrected. Fixes the Linux case-mismatch autoload failure.
- New durable correlation record `App\Models\TokenOkcBasket` (+ migration `2026_09_22_000000_create_token_okc_baskets_table.php`) on the LOCAL connection — the shared `mysql-remote` POS DB is never written to.
- `TokenOkcService`: credential guard, expiry-aware token caching (`expiresIn` minus safety margin), bounded 401 refresh, terminal/fiscal API methods, local terminal selection guards, and persistence before submission exist. Submission is now atomic per sale (unique `active_sale_id` slot), blocks completed-payment resubmission, and applies outcomes through a state-guarded transition that cannot regress an early webhook. `assertTerminalUsable()` validates identity/branch/mode. A conservative recovery layer (`classifyProviderError()`, `getOpenBasketsForCompany()`, `reconcileBasket()`) reconciles non-final baskets against the confirmed open-basket query without ever marking paid or resubmitting. `assertFiscalSnapshotFresh()` gates submission on a fresh stored fiscal snapshot when `require_fresh_fiscal_snapshot` is enabled, and `assertRoutingIdentity()` rejects a blank routing identity for the terminal's mode before any I/O (instant mode needs a nonempty `terminal_id`, list mode a nonempty `branch_id`). `verifyCallbackClient()` compares the callback envelope `clientId` with the configured identity when `require_callback_client_match` is enabled. Callback authentication is still optional when its secret is empty unless `require_callback_auth` is enabled; the fresh-snapshot, routing-identity is always on, and the terminal-verification, callback-auth, and callback-client-match knobs default OFF and need live enablement, and provider unlock/delete operations remain incomplete.
- `TokenOkcBasketMapper`: explicit product mappings take precedence over category/POS tracking sections. Supplied fiscal snapshots are validated against terminal identity, section existence, and matching tax rates. Gross unit price uses stored `price + vat`; per-line totals, stored VAT, representable quantities, and sale/payment totals are checked. Unsupported discounts, service charges, and complimentary items are rejected rather than silently folded into prices. A null sale total still skips aggregate reconciliation. Per-send freshness enforcement now lives in the service-level gate (`TokenOkcService::assertFiscalSnapshotFresh()`), not the mapper; fiscal adjustment support remains open.
- `TokenOkcController`: exception→HTTP mapping (422/502/503/504), redacted webhook logging, new `terminal` and `client-settings` routes, explicit authorization (the authenticated route group requires the `check.user.access` posmanager gate; `sendSale()` requires company-admin `pos` access via `authorizeCompanyAdmin()`), and recovery actions (`recoveryOpenBaskets()` read-only, `reconcile()` company-admin) whose `sendSale()` 502/504 responses carry `classifyProviderError()` guidance.
- Webhook exempt from CSRF; browser routes keep CSRF protection.
- Exception types under `App\Services\TokenOkc\Exceptions`: `TokenOkcException`, `ConfigurationException`, `ValidationException`, `ApiException` (carries provider status), `TransportException`.
- Tests: `tests/Unit/Services/TokenOkc/TokenOkcBasketMapperTest.php`, `tests/Feature/TokenOkc/{TokenOkcServiceTest,TokenOkcWebhookTest,TokenOkcSendFlowTest,TokenOkcAuthorizationTest,TokenOkcRecoveryTest}.php`.

## 1. Runtime and credentials — blocking

- [x] Resolve the case mismatch between `CurrentTeamScope.php`, its `currentTeamScope` class declaration, and imports. Standardize the class and references to `CurrentTeamScope`; verify Composer autoloading on Linux in a fresh PHP process.
- [x] Verify Laravel boots and all `token-okc` routes resolve with their intended middleware.
- [x] Obtain test `client-id` and `client-secret` from Token. Receipt was confirmed on 2026-09-23; no credential values belong in this checklist.
- [x] Configure `TOKEN_OKC_CLIENT_ID` and `TOKEN_OKC_CLIENT_SECRET` in the ignored local `.env`; both are present in fresh CLI runtime configuration. Deployment-provided values were not checked.
- [x] Compare the configured default authentication URL, API base URL, and `/v1/basket` URL with Token's supplied test-environment information; they match. This is document/configuration verification, not a successful API test.
- [ ] Confirm all other endpoint paths under `config('token_okc.endpoints')` against the current provider contract and actual test responses.
- [x] Verify effective configuration in a fresh local CLI process without printing secrets: both credentials present, test URLs match, configuration cache disabled, credential guard passes. Separate web/deployment runtimes remain unverified.
- [x] Reject missing credentials with a clear configuration error before making an outbound request.
- [x] Respect the returned `expiresIn` when caching access tokens, with an expiry safety margin. Verify expired-token handling without unbounded retries.
- [x] Verify connectivity and required schema for both connections. Step 4 verified local `mysql` storage and applied only its basket migration; after the user's configuration fix, `mysql-remote` company/category/product/order/sale data are readable. No migration or destructive test may target the shared POS database.

Relevant files: `config/token_okc.php`, `app/Models/Scopes/CurrentTeamScope.php`, `app/Models/Integration.php`, `app/Models/UserAccess.php`, `app/Models/PosSale.php`, `app/Models/TokenOkcBasket.php`.

Acceptance: The application boots, the integration model autoloads, required configuration is available, and authorized test data can be read without exposing credentials.

## 2. Terminal configuration and fiscal matching — blocking

- [x] Prepare the confirmed test company, active integration, and terminal configuration. Local integration `1` is assigned to company/team `1`.
- [x] Persist the QR-derived identifiers in the authorized local integration. Terminal/branch/mode were rechecked during setup; the merchant identifier is user-supplied and its provider association remains unverified.
- [x] Require an active, explicitly valid terminal. Reject an unknown requested terminal rather than silently selecting another device; reject configurations with no active terminal.
- [x] Implement the outbound Token **Get Terminal** API method. The separate `getTerminals()` controller method returns only locally stored integration configuration.
- [x] Validate that the Get Terminal response contains the requested terminal, branch, and mode; reject empty or mismatched results. `assertTerminalUsable()` now requires a matching identity entry and rejects empty results and branch/mode mismatches.
- [ ] Enable `TOKEN_OKC_VERIFY_TERMINAL` in the live runtime now that response validation is strengthened. Actual API discovery confirmed mode `1` and the terminal endpoint in Step 3, but pre-send verification remains disabled by default.
- [x] Route instant baskets with `terminal-id` and list-mode baskets with `branch-id`.
- [x] Reject missing/invalid branch IDs for list-mode submission and verify routing-header exclusions in tests. `TokenOkcService::assertRoutingIdentity()` runs before any I/O or persistence: instant mode (1) requires a nonempty `terminal_id`, list mode (0) requires a nonempty `branch_id` (blank/whitespace rejected). Isolated tests confirm both rejections leave zero basket rows with `Http::assertNothingSent()`, that instant mode sends `terminal-id` and excludes `branch-id`, and that list mode sends `branch-id` and excludes `terminal-id`.
- [x] Persist refreshed **Get Fiscal Parameters** results and fetch time for the selected terminal in the authorized integration. Automatic refresh/freshness enforcement is not yet built.
- [x] Establish and validate six category and ten product mappings with matching tax rates. Explicit product mappings support the actual POS pivot data without relying on null category columns.
- [x] Reject unresolved section/tax values instead of silently defaulting to section `1` and 20% VAT.
- [x] Reject incompatible section/tax combinations when a stored fiscal snapshot is supplied, even when both values are present.
- [~] Require a valid, fresh fiscal snapshot for every submission. A send-flow gate `TokenOkcService::assertFiscalSnapshotFresh()` now runs before any I/O when `token_okc.require_fresh_fiscal_snapshot` is enabled: it rejects a missing snapshot, a missing/invalid `fiscal_parameters_fetched_at`, or a snapshot older than `fiscal_snapshot_max_age` (default 24h). The knob defaults to false to preserve existing behavior, so legacy configurations without a snapshot still submit; enabling enforcement in the live runtime (and confirming the stored timestamp format) remains open. The mapper's structural snapshot validation (terminal match, sections, tax compatibility) is unchanged.
- [ ] Provide a reusable setup procedure or interface. This company's one-off setup was validated and applied; its temporary ignored runner was removed.

Relevant files: `app/Services/TokenOkc/TokenOkcService.php`, `app/Services/TokenOkc/TokenOkcBasketMapper.php`, `app/Http/Controllers/TokenOkcController.php`.

Acceptance: The intended test terminal, company, mode, and fiscal mappings are unambiguous and validated before submission.

## 3. Basket construction and durable correlation — blocking

- [x] Persist the outgoing `basketID`, sale reference, company/team, target terminal or branch, and submission state before the outbound request can produce a callback.
- [x] Use the dedicated `TokenOkcBasket` record for durable UUID correlation; the webhook no longer looks up `channel_id`, and the shared POS sale is not modified.
- [x] Reuse a prior failed/uncertain basket UUID in the retry path. Provider reconciliation before retry remains a separate open requirement in section 5.
- [x] Distinguish pending submission, accepted basket, completed payment, cancellation, and uncertain outcome. API acceptance does not mark a sale paid.
- [x] Prevent concurrent or repeated submissions atomically. A nullable UNIQUE `active_sale_id` slot makes the per-sale live attempt unique at the database level; a racing second insert is caught as a unique violation and reported as a duplicate rather than creating a second UUID.
- [x] Block accidental resubmission of completed payments; a completed basket for the sale is rejected before any new submission.
- [x] Prevent a late outbound API response from overwriting a state already advanced by a webhook; outcomes apply only while the row is still `submitted`, under a row lock.
- [x] Replace hardcoded `paymentItems.type = 1` with the intended payment behavior. Token defines `1` as cash and `3` as credit card; application payment codes are mapped explicitly.
- [x] Omit `paymentItems` when no payment plan is supplied, allowing payment selection on the terminal rather than declaring the total as cash.
- [x] Filter canceled/deleted/nonpositive-quantity lines and reject empty baskets or negative prices.
- [x] Build gross unit prices from stored per-unit `price + vat`; check fiscal VAT consistency and each stored `total_price` independently, without using current catalog prices.
- [x] Reconcile item totals with the sale net total when present and any explicit payment plan; reject mismatches. Offline sales `109` and `114` match exactly; missing sale totals remain a validation limitation.
- [ ] Verify representative complimentary-item, discount, and service-charge scenarios against POS business rules and implement their fiscal representation. Known adjustment fields/statuses are now explicitly rejected; rejection is not complete support.
- [x] Encode prices/payment amounts as kuruş (`TRY × 100`), quantity as `× 1000`, and tax percentages as `× 100` (10% → `1000`).
- [ ] Complete amount/quantity and device-backed fiscal-combination validation before submission, including edge cases not covered by the current guards.

Relevant files: `app/Services/TokenOkc/TokenOkcBasketMapper.php`, `app/Services/TokenOkc/TokenOkcService.php`, `app/Models/PosSale.php`, `app/Models/PosOrder.php`, `app/Models/TokenOkcBasket.php`.

Acceptance: A representative sale produces a correct basket, has durable callback correlation, and cannot accidentally create duplicate or misclassified payments.

## 4. Webhook delivery, authentication, and processing — blocking

- [x] Exempt only `token-okc/webhook` from CSRF verification; browser-triggered operations retain CSRF protection.
- [~] Require callback authentication. A `require_callback_auth` knob now rejects callbacks with 401 when no secret is configured (instead of silently accepting), and matching still uses `hash_equals`. Confirming compatibility with Token's actual `callbackAuth` delivery format and enabling enforcement in the live runtime remain open.
- [x] Validate the documented envelope: `terminalId`, `clientId`, `operation`, `operationDate`, and `data`.
- [x] Read the basket identifier from `data.basketID`, payment status from `data.status`, and payment details from `data.paymentItems`.
- [x] Process `BASKET_COMPLETED` explicitly: status `0` means success, `-1` means payment abandoned, and `99` means receipt cancellation. Translate these into valid application states, not raw numeric sale statuses.
- [x] Handle `BASKET_LOCKED` and `BASKET_UNLOCKED` without marking the sale paid or setting a payment timestamp.
- [~] Complete callback identity and terminal/company validation. Basket and terminal checks exist. A `verifyCallbackClient()` gate now compares the envelope `clientId` against the expected identity (`callback_client_id`, falling back to `client_id`) using `hash_equals`, behind the default-off `require_callback_client_match` knob; when enforcement is on but no identity is configured, callbacks are rejected (401) rather than silently accepted, mirroring `require_callback_auth`. Enabling it live still requires confirming the exact `clientId` value Token echoes back. Terminal/company cross-checks against the persisted basket remain partially open.
- [x] Persist payment details and the redacted callback payload; set `paid_at` only on a successful completion event.
- [ ] Verify extraction of receipt/invoice metadata from actual provider responses; the dedicated `receipt_data` field currently expects a nested `data.receipt` object.
- [x] Handle duplicate notifications and use a transaction with a row lock for webhook updates.
- [ ] Complete delayed/out-of-order and conflicting-outcome handling across both webhook and outbound response updates; webhook row locking alone does not protect the submission response path.
- [x] Return explicit responses for unknown baskets, missing envelope fields, unsupported operations, and stale/duplicate events.
- [ ] Verify complete payload validation and conflicting-outcome reconciliation, including provider behavior after abandonment or receipt cancellation.
- [x] Map recognized authentication, payload, and processing failures to non-2xx responses in the handler.
- [ ] Confirm Token's delivery/retry policy and verify failure responses through the real HTTP route rather than assuming retries are guaranteed.
- [ ] Use a publicly reachable HTTPS callback URL with a valid certificate; localhost is not reachable from Token Cloud.
- [ ] Inspect the existing client callback setting before registration. A client has one shared callback URL; changing it may affect other integrations. Register or replace it only with approval. A read-only `client-settings` route now exists to inspect before changing.
- [x] Enforce explicit authorization for callback-setting and payment-submission operations and verify company/team boundaries. The authenticated route group now requires the `check.user.access` posmanager gate (so `setWebhook()` and the read endpoints are no longer open to any logged-in session), and `sendSale()` additionally requires an admin-level `pos` access for the sale's own company/team via `authorizeCompanyAdmin()`. Boundary denials (other company, other team, non-admin level, unauthenticated) are covered by isolated tests. The client-level callback URL remains shared; registering/replacing it still requires explicit approval.
- [x] Redact sensitive payload fields and secrets from logs while preserving basket IDs and diagnostic status information.

Relevant files: `routes/web.php`, `app/Http/Middleware/VerifyCsrfToken.php`, `app/Http/Controllers/TokenOkcController.php`, `app/Services/TokenOkc/TokenOkcService.php`.

Acceptance: A documented, authenticated synthetic notification reaches the handler and updates exactly the intended attempt once; invalid or unrelated notifications cannot finalize a sale.

## 5. Recovery and isolated automated tests — blocking

- [x] Prepare a recovery procedure for timeouts and uncertain submissions using open-basket queries before attempting another payment. `TokenOkcService::reconcileBasket()` reconciles a non-final basket against the confirmed `GET /v1/baskets` open-basket query (no unconfirmed details endpoint is invented): a final basket short-circuits with no HTTP; positive evidence the basket is still open upgrades an `uncertain`/`submitted` attempt to `accepted` (never `completed`/paid) under a row lock with a finality re-check so a concurrent webhook always wins; absence from the open list records evidence but leaves the state unchanged pending verification. The provider call runs OUTSIDE the row lock. Durable evidence is stored via `reconciled_at` + an append-only `recovery_log` (migration `2026_09_23_000001_...`, applied to the local `apper` DB only). Exposed as `POST /token-okc/recovery/reconcile` (company-admin gated) and a read-only `GET /token-okc/recovery/open-baskets`.
- [~] Provide a controlled way to handle an open, locked, or abandoned basket through documented status/unlock/update/delete operations as appropriate. Do not blindly retry submission with a fresh UUID. The reconcile workflow never resubmits and returns an explicit recommended action (await `BASKET_COMPLETED`, or close/unlock on the device; verify before any action when not open). Direct provider unlock/delete operations are intentionally NOT implemented because no such endpoint is confirmed in the current contract; they remain open pending provider documentation.
- [x] Cover provider errors including `1007` (duplicate basket), `1018` (locked basket), `1100` (existing open basket), `1103` (total mismatch), `1104` (wrong mode), and `1106` (unknown terminal). `TokenOkcService::classifyProviderError()` maps each documented code to meaning + non-retryable guidance (`retryable=false`, `resubmit_with_new_uuid=false`), and unknown/absent codes fall back to conservative manual review. `sendSale()` embeds this guidance in its 502 (provider) and 504 (transport/uncertain) JSON responses so an operator is told to reconcile rather than resend.
- [x] Add mapper tests for price/quantity/tax scaling, fractional quantities, rounding, discounts, canceled lines, payment types, and total consistency.
- [x] Add mocked HTTP tests for authentication/caching, routing, missing fiscal mapping, API rejection, and transport-failure classification.
- [~] Extend tests for actual token expiry and bounded 401 refresh, device-backed fiscal validation, and reconciliation after uncertain submission. Reconciliation is now covered by `TokenOkcRecoveryTest` (final short-circuit with no HTTP, uncertain/submitted still-open → accepted, not-open leaves state unchanged, accepted not regressed, unknown basket throws, open-basket id normalization/dedup, missing integration). Token expiry/bounded 401 refresh and device-backed fiscal validation remain open.
- [x] Add service-level webhook tests for success, abandonment, receipt cancellation, locked/unlocked events, invalid authentication, unknown baskets, and duplicate delivery.
- [~] Test early webhooks versus a late outbound response, completed-sale resubmission, and database-level duplicate prevention — all now covered. Simultaneous in-process submissions, delayed/out-of-order events, and concurrent finalization still need coverage; the existing sequential service tests do not establish full concurrency safety.
- [ ] Test HTTP routes with the relevant middleware enabled, including external webhook CSRF exemption and retained browser CSRF protection. Current CSRF tests inspect the exception list through reflection only.
- [ ] Verify that send-sale and set-webhook permissions cannot be bypassed across companies or teams.
- [x] Use isolated SQLite databases for the local and `mysql-remote` connections in send-flow tests, with mocked provider responses.
- [ ] Explicitly prevent stray HTTP requests for the entire focused suite and verify all test paths remain isolated from real services/databases.
- [x] Record the historical focused result: `php artisan test --filter=TokenOkc` → 55 passed (106 assertions), from the earlier implementation pass.
- [x] Rerun focused tests after Step 4 fiscal and gross-pricing changes: 107 tests passed, 301 assertions; isolated connection overrides, offline real-sale results, and remaining blockers are recorded above.
- [x] Rerun focused tests and relevant regressions again after the remaining Step 5 safety fixes. The recovery procedure, mandatory-fresh-fiscal-snapshot gate, pre-send routing-identity gate, and callback-clientId match gate are now implemented and tested: `DB_CONNECTION=sqlite DB_DATABASE=:memory: DB_HOST_REMOTE=127.0.0.1 DB_PORT_REMOTE=1 php artisan test --filter=TokenOkc` → **150 passed, 461 assertions** (124/345 before recovery, 134/424 after recovery, 140/439 after the snapshot gate, +5 routing and +5 clientId cases). `php -l` and `git diff --check` are clean. The remaining Step 5 gaps are live-runtime enablement/confirmation items and concurrency/real-HTTP coverage, not unimplemented safety logic.

Acceptance: All critical scenarios pass without contacting a physical terminal or modifying shared production data.

## 6. Physical-test entry gate

Proceed to a separately approved physical test only after the blocking sections above are complete.

- [ ] Token test credentials, environment URLs, terminal registration, and any network restrictions are confirmed.
- [ ] The device has internet access and TokenX Connect is open in the intended mode. Cloud integration does not require direct access to the device's LAN IP.
- [x] Read-only terminal and fiscal checks pass for the intended device (2026-09-23, Step 3). Recheck identity/mode and refresh fiscal data before a payment test.
- [ ] The authorized HTTPS callback is registered and authenticated synthetic delivery has been verified.
- [ ] A low-value test sale, intended payment method, expected receipt outcome, and operator are agreed upon.
- [~] A recovery procedure and correlated logs are available before the first basket is sent. Code-complete and isolated-tested (`reconcileBasket()` + `recovery_log`/`reconciled_at`, admin-gated recovery routes); still unexercised against the live provider, so confirm it against a real open-basket query during the supervised test.
- [ ] The physical test matrix includes successful payment, abandoned payment, receipt cancellation, repeated delivery, wrong mode, and an existing open basket.
- [ ] Actual device payment/receipt results will be reconciled with application state; basket acceptance alone will not count as a successful payment test.

## Official references

- [Token X Connect Cloud — General Introduction](https://developer.tokeninc.com/token-developer-portal-1/x-platform/token-x-connect-cloud/genel-tanitim-tr)
- [Token X Connect Cloud — Developer Documentation](https://developer.tokeninc.com/token-developer-portal-1/x-platform/token-x-connect-cloud/gelistirici-dokumani-tr)
- [Token Postman Collection](https://documenter.getpostman.com/view/29891759/2sB34hEzUj)
- [Basket JSON and payment types, linked by the Cloud documentation](https://developer.tokeninc.com/token-developer-portal-1/x-platform/token-x-connect-wire/gelistirici-dokumani)
