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/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/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 { 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', }), ); }); 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..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); } @@ -200,7 +201,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');