From 2bb9858bcf5650f4c61d747fc05911bb866ee956 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:19:35 +0100 Subject: [PATCH 1/4] [DEV-4539] refund validation checks --- .../controllers/transaction.controller.ts | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/subdomains/core/history/controllers/transaction.controller.ts b/src/subdomains/core/history/controllers/transaction.controller.ts index ff9d6961d3..5ba81d47ce 100644 --- a/src/subdomains/core/history/controllers/transaction.controller.ts +++ b/src/subdomains/core/history/controllers/transaction.controller.ts @@ -424,6 +424,8 @@ export class TransactionController { const refundData = this.refundList.get(transaction.id); if (!refundData) throw new BadRequestException('Request refund data first'); if (!this.isRefundDataValid(refundData)) throw new BadRequestException('Refund data request invalid'); + if (refundData.refundTarget && dto.refundTarget) + throw new BadRequestException('RefundTarget is already set with refundData'); await this.executeRefund(transaction, transaction.targetEntity, refundData, dto); @@ -519,7 +521,7 @@ export class TransactionController { if (!dto.creditorData) throw new BadRequestException('Creditor data is required for bank refunds'); return this.buyCryptoService.refundBankTx(targetEntity, { - refundIban: dto.refundTarget ?? refundData.refundTarget, + refundIban: refundData.refundTarget ?? dto.refundTarget, chargebackCurrency, creditorData: dto.creditorData, ...refundDto, @@ -537,19 +539,18 @@ export class TransactionController { private async getRefundTarget(transaction: Transaction): Promise { if (transaction.refundTargetEntity instanceof BuyFiat) return transaction.refundTargetEntity.chargebackAddress; - // For bank transactions, always return the original IBAN - refund must go to the sender - if (transaction.bankTx?.iban) return transaction.bankTx.iban; - - // For BuyCrypto with checkout (card), return masked card number - if (transaction.refundTargetEntity instanceof BuyCrypto && transaction.refundTargetEntity.checkoutTx) - return `${transaction.refundTargetEntity.checkoutTx.cardBin}****${transaction.refundTargetEntity.checkoutTx.cardLast4}`; - - // For other cases, return existing chargeback IBAN - if (transaction.refundTargetEntity instanceof BankTx) return transaction.bankTx?.iban; - if (transaction.refundTargetEntity instanceof BuyCrypto) return transaction.refundTargetEntity.chargebackIban; - if (transaction.refundTargetEntity instanceof BankTxReturn) return transaction.refundTargetEntity.chargebackIban; + try { + if (transaction.bankTx && (await this.validateIban(transaction.bankTx.iban))) return transaction.bankTx.iban; + } catch (_) { + return transaction.refundTargetEntity instanceof BankTx + ? undefined + : transaction.refundTargetEntity?.chargebackIban; + } - return undefined; + if (transaction.refundTargetEntity instanceof BuyCrypto) + return transaction.refundTargetEntity.checkoutTx + ? `${transaction.refundTargetEntity.checkoutTx.cardBin}****${transaction.refundTargetEntity.checkoutTx.cardLast4}` + : transaction.refundTargetEntity.chargebackIban; } private async validateIban(iban: string): Promise { From b30dfbc8f1107cf2c915e1013e5ff5cd7c8829e8 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:39:23 +0100 Subject: [PATCH 2/4] [DEV-4539] Dont overwrite creditorData --- .../core/buy-crypto/process/services/buy-crypto.service.ts | 2 +- .../supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index b33aff8a80..fda97fec2c 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -545,7 +545,7 @@ export class BuyCryptoService { ) throw new BadRequestException('IBAN not valid or BIC not available'); - const creditorData = dto.creditorData ?? buyCrypto.creditorData; + const creditorData = buyCrypto.creditorData ?? dto.creditorData; if ((dto.chargebackAllowedDate || dto.chargebackAllowedDateUser) && !creditorData) throw new BadRequestException('Creditor data is required for chargeback'); diff --git a/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts b/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts index 2992519413..0f0c0280c1 100644 --- a/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts +++ b/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts @@ -200,7 +200,7 @@ export class BankTxReturnService { ) throw new BadRequestException('IBAN not valid or BIC not available'); - const creditorData = dto.creditorData ?? bankTxReturn.creditorData; + const creditorData = bankTxReturn.creditorData ?? dto.creditorData; if ((dto.chargebackAllowedDate || dto.chargebackAllowedDateUser) && !creditorData) throw new BadRequestException('Creditor data is required for chargeback'); From 5902505cd7a024e60d41d33925bfb72af16a8350 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:55:08 +0100 Subject: [PATCH 3/4] [DEV-4539] fix unit test --- .../__tests__/refund-creditor-data.spec.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/subdomains/supporting/bank-tx/bank-tx-return/__tests__/refund-creditor-data.spec.ts b/src/subdomains/supporting/bank-tx/bank-tx-return/__tests__/refund-creditor-data.spec.ts index f47071db1d..a203b64656 100644 --- a/src/subdomains/supporting/bank-tx/bank-tx-return/__tests__/refund-creditor-data.spec.ts +++ b/src/subdomains/supporting/bank-tx/bank-tx-return/__tests__/refund-creditor-data.spec.ts @@ -1,15 +1,15 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock } from '@golevelup/ts-jest'; -import { BankTxReturnService } from '../bank-tx-return.service'; -import { BankTxReturnRepository } from '../bank-tx-return.repository'; -import { FiatOutputService } from 'src/subdomains/supporting/fiat-output/fiat-output.service'; +import { Test, TestingModule } from '@nestjs/testing'; +import { FiatService } from 'src/shared/models/fiat/fiat.service'; +import { CheckStatus } from 'src/subdomains/core/aml/enums/check-status.enum'; import { TransactionUtilService } from 'src/subdomains/core/transaction/transaction-util.service'; +import { FiatOutputType } from 'src/subdomains/supporting/fiat-output/fiat-output.entity'; +import { FiatOutputService } from 'src/subdomains/supporting/fiat-output/fiat-output.service'; import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service'; import { PricingService } from 'src/subdomains/supporting/pricing/services/pricing.service'; -import { FiatService } from 'src/shared/models/fiat/fiat.service'; import { BankTxReturn } from '../bank-tx-return.entity'; -import { FiatOutputType } from 'src/subdomains/supporting/fiat-output/fiat-output.entity'; -import { CheckStatus } from 'src/subdomains/core/aml/enums/check-status.enum'; +import { BankTxReturnRepository } from '../bank-tx-return.repository'; +import { BankTxReturnService } from '../bank-tx-return.service'; /** * Test: Creditor-Daten Fallback in BankTxReturnService.refundBankTx() @@ -104,7 +104,7 @@ describe('BankTxReturnService - refundBankTx Creditor Data', () => { ); }); - it('should use dto creditor data when provided (override)', async () => { + it('should use chargeback creditor if set', async () => { const dto = { chargebackAllowedDate: new Date(), chargebackAllowedBy: 'Admin', @@ -126,12 +126,12 @@ describe('BankTxReturnService - refundBankTx Creditor Data', () => { mockBankTxReturn.id, false, expect.objectContaining({ - name: 'Override Name', - address: 'Override Address', - houseNumber: '99', - zip: '9999', - city: 'Override City', - country: 'DE', + name: 'Max Mustermann', + address: 'Hauptstrasse', + houseNumber: '42', + zip: '3000', + city: 'Bern', + country: 'CH', }), ); }); From 29e93f671cc4c2fda62832517c139c99130dd075 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:01:56 +0100 Subject: [PATCH 4/4] [DEV-4539] check name for auto refund --- .../services/buy-crypto-preparation.service.ts | 7 ++++++- .../bank-tx-return/bank-tx-return.service.ts | 17 +++++++++-------- 2 files changed, 15 insertions(+), 9 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 e1996d3079..fd5a1ec54b 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 @@ -476,7 +476,12 @@ export class BuyCryptoPreparationService { const chargebackAllowedBy = 'API'; if (entity.bankTx) { - await this.buyCryptoService.refundBankTx(entity, { chargebackAllowedDate, chargebackAllowedBy }); + if ( + Util.includesSameName(entity.userData.verifiedName, entity.creditorData.name) || + Util.includesSameName(entity.userData.completeName, entity.creditorData.name) || + (!entity.userData.verifiedName && !entity.userData.completeName) + ) + await this.buyCryptoService.refundBankTx(entity, { chargebackAllowedDate, chargebackAllowedBy }); } else if (entity.cryptoInput) { await this.buyCryptoService.refundCryptoInput(entity, { chargebackAllowedDate, chargebackAllowedBy }); } else { diff --git a/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts b/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts index 0f0c0280c1..9117f1f5ed 100644 --- a/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts +++ b/src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service.ts @@ -53,10 +53,6 @@ export class BankTxReturnService { const entities = await this.bankTxReturnRepo.find({ where: [ - { - ...baseWhere, - userData: IsNull(), - }, { ...baseWhere, userData: { @@ -71,10 +67,15 @@ export class BankTxReturnService { for (const entity of entities) { try { - await this.refundBankTx(entity, { - chargebackAllowedDate: new Date(), - chargebackAllowedBy: 'API', - }); + if ( + Util.includesSameName(entity.userData.verifiedName, entity.creditorData.name) || + Util.includesSameName(entity.userData.completeName, entity.creditorData.name) || + (!entity.userData.verifiedName && !entity.userData.completeName) + ) + await this.refundBankTx(entity, { + chargebackAllowedDate: new Date(), + chargebackAllowedBy: 'API', + }); } catch (e) { this.logger.error(`Failed to chargeback bank-tx-return ${entity.id}:`, e); }