diff --git a/src/subdomains/core/aml/services/aml-helper.service.ts b/src/subdomains/core/aml/services/aml-helper.service.ts index cf4a777d22..9fa383dc7d 100644 --- a/src/subdomains/core/aml/services/aml-helper.service.ts +++ b/src/subdomains/core/aml/services/aml-helper.service.ts @@ -1,3 +1,4 @@ +import * as IbanTools from 'ibantools'; import { Config, Environment } from 'src/config/config'; import { Active } from 'src/shared/models/active'; import { Country } from 'src/shared/models/country/country.entity'; @@ -218,7 +219,7 @@ export class AmlHelperService { (entity.bankTx || entity.checkoutTx) && entity.userData.phone && entity.userData.birthday && - (!entity.userData.accountType || entity.userData.accountType === AccountType.PERSONAL) && + entity.userData.isPersonalAccount && Util.yearsDiff(entity.userData.birthday) > 55 ) errors.push(AmlError.PHONE_VERIFICATION_NEEDED); @@ -252,28 +253,46 @@ export class AmlHelperService { ) errors.push(AmlError.BIC_BLACKLISTED); if ( - blacklist.some((b) => - b.matches( - [ - SpecialExternalAccountType.BANNED_IBAN, - SpecialExternalAccountType.BANNED_IBAN_BUY, - SpecialExternalAccountType.BANNED_IBAN_AML, - ], - entity.bankTx.iban, - ), + blacklist.some( + (b) => + b.matches( + [ + SpecialExternalAccountType.BANNED_IBAN, + SpecialExternalAccountType.BANNED_IBAN_BUY, + SpecialExternalAccountType.BANNED_IBAN_AML, + ], + entity.bankTx.iban, + ) || + b.matches( + [ + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BLZ_BUY, + SpecialExternalAccountType.BANNED_BLZ_AML, + ], + IbanTools.extractIBAN(entity.bankTx.iban).bankIdentifier, + ), ) ) errors.push(AmlError.IBAN_BLACKLISTED); if ( + !entity.userData.phoneCallCheckDate && + entity.userData.isPersonalAccount && phoneCallList.some((b) => b.matches([SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_BIC_BUY], entity.bankTx.bic), ) ) errors.push(AmlError.BIC_PHONE_VERIFICATION_NEEDED); if ( - phoneCallList.some((b) => - b.matches([SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_IBAN_BUY], entity.bankTx.iban), + !entity.userData.phoneCallCheckDate && + entity.userData.isPersonalAccount && + phoneCallList.some( + (b) => + b.matches([SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_IBAN_BUY], entity.bankTx.iban) || + b.matches( + [SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_BLZ_BUY], + IbanTools.extractIBAN(entity.bankTx.iban).bankIdentifier, + ), ) ) errors.push(AmlError.IBAN_PHONE_VERIFICATION_NEEDED); @@ -328,15 +347,24 @@ export class AmlHelperService { if (entity.sell.fiat.name === 'CHF' && !Config.isDomesticIban(entity.sell.iban)) errors.push(AmlError.ABROAD_CHF_NOT_ALLOWED); if ( - blacklist.some((b) => - b.matches( - [ - SpecialExternalAccountType.BANNED_IBAN, - SpecialExternalAccountType.BANNED_IBAN_SELL, - SpecialExternalAccountType.BANNED_IBAN_AML, - ], - entity.sell.iban, - ), + blacklist.some( + (b) => + b.matches( + [ + SpecialExternalAccountType.BANNED_IBAN, + SpecialExternalAccountType.BANNED_IBAN_SELL, + SpecialExternalAccountType.BANNED_IBAN_AML, + ], + entity.sell.iban, + ) || + b.matches( + [ + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BLZ_SELL, + SpecialExternalAccountType.BANNED_BLZ_AML, + ], + IbanTools.extractIBAN(entity.sell.iban).bankIdentifier, + ), ) ) errors.push(AmlError.IBAN_BLACKLISTED); @@ -438,11 +466,7 @@ export class AmlHelperService { break; case AmlRule.RULE_16: - if ( - entity instanceof BuyCrypto && - entity.userData.accountType === AccountType.PERSONAL && - !entity.userData.phoneCallCheckDate - ) + if (entity instanceof BuyCrypto && entity.userData.isPersonalAccount && !entity.userData.phoneCallCheckDate) errors.push(AmlError.PHONE_VERIFICATION_NEEDED); break; } diff --git a/src/subdomains/core/transaction/transaction-util.service.ts b/src/subdomains/core/transaction/transaction-util.service.ts index 4d1f8f704b..dada1af9b8 100644 --- a/src/subdomains/core/transaction/transaction-util.service.ts +++ b/src/subdomains/core/transaction/transaction-util.service.ts @@ -107,12 +107,20 @@ export class TransactionUtilService { if ( blockedAccounts.some( (b) => - [ + ([ SpecialExternalAccountType.BANNED_IBAN, SpecialExternalAccountType.BANNED_IBAN_BUY, SpecialExternalAccountType.BANNED_IBAN_SELL, SpecialExternalAccountType.BANNED_IBAN_AML, - ].includes(b.type) && b.value === iban, + ].includes(b.type) && + b.value === iban) || + ([ + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BLZ_BUY, + SpecialExternalAccountType.BANNED_BLZ_SELL, + SpecialExternalAccountType.BANNED_BLZ_AML, + ].includes(b.type) && + b.value === IbanTools.extractIBAN(iban).bankIdentifier), ) ) throw new BadRequestException('Iban not allowed'); diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index 3626acc275..bb893136ae 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -257,7 +257,7 @@ export class KycService { } else if ( errors.includes(KycError.VERIFIED_NAME_MISSING) && errors.length === 1 && - entity.userData.accountType === AccountType.PERSONAL + entity.userData.isPersonalAccount ) { await this.userDataService.updateUserDataInternal(entity.userData, { verifiedName: `${entity.userData.firstname} ${entity.userData.surname}`, @@ -1475,7 +1475,7 @@ export class KycService { identStep.userData.verifiedCountry ?? identStep.userData.country; - if (identStep.userData.accountType === AccountType.PERSONAL) { + if (identStep.userData.isPersonalAccount) { // Personal Account if (!userCountry.dfxEnable) errors.push(KycError.COUNTRY_NOT_ALLOWED); if (userCountry.manualReviewRequired) errors.push(KycError.MANUAL_REVIEW_REQUIRED); diff --git a/src/subdomains/generic/kyc/services/name-check.service.ts b/src/subdomains/generic/kyc/services/name-check.service.ts index 9f97217a62..115a72fdd1 100644 --- a/src/subdomains/generic/kyc/services/name-check.service.ts +++ b/src/subdomains/generic/kyc/services/name-check.service.ts @@ -2,7 +2,6 @@ import { Injectable, InternalServerErrorException, NotFoundException, OnModuleIn import { Util } from 'src/shared/utils/util'; import { IsNull } from 'typeorm'; import { BankData, BankDataType } from '../../user/models/bank-data/bank-data.entity'; -import { AccountType } from '../../user/models/user-data/account-type.enum'; import { UserData } from '../../user/models/user-data/user-data.entity'; import { UserDataService } from '../../user/models/user-data/user-data.service'; import { DilisenseApiData } from '../dto/input/dilisense-data.dto'; @@ -48,7 +47,7 @@ export class NameCheckService implements OnModuleInit { // ); // Personal name check - if (!bankData.userData.accountType || bankData.userData.accountType === AccountType.PERSONAL) { + if (bankData.userData.isPersonalAccount) { const { data, file } = await this.getRiskDataAndUploadPdf( bankData.userData, false, diff --git a/src/subdomains/generic/user/models/bank-data/bank-data.service.ts b/src/subdomains/generic/user/models/bank-data/bank-data.service.ts index cc391c82e4..2ee4ed1d1f 100644 --- a/src/subdomains/generic/user/models/bank-data/bank-data.service.ts +++ b/src/subdomains/generic/user/models/bank-data/bank-data.service.ts @@ -23,7 +23,6 @@ import { SpecialExternalAccountService } from 'src/subdomains/supporting/payment import { FindOptionsRelations, FindOptionsWhere, IsNull, Like, Not } from 'typeorm'; import { AccountMerge, MergeReason } from '../account-merge/account-merge.entity'; import { AccountMergeService } from '../account-merge/account-merge.service'; -import { AccountType } from '../user-data/account-type.enum'; import { KycType, UserDataStatus } from '../user-data/user-data.enum'; import { BankData, BankDataType, BankDataVerificationError } from './bank-data.entity'; import { UpdateBankDataDto } from './dto/update-bank-data.dto'; @@ -70,17 +69,14 @@ export class BankDataService { async verifyBankData(entity: BankData): Promise { try { - if ( - !entity.userData.verifiedName && - (entity.userData.accountType === AccountType.PERSONAL || entity.type === BankDataType.BANK_IN) - ) + if (!entity.userData.verifiedName && (entity.userData.isPersonalAccount || entity.type === BankDataType.BANK_IN)) await this.userDataRepo.update(...entity.userData.setVerifiedName(entity.name)); if (entity.type === BankDataType.USER) return; if ([BankDataType.IDENT, BankDataType.NAME_CHECK].includes(entity.type)) { if ( - entity.userData.accountType === AccountType.PERSONAL || + entity.userData.isPersonalAccount || entity.userData.hasCompletedStep(KycStepName.LEGAL_ENTITY) || entity.userData.hasCompletedStep(KycStepName.SOLE_PROPRIETORSHIP_CONFIRMATION) ) { 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 d5430deb17..aa6f840252 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 @@ -639,6 +639,10 @@ export class UserData extends IEntity { // --- KYC PROCESS --- // + get isPersonalAccount(): boolean { + return !this.accountType || this.accountType === AccountType.PERSONAL; + } + get hasSuspiciousMail(): boolean { return (this.mail?.split('@')[0].match(/\d/g) ?? []).length > 2; } @@ -741,7 +745,7 @@ export class UserData extends IEntity { get requiredKycFields(): string[] { return ['accountType', 'mail', 'phone', 'firstname', 'surname', 'street', 'location', 'zip', 'country'].concat( - !this.accountType || this.accountType === AccountType.PERSONAL + this.isPersonalAccount ? [] : ['organizationName', 'organizationStreet', 'organizationLocation', 'organizationZip', 'organizationCountry'], ); @@ -752,9 +756,7 @@ export class UserData extends IEntity { } get requiredInvoiceFields(): string[] { - return ['accountType'].concat( - !this.accountType || this.accountType === AccountType.PERSONAL ? ['firstname', 'surname'] : ['organizationName'], - ); + return ['accountType'].concat(this.isPersonalAccount ? ['firstname', 'surname'] : ['organizationName']); } get isInvoiceDataComplete(): boolean { diff --git a/src/subdomains/supporting/bank/bank-account/is-dfx-iban.validator.ts b/src/subdomains/supporting/bank/bank-account/is-dfx-iban.validator.ts index 6c9e19f331..b7226c76f7 100644 --- a/src/subdomains/supporting/bank/bank-account/is-dfx-iban.validator.ts +++ b/src/subdomains/supporting/bank/bank-account/is-dfx-iban.validator.ts @@ -30,6 +30,7 @@ export class IsDfxIbanValidator implements ValidatorConstraintInterface { ) {} private blockedIbans: string[] = []; + private blockedBLZs: string[] = []; private blockedBICs: string[] = []; private dfxBanks: Bank[] = []; private currentBIC: string = undefined; @@ -37,11 +38,23 @@ export class IsDfxIbanValidator implements ValidatorConstraintInterface { async validate(_: string, args: ValidationArguments) { // blacklist types const type = args.constraints[0]; - const types = [SpecialExternalAccountType.BANNED_IBAN, SpecialExternalAccountType.BANNED_BIC]; + const types = [ + SpecialExternalAccountType.BANNED_IBAN, + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BIC, + ]; if ([IbanType.BUY, IbanType.BOTH].includes(type)) - types.push(SpecialExternalAccountType.BANNED_IBAN_BUY, SpecialExternalAccountType.BANNED_BIC_BUY); + types.push( + SpecialExternalAccountType.BANNED_IBAN_BUY, + SpecialExternalAccountType.BANNED_BLZ_BUY, + SpecialExternalAccountType.BANNED_BIC_BUY, + ); if ([IbanType.SELL, IbanType.BOTH].includes(type)) - types.push(SpecialExternalAccountType.BANNED_IBAN_SELL, SpecialExternalAccountType.BANNED_BIC_SELL); + types.push( + SpecialExternalAccountType.BANNED_IBAN_SELL, + SpecialExternalAccountType.BANNED_BLZ_SELL, + SpecialExternalAccountType.BANNED_BIC_SELL, + ); const blacklists = await this.specialExternalAccountService.getBlacklist(types); @@ -56,6 +69,15 @@ export class IsDfxIbanValidator implements ValidatorConstraintInterface { ].includes(b.type), ) .map((b) => b.value); + this.blockedBLZs = blacklists + .filter((b) => + [ + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BLZ_BUY, + SpecialExternalAccountType.BANNED_BLZ_SELL, + ].includes(b.type), + ) + .map((b) => b.value); this.blockedBICs = blacklists .filter((b) => [ @@ -86,6 +108,9 @@ export class IsDfxIbanValidator implements ValidatorConstraintInterface { const isBlocked = this.blockedIbans.some((i) => new RegExp(i.toLowerCase()).test(iban.toLowerCase())); if (isBlocked) return `${args.property} not allowed`; + if (this.blockedBLZs.some((i) => new RegExp(i).test(IbanTools.extractIBAN(iban).bankIdentifier))) + return `${args.property} not allowed`; + if (this.blockedBICs.some((b) => new RegExp(b.toLowerCase()).test(this.currentBIC?.toLowerCase()))) return `${args.property} BIC not allowed`; diff --git a/src/subdomains/supporting/payment/entities/special-external-account.entity.ts b/src/subdomains/supporting/payment/entities/special-external-account.entity.ts index ead402339a..bbe31599a2 100644 --- a/src/subdomains/supporting/payment/entities/special-external-account.entity.ts +++ b/src/subdomains/supporting/payment/entities/special-external-account.entity.ts @@ -12,9 +12,14 @@ export enum SpecialExternalAccountType { BANNED_BIC_BUY = 'BannedBicBuy', BANNED_BIC_SELL = 'BannedBicSell', BANNED_BIC_AML = 'BannedBicAml', + BANNED_BLZ = 'BannedBlz', + BANNED_BLZ_BUY = 'BannedBlzBuy', + BANNED_BLZ_SELL = 'BannedBlzSell', + BANNED_BLZ_AML = 'BannedBlzAml', BANNED_MAIL = 'BannedMail', BANNED_ACCOUNT_IBAN = 'BannedAccountIban', AML_PHONE_CALL_NEEDED_BIC_BUY = 'AmlPhoneCallNeededBicBuy', + AML_PHONE_CALL_NEEDED_BLZ_BUY = 'AmlPhoneCallNeededBlzBuy', AML_PHONE_CALL_NEEDED_IBAN_BUY = 'AmlPhoneCallNeededIbanBuy', } diff --git a/src/subdomains/supporting/payment/services/special-external-account.service.ts b/src/subdomains/supporting/payment/services/special-external-account.service.ts index 43429f56c9..f52210f405 100644 --- a/src/subdomains/supporting/payment/services/special-external-account.service.ts +++ b/src/subdomains/supporting/payment/services/special-external-account.service.ts @@ -49,6 +49,7 @@ export class SpecialExternalAccountService { type: In([ SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_BIC_BUY, SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_IBAN_BUY, + SpecialExternalAccountType.AML_PHONE_CALL_NEEDED_BLZ_BUY, ]), }); } @@ -67,6 +68,10 @@ export class SpecialExternalAccountService { SpecialExternalAccountType.BANNED_BIC_AML, SpecialExternalAccountType.BANNED_MAIL, SpecialExternalAccountType.BANNED_ACCOUNT_IBAN, + SpecialExternalAccountType.BANNED_BLZ, + SpecialExternalAccountType.BANNED_BLZ_BUY, + SpecialExternalAccountType.BANNED_BLZ_SELL, + SpecialExternalAccountType.BANNED_BLZ_AML, ], ), });