fix(api): let a rejected Avenia KYC account become approved on a successful retry#1273
Conversation
…essful retry updateAveniaKycOutcome only flipped accounts that were in_review (mirroring the legacy WHERE internal_status = 'Requested' guard), and the in-flight poll branch excluded rejected accounts. A user whose first KYC attempt was rejected and whose retry succeeded therefore stayed 'rejected' forever, and ramp registration failed with 'No completed Avenia profile found for this API key user' despite a successful KYC. Approval is now the only terminal state: a stale attempt read never downgrades an approved account, but rejected/pending accounts follow the latest provider attempt, and a processing retry moves a rejected account back to in_review. Repeated polls of an unchanged outcome no-op.
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vrtx-dashboard ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Fixes a BRLA/Avenia KYC state-machine edge case in the API where a subaccount that was previously rejected could not transition to approved after a successful retry, causing downstream ramp registration to fail despite the provider showing an approved outcome.
Changes:
- Adjusts
updateAveniaKycOutcomeso approved is the only terminal state, allowingrejected→approvedon successful retried attempts while preventing downgrades from approved. - Updates
fetchSubaccountKycStatusto allow arejectedaccount to move back toin_reviewwhile a new attempt is processing. - Adds regression tests covering rejected→approved retry, rejected→in_review while processing, and “approved never downgraded” behavior; updates the BRLA security spec to match.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| docs/security-spec/05-integrations/brla.md | Updates integration/security documentation to reflect the new “approved is terminal” transition rules. |
| apps/api/src/api/services/avenia/avenia-customer.service.ts | Updates the poll-driven outcome transition logic to allow rejected→approved retries and preserve idempotency/terminal semantics. |
| apps/api/src/api/controllers/brla.controller.ts | Expands in-flight polling logic to include retried attempts for previously rejected accounts. |
| apps/api/src/api/controllers/brla.controller.test.ts | Adds regression tests that reproduce and prevent the stuck-rejected scenario and confirm approved is never downgraded. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Pre-existing useSortedClasses violation on staging that fails 'bun verify' in CI.
Problem
Seen in Sentry: a new user completed KYC successfully, but ramp registration failed with "No completed Avenia profile found for this API key user."
Root cause:
updateAveniaKycOutcomeonly flipped accounts whose status wasin_review(deliberately mirroring the legacyUPDATE ... WHERE internal_status = 'Requested'guard), and the in-flight poll branch infetchSubaccountKycStatusexplicitly excludedrejectedaccounts. So for a user whose first KYC attempt was rejected and whose retry succeeded:in_review→rejected.createSubaccountcall), so nothing resets the status.updateAveniaKycOutcome, which silently no-ops (rejected ≠ in_review). The user still sees "KYC approved" because the poll response is built from Avenia's live attempt status.resolveAveniaAccountForUserfinds noapprovedaccount → registration fails.Fix
updateAveniaKycOutcome: approved is now the only terminal state. A stale attempt read never downgrades an approved account, butrejected/pendingaccounts follow the latest provider attempt result (Avenia is the source of truth). Repeated polls of an unchanged outcome no-op, preserving the write-once behavior.fetchSubaccountKycStatus: arejectedaccount with a fresh processing attempt moves back toin_review(previously excluded).docs/security-spec/05-integrations/brla.md) updated — it documented the old guard.Tests
Three regression tests in
brla.controller.test.ts:bun test(25/25 in file),bun lint,bun typecheckall pass.Note for production
Production (pre-cutover
tax_idsmodel) has the same flaw until this ships via staging → main. The affected user's row is presumably stuck atinternal_status = 'Rejected'and needs a one-off flip toAcceptedafter confirming their identity is CONFIRMED on Avenia's side.