Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Register
  • Sign in
  • A Ahad Mobile
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • holatech
  • Ahad Mobile
  • Merge requests
  • !82

feat(keysecure): cardholder onboarding (SP3)

  • Review changes

  • Download
  • Patches
  • Plain diff
Merged Khanh Pham requested to merge feat/keysecure-sp3-cardholder into develop Sep 30, 2026
  • Overview 0
  • Commits 16
  • Pipelines 1
  • Changes 32

KeySecure SP3 — cardholder onboarding

Lets a user whose Sumsub KYC is completed become a KeySecure cardholder (one per user), fill missing KYC fields in-app, track the provider review, resubmit after a rejection, and watches the merchant reserve fund. No card issuing yet.

What's in it

  • GET /keysecure/cardholder — status (NOT_STARTED, SUBMITTING, SUBMIT_FAILED, IN_REVIEW, APPROVED, REJECTED), plus the fields still missing and any blocking reason.
  • POST /keysecure/cardholder — optional body { gender, address{street, subStreet?, city, state, postCode?, country}, phone }. Missing fields are patched into the Sumsub applicant, then a Sumsub share token is created and sent to cardholder/apply.
    • 200 new status · 409 already in progress/approved · 422 missing/blocked (listed in the body) · 403 KYC not completed · 502 transient provider error · 503 not configured.
  • New table keysecure_cardholders (migration 1789900000000), unique per user; fixed cardholder_unique_no = ahad_<userId> so the provider can never hold two cardholders for one user.
  • Pre-checks before apply: Latin name, ID card/passport, E.164 phone, email matching the account, English address text, Channel-01 restricted countries (13).
  • Concurrency: per-user claim (INSERT … ON CONFLICT DO NOTHING + SELECT … FOR UPDATE); verified on real Postgres (5 concurrent submits → exactly 1 apply).
  • Error mapping: 2001/2003/2004/outcome-unknown → reconcile via cardholder/list; 2005–2012 → SUBMIT_FAILED with a fixed reason code (never the provider message); provably-not-sent failures release the slot so the user can retry at once.
  • Cron KeySecureCardholderSyncScheduler every 5 min (Redis lock, 100 rows, compare-and-set writes, stale SUBMITTING > 30 min released).
  • KeySecureReserveService + 30-min monitor: warns when the reserve drops below KEYSECURE_RESERVE_LOW_USD (bignumber; incomplete/unreadable data logs "balance unknown", never ok).
  • SumsubShareService (separate share app token); its Sumsub error logs carry only operation + HTTP status (no PII).

Config (new env vars)

SUMSUB_SHARE_APP_TOKEN, SUMSUB_SHARE_SECRET_KEY, KEYSECURE_SUMSUB_RECIPIENT, KEYSECURE_RESERVE_LOW_USD (empty = monitor off). Without the Sumsub share vars the endpoints answer 503. Staging secrets not added yet.

Verification

  • yarn jest src/modules/keysecure src/modules/keysecure-cardholder src/modules/sumsub: 405 passed, 2 skipped (Postgres race spec, gated by RUN_DB_TESTS=1, run manually: 2/2).
  • yarn build OK; eslint clean on all touched files.
  • Migration up/down checked on a throwaway DB; built app booted on a DB copy (migration applied, DI ok, unauthenticated GET/POST → 401).
  • Reviews: per-task Sonnet reviews, final whole-branch Opus review approved after 2 rounds.

Not in this MR / follow-ups

  • Real sandbox check that KeySecure imports the fixedInfo gender/address and top-level phone/email we patch.
  • Card issuing, Card_update handler, webhook replay cron.

🤖 Generated with Claude Code

Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: feat/keysecure-sp3-cardholder