Summary
Removes the Reap card rail from the money paths and moves card funding fully onto KeySecure (credit mode, account.transfer per cardholder).
-
Card ledger: new
card_accounts/card_account_entries(one USD balance per user, migration1790000000000), outside thereapmodule. -
Transfers outbox:
card_transferswith conditional PENDING → APPLIED/FAILED settles, idempotent source hooks retried untilsource_settled_atis set, and a sweeper. -
Issuance: virtual card apply with fee hold, refund on explicit refusals only, apply sweeper,
Card_updatehandling. -
Money paths:
POST /card-account/deposit(fiat wallet → card) and Convert both ways now pushaccount.transferIn/Out; card → USDC is provider-first. -
Consumption webhooks: posted on
Successonly; replay scheduler; staleness guard for reversals/refunds. -
Rail / reconciliation: rail
KEYSECUREreplacesREAP;CARD,CARD_FLOAT,CARD_ACCOUNTSinvariants read from one snapshot. -
Consumers: balance and chats repointed; chat card-to-card transfer now returns 422
CARD_TRANSFER_UNAVAILABLE; legacyPOST /cards/:id/depositreturns 400; Reap top-up sweeper unregistered.
API changes for the app (SP4b)
- Convert quote no longer takes
cardId. - New endpoints under
/card-accountand/card-account/cards/*.
Deploy / cutover
- New env:
KEYSECURE_CARD_PACKAGE_NO,RAIL_FLOOR_KEYSECURE_USD. Removed:KEYSECURE_RESERVE_LOW_USD. - Run migration
1790000000000-create-card-account-tables. - One-off data step on staging/prod: reset
normal.CARD_USD(still holds the old Reap card float) and fundreserve.CARD_USDto mirror the KeySecure reserve fund, otherwise deposits fail. - Set
provide_card_fee_virtual = 0.
Testing
- Jest (card-account, transactions, conversion, treasury, cards, keysecure): 1079 tests pass.
ramp-limits.velocityfails on date math and was already failing on develop. - Postgres specs (
RUN_DB_TESTS=1,-i) pass, including the newreconciliation.card-flow.postgres.spec.tsthat drives deposit, Convert both ways, refund and consumption postings.ramp-netting.postgresrefuses a non-localhost DB host (pre-existing). - eslint on changed files clean;
yarn buildpasses. - Sandbox smoke not run yet: card issuance is blocked until the sandbox reserve fund is topped up (
/card/applyreturns 3008).
Known follow-ups
- A short
reserve.CARD_USDon deposit surfaces as a 400 with an internal account id; should map to 502/503 + ALERT. - Code 3008 (insufficient balance) is not yet classified as a refusal on card apply.
- Freeze / client-token transport errors still return 500.