Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
27 changes: 14 additions & 13 deletions src/subdomains/core/history/controllers/transaction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand All @@ -537,19 +539,18 @@ export class TransactionController {
private async getRefundTarget(transaction: Transaction): Promise<string | undefined> {
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<boolean> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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',
Expand All @@ -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',
}),
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ export class BankTxReturnService {

const entities = await this.bankTxReturnRepo.find({
where: [
{
...baseWhere,
userData: IsNull(),
},
{
...baseWhere,
userData: {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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');

Expand Down
Loading