Skip to content

I-033 — Airwallex merchant enrichment silently degraded

Symptom

Card purchases on the Airwallex transaction list show the raw fallback label "ISSUING_CAPTURE (CARD_PURCHASE)" instead of the enriched description ("Purchase at Google Cloud, SGP, (Company Card, ****4687)") they used to show.

Root cause (proven from production runtime logs, 2026-07-15 traffic)

Airwallex's wallet feed (financial_transactions) carries no merchant info (description: null on card rows). Enrichment comes from a second call to the Issuing API (/api/v1/issuing/transactions), matched by date+amount+currency — both in the Bank page (/api/airwallex/card-transactionsBankDashboard) and at accounting-import time (/api/airwallex/sync). An unmatched card row silently falls back to the raw type labels.

Three facts combined into a time bomb: 1. /api/airwallex/card-transactions fetched only page 0 (100 rows) of the 2-year window — prod logs show count: 100, hasMore: true. 2. The account now has 130 card transactions (the sync's single 200-row page still got all of them — itemCount: 130, hasMore: false). 3. Airwallex returns issuing transactions OLDEST-first — provable from the first row's card_transaction_lifecycle_id, a UUIDv7 whose embedded timestamp decodes to 2025-01-24 (the account's earliest card era).

So page 0 = the oldest 100; the newest ~30 card transactions were invisible to the matcher, and every recent purchase lost its merchant name. It "used to work" because it armed itself the day the card-tx count crossed 100. The credentials were fine and the code unchanged since it worked (verified). The accounting-sync path still enriched correctly (130 < its 200 page size) but had the same class of bug one threshold later.

Fix

Paginate the full window everywhere (same class of fix as T-149 for OCBC): - lib/airwallex/client.ts: new getAllFinancialTransactions() (raw rows, all pages); getAllCardTransactions() already existed but was unused by these paths. - pages/api/airwallex/card-transactions.ts: fetches ALL pages via getAllCardTransactions (was single page-0 call); hasMore now always false. - pages/api/airwallex/sync.ts: both fetches now use the paginating helpers (was single 200-row pages — would truncate at >200 rows).

Display-only residual (not fixed here): the Bank page transaction list itself (/api/airwallex/transactions?pageSize=100) still shows only the newest 100 financial rows — cosmetic pagination, separate from enrichment.

Verification

Heavy tsc --noEmit clean. Live check is owner-gated (needs prod creds + deploy): after deploying, recent card purchases on Bank Access > Airwallex should regain merchant names. NOTE: merged ≠ deployed — auto-deploy is off.

Decision log

2026-07-16 — ✅ Read AGENTS.md (incl. the 2026-07-05 deployment guardrail). Diagnosed from production runtime logs via the Vercel API (no code had changed; proved the truncation + oldest-first ordering). Ticket-only fix on the Airwallex fetch layer; owner approved shipping ("Yes" to the pagination fix, 2026-07-16). Source: Bank Access (Diagnosticcs) · https://claude.ai/code/session_01S4ztAsDZfR97qXjhPdwLn4