From 6b2eec06aeca4c8ba1c7fbafa7cdb78e953cf70c Mon Sep 17 00:00:00 2001 From: Daniel Padrino Date: Thu, 25 Jun 2026 13:35:27 -0300 Subject: [PATCH] fix(api): reduce ERROR-log noise from expected client failures - realunit: an upstream Aktionariat 4xx (e.g. business 400) was wrapped in a 503 ServiceUnavailableException and self-logged at ERROR, producing two ERROR lines per occurrence. Log at WARN and surface upstream 4xx as BadRequestException so it is not counted as a server fault. - buy-crypto: the expected ConflictException (duplicate KYC file ID) raised during the AML check is now logged at WARN instead of ERROR; all other failures stay ERROR. --- .../process/services/buy-crypto-preparation.service.ts | 8 ++++++-- src/subdomains/supporting/realunit/realunit.service.ts | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts index b7c821b4ce..ccdeaf8216 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { ConflictException, Injectable } from '@nestjs/common'; import { Config } from 'src/config/config'; import { TransactionStatus } from 'src/integration/sift/dto/sift.dto'; import { SiftService } from 'src/integration/sift/services/sift.service'; @@ -210,7 +210,11 @@ export class BuyCryptoPreparationService { if (entity.amlCheck === CheckStatus.FAIL) void this.siftService.buyCryptoTransaction(entity, TransactionStatus.FAILURE); } catch (e) { - this.logger.error(`Error during buy-crypto ${entity.id} AML check:`, e); + if (e instanceof ConflictException) { + this.logger.warn(`Error during buy-crypto ${entity.id} AML check:`, e); + } else { + this.logger.error(`Error during buy-crypto ${entity.id} AML check:`, e); + } } } } diff --git a/src/subdomains/supporting/realunit/realunit.service.ts b/src/subdomains/supporting/realunit/realunit.service.ts index 51934d9a2f..c3ee3a2d38 100644 --- a/src/subdomains/supporting/realunit/realunit.service.ts +++ b/src/subdomains/supporting/realunit/realunit.service.ts @@ -60,6 +60,7 @@ import { FeeService } from 'src/subdomains/supporting/payment/services/fee.servi import { SwissQRService } from 'src/subdomains/supporting/payment/services/swiss-qr.service'; import { TransactionRequestService } from 'src/subdomains/supporting/payment/services/transaction-request.service'; import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service'; +import { isClientError } from 'src/tracing'; import { transliterate } from 'transliteration'; import { AssetPricesService } from '../pricing/services/asset-prices.service'; import { PriceCurrency, PriceValidity, PricingService } from '../pricing/services/pricing.service'; @@ -611,10 +612,12 @@ export class RealUnitService { price: Math.round(request.amount * 100), }); } catch (error) { + const upstreamStatus = error?.response?.status; const message = error?.response?.data ? JSON.stringify(error.response.data) : error?.message || error; - this.logger.error( + this.logger.warn( `Failed to request payment instructions from Aktionariat for request ${requestId} (currency: ${fiat.name}, shares: ${Math.floor(request.estimatedAmount)}, price: ${Math.round(request.amount * 100)}): ${message}`, ); + if (isClientError(upstreamStatus)) throw new BadRequestException(`Aktionariat API error: ${message}`); throw new ServiceUnavailableException(`Aktionariat API error: ${message}`); }