diff --git a/src/subdomains/core/aml/enums/aml-reason.enum.ts b/src/subdomains/core/aml/enums/aml-reason.enum.ts index 21d376269b..a38d10c1dc 100644 --- a/src/subdomains/core/aml/enums/aml-reason.enum.ts +++ b/src/subdomains/core/aml/enums/aml-reason.enum.ts @@ -57,6 +57,14 @@ export const KycAmlReasons = [ AmlReason.KYC_DATA_NEEDED, ]; +export const PhoneAmlReasons = [ + AmlReason.MANUAL_CHECK_PHONE, + AmlReason.MANUAL_CHECK_PHONE_FAILED, + AmlReason.MANUAL_CHECK_IP_PHONE, + AmlReason.MANUAL_CHECK_IP_COUNTRY_PHONE, + AmlReason.MANUAL_CHECK_EXTERNAL_ACCOUNT_PHONE, +]; + export const RecheckAmlReasons = [ AmlReason.MANUAL_CHECK_PHONE, AmlReason.MANUAL_CHECK_IP_PHONE, diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index bd974e7fdb..a9add025f5 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -670,6 +670,11 @@ export class BuyCryptoService { async resetAmlCheck(id: number): Promise { const entity = await this.buyCryptoRepo.findOne({ where: { id }, relations: { chargebackOutput: true } }); if (!entity) throw new NotFoundException('BuyCrypto not found'); + + await this.resetAmlCheckInternal(entity); + } + + async resetAmlCheckInternal(entity: BuyCrypto): Promise { if (entity.isComplete || entity.batch || entity.chargebackOutput?.isComplete || entity.chargebackAllowedDate) throw new BadRequestException('BuyCrypto is already complete or payout initiated'); if (!entity.amlCheck) throw new BadRequestException('BuyCrypto AML check is not set'); diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index 925582e953..f1f45b04ac 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -386,6 +386,11 @@ export class BuyFiatService { relations: { fiatOutput: true, transaction: { userData: true }, outputAsset: true }, }); if (!entity) throw new NotFoundException('BuyFiat not found'); + + await this.resetAmlCheckInternal(entity); + } + + async resetAmlCheckInternal(entity: BuyFiat): Promise { if (entity.isComplete || entity.fiatOutput?.isComplete || entity.chargebackAllowedDate) throw new BadRequestException('BuyFiat is already complete'); if (!entity.amlCheck) throw new BadRequestException('BuyFiat amlcheck is not set'); diff --git a/src/subdomains/generic/user/models/user-data/user-data.entity.ts b/src/subdomains/generic/user/models/user-data/user-data.entity.ts index ee4691f857..e36d1505cf 100644 --- a/src/subdomains/generic/user/models/user-data/user-data.entity.ts +++ b/src/subdomains/generic/user/models/user-data/user-data.entity.ts @@ -1,5 +1,6 @@ import { BadRequestException, NotFoundException } from '@nestjs/common'; import { Config } from 'src/config/config'; +import { UserRole } from 'src/shared/auth/user-role.enum'; import { Country } from 'src/shared/models/country/country.entity'; import { IEntity, UpdateResult } from 'src/shared/models/entity'; import { Fiat } from 'src/shared/models/fiat/fiat.entity'; @@ -29,7 +30,6 @@ import { AccountOpenerAuthorization, Organization } from '../organization/organi import { UserDataRelation } from '../user-data-relation/user-data-relation.entity'; import { UpdateUserDto } from '../user/dto/update-user.dto'; import { TradingLimit } from '../user/dto/user.dto'; -import { UserRole } from 'src/shared/auth/user-role.enum'; import { UserStatus } from '../user/user.enum'; import { Wallet } from '../wallet/wallet.entity'; import { AccountType } from './account-type.enum'; @@ -841,6 +841,7 @@ export const UserDataComplianceUpdateCols = [ 'phoneCallIpCheckDate', 'phoneCallIpCountryCheckDate', 'phoneCallExternalAccountCheckDate', + 'phoneCallExternalAccountCheckValue', ]; export function KycCompleted(kycStatus?: KycStatus): boolean { diff --git a/src/subdomains/generic/user/models/user-data/user-data.service.ts b/src/subdomains/generic/user/models/user-data/user-data.service.ts index 573daf44ca..f7d8e17385 100644 --- a/src/subdomains/generic/user/models/user-data/user-data.service.ts +++ b/src/subdomains/generic/user/models/user-data/user-data.service.ts @@ -24,13 +24,16 @@ import { ApiKeyService } from 'src/shared/services/api-key.service'; import { DfxLogger } from 'src/shared/services/dfx-logger'; import { DfxCron } from 'src/shared/utils/cron'; import { AmountType, Util } from 'src/shared/utils/util'; +import { PhoneAmlReasons } from 'src/subdomains/core/aml/enums/aml-reason.enum'; import { CheckStatus } from 'src/subdomains/core/aml/enums/check-status.enum'; +import { BuyCryptoService } from 'src/subdomains/core/buy-crypto/process/services/buy-crypto.service'; import { CustodyService } from 'src/subdomains/core/custody/services/custody.service'; import { HistoryFilter, HistoryFilterKey } from 'src/subdomains/core/history/dto/history-filter.dto'; import { DefaultPaymentLinkConfig, PaymentLinkConfig, } from 'src/subdomains/core/payment-link/entities/payment-link.config'; +import { BuyFiatService } from 'src/subdomains/core/sell-crypto/process/services/buy-fiat.service'; import { KycAddress, KycPersonalData } from 'src/subdomains/generic/kyc/dto/input/kyc-data.dto'; import { KycError } from 'src/subdomains/generic/kyc/dto/kyc-error.enum'; import { MergedDto } from 'src/subdomains/generic/kyc/dto/output/kyc-merged.dto'; @@ -113,6 +116,8 @@ export class UserDataService { private readonly ipLogService: IpLogService, @Inject(forwardRef(() => CustodyService)) private readonly custodyService: CustodyService, + private readonly buyCryptoService: BuyCryptoService, + private readonly buyFiatService: BuyFiatService, ) {} // --- GETTERS --- // @@ -298,7 +303,12 @@ export class UserDataService { async updateUserData(userDataId: number, dto: UpdateUserDataDto): Promise { const userData = await this.userDataRepo.findOne({ where: { id: userDataId }, - relations: { users: { wallet: true }, kycSteps: true, wallet: true }, + relations: { + users: { wallet: true }, + kycSteps: true, + wallet: true, + transactions: { buyCrypto: true, buyFiat: true }, + }, }); if (!userData) throw new NotFoundException('User data not found'); @@ -307,6 +317,31 @@ export class UserDataService { if (dto.phoneCallExternalAccountCheckValue) userData.addPhoneCallExternalAccountCheckValue(dto.phoneCallExternalAccountCheckValue); + if ( + dto.phoneCallStatus === PhoneCallStatus.COMPLETED && + [PhoneCallStatus.FAILED, PhoneCallStatus.USER_REJECTED].includes(userData.phoneCallStatus) + ) { + for (const tx of userData.transactions.filter((t) => t.buyCrypto || t.buyFiat)) { + if (tx.amlCheck === CheckStatus.FAIL) { + if ( + tx.buyCrypto && + !tx.buyCrypto.isComplete && + !tx.buyCrypto.chargebackAllowedDate && + PhoneAmlReasons.includes(tx.buyCrypto.amlReason) + ) + await this.buyCryptoService.resetAmlCheckInternal(tx.buyCrypto); + + if ( + tx.buyFiat && + !tx.buyFiat.isComplete && + !tx.buyFiat.chargebackAllowedDate && + PhoneAmlReasons.includes(tx.buyFiat.amlReason) + ) + await this.buyFiatService.resetAmlCheckInternal(tx.buyFiat); + } + } + } + if (dto.bankTransactionVerification === CheckStatus.PASS) { // cancel a pending video ident, if ident is completed const identCompleted = userData.hasCompletedStep(KycStepName.IDENT);