Skip to content

fix: route AML check for suspicious users to GSheet review#3638

Open
TaprootFreak wants to merge 1 commit intodevelopfrom
fix/aml-suspicious-user-handling
Open

fix: route AML check for suspicious users to GSheet review#3638
TaprootFreak wants to merge 1 commit intodevelopfrom
fix/aml-suspicious-user-handling

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

Problem

Buy/Sell-Transactions of users with riskStatus = SUSPICIOUS are silently dropped by the AML pipeline. The transaction is created with amlCheck = null, status = Created, and never advances. There is no visibility, no audit trail, and no chargeback path — the funds sit in limbo until manual SQL intervention.

Concrete incident: buy_crypto 120973 (35'000 CHF, user 383865, transaction 312001, bank_tx 197696) has been stuck since 2026-04-28 07:29 UTC. It is the only entry in the entire database with amlCheck = null AND status = Created. The user landed on the AML list (amlListAddedDate = 2026-04-27 08:49) and was flagged Suspicious by Compliance shortly after a successful preceding buy (120849, 50k CHF). The next buy gets caught in this gap. This needs to clear urgently — the customer is waiting on a 35k CHF deposit.

Root cause

buy-crypto-preparation.service.ts:67 and buy-fiat-preparation.service.ts:60 filter out SUSPICIOUS users from the AML query:

transaction: { userData: { riskStatus: Not(RiskStatus.SUSPICIOUS) } },

But unlike BLOCKED (which has a corresponding rule in AmlHelperService.getAmlErrors mapping to USER_DATA_BLOCKED → GSHEET), SUSPICIOUS has no rule anywhere. The 14-day expired-pending reaper (aml-helper.service.ts:642) doesn't apply either — it only operates on entries that are already PENDING, not null. Result: the transaction is invisible to every job and dashboard.

Fix

Treat SUSPICIOUS exactly like BLOCKED: surface it through the existing AML rule pipeline so it lands in the GSheet review queue with a clear reason. Compliance gets the same workflow they already use for BLOCKED users — review, then either set riskStatus = RELEASED (AML reruns and processes normally) or trigger a chargeback manually.

Changes:

  • aml-error.enum.ts: add USER_DATA_SUSPICIOUS error mapped to CRUCIAL / GSHEET / USER_DATA_SUSPICIOUS (parallel to USER_DATA_BLOCKED)
  • aml-reason.enum.ts: add USER_DATA_SUSPICIOUS reason and include it in AmlReasonWithoutReason
  • aml-helper.service.ts: add if (entity.userData.isSuspicious) errors.push(AmlError.USER_DATA_SUSPICIOUS); next to the existing BLOCKED check
  • buy-crypto-preparation.service.ts / buy-fiat-preparation.service.ts: remove the riskStatus: Not(SUSPICIOUS) filter — the rule above now handles it
  • sift.dto.ts: map to DeclineCategory.RISKY (Sift signal that the user was flagged for risk review)
  • transaction.dto.ts: map to TransactionReason.UNKNOWN (parallel to BLOCKED)

The downstream isSuspicious filters in buy-crypto-out.service.ts and buy-crypto-batch.service.ts are intentionally left in place — they only act on amlCheck = PASS entries, so they remain a defensive guard against accidental auto-payouts to suspicious users.

Test plan

  • Once merged and deployed, verify buy_crypto 120973 picks up amlCheck = GSHEET with amlReason = UserDataSuspicious on the next AML job tick
  • Confirm it appears in the Compliance GSheet review queue alongside USER_DATA_BLOCKED entries
  • Verify no regression for riskStatus = NA / RELEASED users (default path unchanged)
  • Verify BLOCKED users still route to USER_DATA_BLOCKED GSheet (rule order preserved)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant