From fdd28bc49f368cd8f79845d9f2b1be4a1dfb7eef Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:43:29 +0100 Subject: [PATCH 1/3] fix: review nationality step immediately instead of waiting for cron job The nationality review check is trivial (blocked/merged user, allowed nationality) and can be performed inline when the user submits their data, eliminating the up-to-1-minute wait for the cron job to process the step. --- .../generic/kyc/controllers/kyc.controller.ts | 2 +- .../generic/kyc/services/kyc.service.ts | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/subdomains/generic/kyc/controllers/kyc.controller.ts b/src/subdomains/generic/kyc/controllers/kyc.controller.ts index 36b92ea510..c0c7aa4332 100644 --- a/src/subdomains/generic/kyc/controllers/kyc.controller.ts +++ b/src/subdomains/generic/kyc/controllers/kyc.controller.ts @@ -214,7 +214,7 @@ export class KycController { @Param('id') id: string, @Body() data: KycNationalityData, ): Promise { - return this.kycService.updateKycStep(code, +id, data, ReviewStatus.INTERNAL_REVIEW); + return this.kycService.updateNationalityStep(code, +id, data); } @Put('data/recommendation/:id') diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index 3626acc275..3b2a9e953d 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -575,6 +575,41 @@ export class KycService { return this.updateKycStepAndLog(kycStep, user, data, reviewStatus); } + async updateNationalityStep(kycHash: string, stepId: number, data: KycNationalityData): Promise { + const user = await this.getUser(kycHash); + const kycStep = user.getPendingStepOrThrow(stepId); + + const nationality = await this.countryService.getCountry(data.nationality.id); + if (!nationality) throw new BadRequestException('Nationality not found'); + + Object.assign(data.nationality, { id: nationality.id, symbol: nationality.symbol }); + + // set to INTERNAL_REVIEW first (same as before) + await this.kycStepRepo.update(...kycStep.update(ReviewStatus.INTERNAL_REVIEW, data)); + await this.createStepLog(user, kycStep); + + // immediately review instead of waiting for cron job + if (!Config.kyc.residencePermitCountries.includes(nationality.symbol)) { + const errors = this.getNationalityErrors(kycStep, nationality); + const comment = errors.join(';'); + + if (errors.some((e) => KycStepIgnoringErrors.includes(e))) { + await this.kycStepRepo.update(...kycStep.ignored(comment)); + } else if (errors.length > 0) { + await this.kycStepRepo.update(...kycStep.manualReview(comment)); + } else { + await this.kycStepRepo.update(...kycStep.complete()); + await this.checkDfxApproval(kycStep); + } + + await this.createStepLog(user, kycStep); + } + + await this.updateProgress(user, false); + + return KycStepMapper.toStepBase(kycStep); + } + async updateBeneficialOwnerData(kycHash: string, stepId: number, data: KycBeneficialData): Promise { const user = await this.getUser(kycHash); const kycStep = user.getPendingStepOrThrow(stepId); From 2121d45a5e363288b8903fc7970bc1b026139e5d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:50:37 +0100 Subject: [PATCH 2/3] refactor: extract shared reviewNationalityData helper method Eliminate code duplication between updateNationalityStep (inline review) and reviewNationalityStep (cron job) by extracting the review logic into a single private helper method. --- .../generic/kyc/services/kyc.service.ts | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index 3b2a9e953d..dc064c1100 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -176,22 +176,7 @@ export class KycService { const result = entity.getResult(); const nationality = await this.countryService.getCountry(result.nationality.id); - //Skip nationalities which needs a residencePermit first - if (Config.kyc.residencePermitCountries.includes(nationality.symbol)) continue; - - const errors = this.getNationalityErrors(entity, nationality); - const comment = errors.join(';'); - - if (errors.some((e) => KycStepIgnoringErrors.includes(e))) { - await this.kycStepRepo.update(...entity.ignored(comment)); - } else if (errors.length > 0) { - await this.kycStepRepo.update(...entity.manualReview(comment)); - } else { - await this.kycStepRepo.update(...entity.complete()); - await this.checkDfxApproval(entity); - } - - await this.createStepLog(entity.userData, entity); + await this.reviewNationalityData(entity, entity.userData, nationality); } catch (e) { this.logger.error(`Failed to auto review nationality step ${entity.id}:`, e); } @@ -584,26 +569,10 @@ export class KycService { Object.assign(data.nationality, { id: nationality.id, symbol: nationality.symbol }); - // set to INTERNAL_REVIEW first (same as before) await this.kycStepRepo.update(...kycStep.update(ReviewStatus.INTERNAL_REVIEW, data)); await this.createStepLog(user, kycStep); - // immediately review instead of waiting for cron job - if (!Config.kyc.residencePermitCountries.includes(nationality.symbol)) { - const errors = this.getNationalityErrors(kycStep, nationality); - const comment = errors.join(';'); - - if (errors.some((e) => KycStepIgnoringErrors.includes(e))) { - await this.kycStepRepo.update(...kycStep.ignored(comment)); - } else if (errors.length > 0) { - await this.kycStepRepo.update(...kycStep.manualReview(comment)); - } else { - await this.kycStepRepo.update(...kycStep.complete()); - await this.checkDfxApproval(kycStep); - } - - await this.createStepLog(user, kycStep); - } + await this.reviewNationalityData(kycStep, user, nationality); await this.updateProgress(user, false); @@ -1408,6 +1377,24 @@ export class KycService { return errors; } + private async reviewNationalityData(kycStep: KycStep, user: UserData, nationality: Country): Promise { + if (Config.kyc.residencePermitCountries.includes(nationality.symbol)) return; + + const errors = this.getNationalityErrors(kycStep, nationality); + const comment = errors.join(';'); + + if (errors.some((e) => KycStepIgnoringErrors.includes(e))) { + await this.kycStepRepo.update(...kycStep.ignored(comment)); + } else if (errors.length > 0) { + await this.kycStepRepo.update(...kycStep.manualReview(comment)); + } else { + await this.kycStepRepo.update(...kycStep.complete()); + await this.checkDfxApproval(kycStep); + } + + await this.createStepLog(user, kycStep); + } + private getFinancialDataErrors(entity: KycStep): KycError[] { const errors = this.getStepDefaultErrors(entity); const financialStepResult = entity.getResult(); From 433e1c74f79d3ed1c26812f2aa876a9933115d38 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:58:26 +0100 Subject: [PATCH 3/3] refactor: remove dead nationality branch from updateKycStep The nationality path is now handled by the dedicated updateNationalityStep method, so the if (data.nationality) branch in updateKycStep is unreachable. --- src/subdomains/generic/kyc/services/kyc.service.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index dc064c1100..3a69baf122 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -545,17 +545,10 @@ export class KycService { data: Partial, reviewStatus: ReviewStatus, ): Promise { - let user = await this.getUser(kycHash); + const user = await this.getUser(kycHash); const kycStep = user.getPendingStepOrThrow(stepId); - if (data.nationality) { - const nationality = await this.countryService.getCountry(data.nationality.id); - if (!nationality) throw new BadRequestException('Nationality not found'); - - Object.assign(data.nationality, { id: nationality.id, symbol: nationality.symbol }); - } else { - user = await this.userDataService.updateUserDataInternal(user, data); - } + await this.userDataService.updateUserDataInternal(user, data); return this.updateKycStepAndLog(kycStep, user, data, reviewStatus); }