Summary
Adds be/src/modules/keysecure/, a self-contained client for the KeySecure (ChainUp) card-issuing merchant API. This is SP1 of the card-issuing migration: the provider client only. No business flow, DB table, controller or FE change yet — nothing consumes the module so far.
-
Config —
KEYSECURE_*env block. The module boots without credentials and throwsKeySecureNotConfiguredErroron first use, so environments without KeySecure are unaffected. Production requires an explicitKEYSECURE_BASE_URL. - Access token — shared through Redis (namespaced by base URL + Api-Key, cluster hash tag) with fenced single-flight refresh, so several API instances never revoke each other's token.
-
HTTP client — five request kinds with their own retry rules. Writes without an idempotency key (activate, status, PIN) and one-time client tokens are never replayed. A timeout, a 5xx, or a malformed success response on a write surfaces as
outcomeUnknown, so callers reconcile by unique number instead of retrying blindly. Logging is whitelist-only: path templates with masked identifiers, no bodies, headers or tokens. - Crypto — AES-256-CBC with an IV prefix for PIN/CVV/private card data (key = SHA-256(apiSecret)), strict PIN rule (6 digits, no 3-digit identical/ascending/descending run), and callback HMAC-SHA256 verification. The canonical form is inferred; enforcement waits for a real sandbox callback (SP2).
-
Endpoints — 20 merchant endpoints (cardholder, card, account/reserve fund, consumption) with camelCase DTOs. Money stays a decimal string end to end (
lossless-json). -
Sandbox — 6 simulators (sandbox-only), plus
yarn keysecure:smoke <token|packages|lists|reserve|crypto|bins|all|simulate>.
New dependency: lossless-json.
Test plan
-
yarn jest src/modules/keysecure— 171/171 pass -
Full yarn test— onlytreasury.config.specandadmin-lending.risk.specfail, identically at base271a91f7(pre-existing) -
yarn buildclean; eslint 0 errors on all changed files -
yarn keysecure:smoke allagainst the real sandbox — token, packages, lists, reserve, crypto, bins (1012 = credit mode) all PASS -
Full app boot ( node dist/main) —KeySecureModuleinitialises, app starts -
Per-task spec + quality review, whole-branch review, Codex review gate (approved in round 2)
Deploy note: with no KEYSECURE_* env set on staging the module stays dormant.