From a432e9a27d7f72bb7b7e361fa5dd0b29bdc9b89d Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:35:46 +0100 Subject: [PATCH 1/5] [NOTASK] new dfxApproval reasons (#3434) --- src/subdomains/generic/kyc/dto/kyc-error.enum.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/subdomains/generic/kyc/dto/kyc-error.enum.ts b/src/subdomains/generic/kyc/dto/kyc-error.enum.ts index 161edd6550..3517e36308 100644 --- a/src/subdomains/generic/kyc/dto/kyc-error.enum.ts +++ b/src/subdomains/generic/kyc/dto/kyc-error.enum.ts @@ -36,7 +36,7 @@ export enum KycError { DENIED_RECOMMENDATION = 'DeniedRecommendation', RECOMMENDER_BLOCKED = 'RecommenderBlocked', - // FinancialData errors + // FinancialData errors/reasons MISSING_INFO = 'MissingInfo', RISKY_BUSINESS = 'RiskyBusiness', INCORRECT_INFO = 'IncorrectInfo', @@ -50,6 +50,8 @@ export enum KycError { // DfxApproval errors BANK_RECALL_FEE_NOT_PAID = 'BankRecallFeeNotPaid', OPEN_SANCTIONED_NAME_CHECK = 'OpenSanctionedNameCheck', + RISK_ACCEPTED = 'RiskAccepted', + NO_GWG_RISK = 'NoGwgRisk', // Deactivated userData errors USER_DATA_DEACTIVATED = 'UserDataDeactivated', @@ -95,6 +97,8 @@ export const KycErrorMap: Record = { [KycError.RESIDENCE_PERMIT_CHECK_REQUIRED]: undefined, [KycError.EXPIRED_STEP]: 'Your documents are expired', [KycError.MANUAL_REVIEW_REQUIRED]: undefined, + [KycError.RISK_ACCEPTED]: undefined, + [KycError.NO_GWG_RISK]: undefined, }; export const KycReasonMap: { [e in KycError]?: KycStepReason } = { From 40d465d787e7f3347690ef01e7151e79b37d2f22 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:36:42 +0100 Subject: [PATCH 2/5] chore: various improvements/fixes (#3447) --- .../process/entities/buy-crypto.entity.ts | 2 +- .../core/referral/referral.module.ts | 2 +- .../reward/services/ref-reward.service.ts | 18 +++++++++++++++++ .../generic/kyc/services/kyc-log.service.ts | 9 ++++----- .../dashboard/dashboard-financial.service.ts | 20 +++---------------- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts index 0990d473ed..e9381ec384 100644 --- a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts +++ b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts @@ -715,7 +715,7 @@ export class BuyCrypto extends IEntity { refFactor: null, usedFees: null, networkStartFeeAmount: null, - status: null, + status: BuyCryptoStatus.CREATED, }; Object.assign(this, update); diff --git a/src/subdomains/core/referral/referral.module.ts b/src/subdomains/core/referral/referral.module.ts index 7cd4d6e019..265bc34db0 100644 --- a/src/subdomains/core/referral/referral.module.ts +++ b/src/subdomains/core/referral/referral.module.ts @@ -46,6 +46,6 @@ import { RefRewardService } from './reward/services/ref-reward.service'; RefRewardOutService, RefRewardJobService, ], - exports: [RefService, RefRewardService, RefRewardRepository], + exports: [RefService, RefRewardService], }) export class ReferralModule {} diff --git a/src/subdomains/core/referral/reward/services/ref-reward.service.ts b/src/subdomains/core/referral/reward/services/ref-reward.service.ts index af6a35d23d..ab4b88423f 100644 --- a/src/subdomains/core/referral/reward/services/ref-reward.service.ts +++ b/src/subdomains/core/referral/reward/services/ref-reward.service.ts @@ -239,4 +239,22 @@ export class RefRewardService { cryptoCurrency: v.outputAsset.dexName, })); } + + async getRewardRecipients(from?: Date): Promise<{ userDataId: number; count: number; totalChf: number }[]> { + const query = this.rewardRepo + .createQueryBuilder('r') + .innerJoin('r.user', 'u') + .select('u.userDataId', 'userDataId') + .addSelect('COUNT(*)', 'count') + .addSelect('ROUND(SUM(r.amountInChf), 0)', 'totalChf') + .where('r.status != :excluded', { excluded: RewardStatus.USER_SWITCH }) + .groupBy('u.userDataId') + .orderBy('totalChf', 'DESC'); + + if (from) { + query.andWhere('r.created >= :from', { from }); + } + + return query.getRawMany(); + } } diff --git a/src/subdomains/generic/kyc/services/kyc-log.service.ts b/src/subdomains/generic/kyc/services/kyc-log.service.ts index 0e5877285c..5471604c1f 100644 --- a/src/subdomains/generic/kyc/services/kyc-log.service.ts +++ b/src/subdomains/generic/kyc/services/kyc-log.service.ts @@ -91,11 +91,10 @@ export class KycLogService { } async getLogsByUserDataId(userDataId: number): Promise { - return this.kycLogRepo - .createQueryBuilder('log') - .where('log.userDataId = :userDataId', { userDataId }) - .orderBy('log.created', 'DESC') - .getMany(); + return this.kycLogRepo.find({ + where: { userData: { id: userDataId } }, + order: { created: 'DESC' }, + }); } async createKycFileLog(log: string, user?: UserData) { diff --git a/src/subdomains/supporting/dashboard/dashboard-financial.service.ts b/src/subdomains/supporting/dashboard/dashboard-financial.service.ts index ef6f4f17ba..3b5b6f622a 100644 --- a/src/subdomains/supporting/dashboard/dashboard-financial.service.ts +++ b/src/subdomains/supporting/dashboard/dashboard-financial.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { AssetService } from 'src/shared/models/asset/asset.service'; -import { RefRewardRepository } from '../../core/referral/reward/ref-reward.repository'; +import { RefRewardService } from '../../core/referral/reward/services/ref-reward.service'; import { Log } from '../log/log.entity'; import { LogService } from '../log/log.service'; import { FinanceLog } from '../log/dto/log.dto'; @@ -19,7 +19,7 @@ export class DashboardFinancialService { constructor( private readonly logService: LogService, private readonly assetService: AssetService, - private readonly refRewardRepo: RefRewardRepository, + private readonly refRewardService: RefRewardService, ) {} async getFinancialLog(from?: Date, dailySample?: boolean): Promise { @@ -37,21 +37,7 @@ export class DashboardFinancialService { } async getRefRewardRecipients(from?: Date): Promise { - const query = this.refRewardRepo - .createQueryBuilder('r') - .innerJoin('r.user', 'u') - .select('u.userDataId', 'userDataId') - .addSelect('COUNT(*)', 'count') - .addSelect('ROUND(SUM(r.amountInChf), 0)', 'totalChf') - .where('r.status != :excluded', { excluded: 'UserSwitch' }) - .groupBy('u.userDataId') - .orderBy('totalChf', 'DESC'); - - if (from) { - query.andWhere('r.created >= :from', { from }); - } - - return query.getRawMany(); + return this.refRewardService.getRewardRecipients(from); } async getLatestFinancialChanges(): Promise { From 017ae56304eb8312d2c7f912b43536d6040ba17f Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:35:20 +0100 Subject: [PATCH 3/5] Fix KYC ContactData step not completing on mail update (#3455) * Fix KYC ContactData step not completing on mail update When PUT /v2/user/mail is called, initializeProcess() returns early if a ContactData step already exists, even if the step is still InProgress. This prevents the KYC level from advancing from 0 to 10. Now checks for a pending ContactData step and completes it with the user's email when available, then calls updateProgress() to advance the KYC level. * Add missing step log when completing ContactData via mail update --- src/subdomains/generic/kyc/services/kyc.service.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index b6338915fc..128dc37135 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -468,7 +468,19 @@ export class KycService { async initializeProcess(userData: UserData): Promise { const user = await this.getUser(userData.kycHash); - if (user.getStepsWith(KycStepName.CONTACT_DATA).length > 0) return user; + + const contactSteps = user.getStepsWith(KycStepName.CONTACT_DATA); + if (contactSteps.length > 0) { + // Complete pending ContactData step if user now has an email + const pendingStep = contactSteps.find((s) => s.isInProgress); + if (pendingStep && user.mail) { + const result = await this.trySetMail(user, pendingStep, user.mail); + await this.kycStepRepo.update(...result); + await this.createStepLog(user, pendingStep); + await this.updateProgress(user, false); + } + return user; + } return this.updateProgress(user, true, false); } From cb3b2f299a9f488fa56258f11c83926cf808da23 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:58:20 +0100 Subject: [PATCH 4/5] Log failed mail update attempts on conflict (#3456) * Log failed mail update attempts on conflict When a user tries to set an email that already belongs to another account, the attempt was silently lost. Now creates a KYC log entry with the attempted mail and error reason before throwing the ConflictException. * Fix prettier formatting --- .../generic/user/models/user-data/user-data.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) 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 b33b4ae329..e6db3ff3d8 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 @@ -731,6 +731,12 @@ export class UserDataService { if (mergeRequested) errorMessage += ' - account merge request sent'; } + await this.kycLogService.createLogInternal( + userData, + KycLogType.MAIL_CHANGE, + `Failed: ${userData.mail} -> ${mail} (${errorMessage})`, + ); + throw new ConflictException(errorMessage); } From 4751ae7ee51a09b401d39c70baf0dadf2df14b4f Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:05:47 +0100 Subject: [PATCH 5/5] Fail ContactData kyc_step on mail conflict in user/mail endpoint (#3457) When PUT /v2/user/mail is called with an email that already belongs to another account, the pending ContactData step was left unchanged in InProgress status with no record of the attempt. Now fails the step with the attempted mail and error reason (matching the behavior of PUT /v2/kyc/data/contact), and creates a step log. --- .../generic/kyc/services/kyc.service.ts | 16 ++++++++++++++++ .../user/models/user-data/user-data.service.ts | 6 +----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index 128dc37135..e0f9abbf06 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -485,6 +485,22 @@ export class KycService { return this.updateProgress(user, true, false); } + async failContactStepForMail(userData: UserData, mail: string, error: string): Promise { + try { + const user = await this.getUser(userData.kycHash); + const pendingStep = user.getStepsWith(KycStepName.CONTACT_DATA).find((s) => s.isInProgress); + if (!pendingStep) return; + + const kycError = error.includes('account merge request sent') + ? KycError.USER_DATA_MERGE_REQUESTED + : KycError.USER_DATA_EXISTING; + await this.kycStepRepo.update(...pendingStep.fail({ mail }, kycError)); + await this.createStepLog(user, pendingStep); + } catch (e) { + this.logger.error(`Failed to update ContactData step for account ${userData.id}:`, e); + } + } + public getMailFailedReason(comment: string, language: string): string { return `
    ${comment ?.split(';') 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 e6db3ff3d8..c48789205d 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 @@ -731,11 +731,7 @@ export class UserDataService { if (mergeRequested) errorMessage += ' - account merge request sent'; } - await this.kycLogService.createLogInternal( - userData, - KycLogType.MAIL_CHANGE, - `Failed: ${userData.mail} -> ${mail} (${errorMessage})`, - ); + await this.kycService.failContactStepForMail(userData, mail, errorMessage); throw new ConflictException(errorMessage); }