From cc39fe946c3098f7bd5509e9f20dd2106f2eb979 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:35:40 +0100 Subject: [PATCH 1/2] feat: dedicated CHANGE_PHONE KYC step for phone updates (#3368) --- .../generic/kyc/entities/kyc-step.entity.ts | 3 + .../generic/kyc/enums/kyc-step-name.enum.ts | 1 + .../user/models/user-data/user-data.entity.ts | 1 - .../models/user-data/user-data.service.ts | 55 ++++++++++++++----- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts index 251902d301..e6c1c7aee3 100644 --- a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts +++ b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts @@ -133,6 +133,9 @@ export class KycStep extends IEntity { case KycStepName.PAYMENT_AGREEMENT: return { url: `${apiUrl}/data/payment/${this.id}`, type: UrlType.API }; + + case KycStepName.PHONE_CHANGE: + return { url: '', type: UrlType.NONE }; } } diff --git a/src/subdomains/generic/kyc/enums/kyc-step-name.enum.ts b/src/subdomains/generic/kyc/enums/kyc-step-name.enum.ts index 707a2bd3ae..ac26e6e434 100644 --- a/src/subdomains/generic/kyc/enums/kyc-step-name.enum.ts +++ b/src/subdomains/generic/kyc/enums/kyc-step-name.enum.ts @@ -22,6 +22,7 @@ export enum KycStepName { // additional features PAYMENT_AGREEMENT = 'PaymentAgreement', RECALL_AGREEMENT = 'RecallAgreement', + PHONE_CHANGE = 'PhoneChange', // external registrations REALUNIT_REGISTRATION = 'RealUnitRegistration', 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 aa6f840252..93fdfcddd8 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 @@ -496,7 +496,6 @@ export class UserData extends IEntity { setUserDataSettings(dto: UpdateUserDto): UpdateResult { const update: Partial = { - phone: dto.phone ?? this.phone, language: dto.language ?? this.language, currency: dto.currency ?? this.currency, }; 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 e4a55f545b..b0a9222ffe 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 @@ -736,12 +736,45 @@ export class UserDataService { return userData; } - // --- SETTINGS UPDATE --- // - async updateUserSettings(userData: UserData, dto: UpdateUserDto): Promise { - // check phone KYC is already started - if (userData.kycLevel != KycLevel.LEVEL_0 && (dto.phone === null || dto.phone === '')) + // --- PHONE UPDATE --- // + async updatePhone(userData: UserData, phone: string): Promise { + if (userData.kycLevel !== KycLevel.LEVEL_0 && !phone) throw new BadRequestException('KYC already started, user data deletion not allowed'); + const previousPhone = userData.phone; + + await this.userDataRepo.update(userData.id, { + phone, + phoneCallCheckDate: null, + phoneCallIpCheckDate: null, + phoneCallIpCountryCheckDate: null, + }); + + Object.assign(userData, { + phone, + phoneCallCheckDate: null, + phoneCallIpCheckDate: null, + phoneCallIpCountryCheckDate: null, + }); + + // update Sift + for (const user of userData.users) { + this.siftService.updateAccount({ + $user_id: user.id.toString(), + $time: Date.now(), + $phone: phone, + }); + } + + // create KYC step + await this.kycService.createCustomKycStep(userData, KycStepName.PHONE_CHANGE, ReviewStatus.COMPLETED, { + phone, + previousPhone, + }); + } + + // --- SETTINGS UPDATE --- // + async updateUserSettings(userData: UserData, dto: UpdateUserDto): Promise { // check language if (dto.language) { dto.language = await this.languageService.getLanguage(dto.language.id); @@ -754,17 +787,9 @@ export class UserDataService { if (!dto.currency) throw new BadRequestException('Currency not found'); } - const phoneChanged = dto.phone && dto.phone !== userData.phone; - - const updateSiftAccount: CreateAccount = { $time: Date.now() }; - - if (phoneChanged) updateSiftAccount.$phone = dto.phone; - - if (phoneChanged) { - for (const user of userData.users) { - updateSiftAccount.$user_id = user.id.toString(); - this.siftService.updateAccount(updateSiftAccount); - } + // check phone + if (dto.phone && dto.phone !== userData.phone) { + await this.updatePhone(userData, dto.phone); } await this.userDataRepo.update(...userData.setUserDataSettings(dto)); From 81dffc7272b9a6bd73426ea9cfd9bfa5b0f484e5 Mon Sep 17 00:00:00 2001 From: Lam Nguyen <32935491+xlamn@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:51:43 +0100 Subject: [PATCH 2/2] fix: kyc steps null safety. (#3370) --- src/subdomains/generic/kyc/dto/mapper/kyc-info.mapper.ts | 2 +- .../generic/user/models/user-data/user-data.entity.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/subdomains/generic/kyc/dto/mapper/kyc-info.mapper.ts b/src/subdomains/generic/kyc/dto/mapper/kyc-info.mapper.ts index 98ec95df6e..3876a7a363 100644 --- a/src/subdomains/generic/kyc/dto/mapper/kyc-info.mapper.ts +++ b/src/subdomains/generic/kyc/dto/mapper/kyc-info.mapper.ts @@ -49,7 +49,7 @@ export class KycInfoMapper { ); return KycInfoMapper.sortSteps( - userData.kycSteps.filter((s) => s.status !== ReviewStatus.CANCELED).concat(openSteps), + (userData.kycSteps ?? []).filter((s) => s.status !== ReviewStatus.CANCELED).concat(openSteps), ); } 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 93fdfcddd8..9050e3a94d 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 @@ -647,7 +647,7 @@ export class UserData extends IEntity { } getStep(stepId: number): KycStep | undefined { - return this.kycSteps.find((s) => s.id === stepId); + return (this.kycSteps ?? []).find((s) => s.id === stepId); } getStepOrThrow(stepId: number): KycStep { @@ -658,7 +658,7 @@ export class UserData extends IEntity { } getStepsWith(name?: KycStepName, type?: KycStepType, sequenceNumber?: number): KycStep[] { - return this.kycSteps.filter( + return (this.kycSteps ?? []).filter( (s) => (!name || s.name === name) && (!type || s.type === type) && @@ -686,7 +686,7 @@ export class UserData extends IEntity { } get hasStepsInProgress(): boolean { - return this.kycSteps.some((s) => s.isInProgress); + return (this.kycSteps ?? []).some((s) => s.isInProgress); } getNextSequenceNumber(stepName: KycStepName, stepType?: KycStepType): number {