Problem
A card created via Card → Issue stays in Processing (APPLYING) for days instead of resolving.
Task: https://hola-tech.sg.larksuite.com/record/GAb2rB3zxeRrzxcffFMlh6stg8u
Root cause
Two branches in CardIssuanceService can leave an APPLYING row forever, bypassing the 30-minute give-up:
-
Unsubmitted card (
submittedAt = null, open-fee top-up path): while the funding transfer isPENDING,advanceUnsubmittedreturned early on every sweep — "nothing to give up on until it settles". A top-up the provider can never settle (e.g. a permanent refusal on anIntransfer, which staysPENDINGby design) kept the card Processing indefinitely. -
Submitted card listed with an unmappable provider status (e.g.
Pending, the status seen in stagingCard_updatepayloads):reconcileApplyingcalledfilland returned before the give-up check, so a card the provider never activates never timed out.
Fix
Both branches now reach the give-up deadline:
- Unsubmitted + funding
PENDINGpastCARD_APPLY_GIVE_UP_MS→ cardFAILEDwithFUNDING_TIMEOUT, issue fee refunded (existing conditionalfailApplying, refund only on the winning update). The transfer itself stays with its own sweeper; a late settlement credits/reverses through the normal deposit hooks, andclaimSubmitonly fires on anAPPLYINGrow, so a lateAPPLIEDnever submits aFAILEDcard. - Submitted + provider summary with unmappable status →
fillstill recordsproviderStatus, then falls through to the give-up: young cards keep waiting; cards past the window fail withAPPLY_TIMEOUTand refund. A summary with a mappable status (e.g.Activated) still resolves immediately, even when old.
No schema changes. Frontend needs no change: the list/detail refetch on revisit shows the resolved status, and FAILED cards already render the refund hint.
Tests
- New regressions in
card-issuance.service.spec.ts:- funding
PENDINGwithin the window waits; past the window →FUNDING_TIMEOUT+ refund, no apply; - provider
Pendingyoung → staysAPPLYINGwithproviderStatusstored, no refund; past the window →APPLY_TIMEOUT+ refund; - provider
Activatedpast the window → fillsACTIVE, never gives up.
- funding
- Red/green proof: with the service change reverted, the two new timeout tests fail; with it applied, all pass.
- Full
src/modules/card-accountsuite: 431 passed (19 Postgres-gated skipped), including sweeper and transfer suites.