From f2e7d54982b598cf116485f053aaa6084fff3b4f Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:21:48 +0100 Subject: [PATCH 1/3] [NOTASK] fix refundAmount bug --- .../supporting/payment/services/transaction-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index d6972a33c0..77cebaad32 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -464,7 +464,7 @@ export class TransactionHelper implements OnModuleInit { expiryDate: Util.secondsAfter(Config.transactionRefundExpirySeconds), inputAmount: Util.roundReadable(inputAmount, amountType), inputAsset, - refundAmount, + refundAmount: refundAmount > refundEntity.refundAmount ? refundEntity.refundAmount : refundAmount, fee: { dfx: feeDfx, network: feeNetwork, From 3726d4a900ecd2e1ed051de5e67a946ae414635f Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:47:32 +0100 Subject: [PATCH 2/3] [NOTASK] Fix different currency refund bug --- .../process/services/buy-crypto.service.ts | 1 + .../history/controllers/transaction.controller.ts | 8 +++++++- src/subdomains/core/history/dto/refund-data.dto.ts | 4 ++++ .../core/history/dto/refund-internal.dto.ts | 1 + .../core/transaction/transaction-util.service.ts | 12 ++++++++++-- .../bank-tx/bank-tx-return/bank-tx-return.service.ts | 2 +- .../payment/services/transaction-helper.ts | 11 ++++++----- 7 files changed, 30 insertions(+), 9 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 006c9ea7f4..a9155f0c12 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 @@ -535,6 +535,7 @@ export class BuyCryptoService { TransactionUtilService.validateRefund(buyCrypto, { refundIban: chargebackIban, chargebackAmount, + chargebackAmountInInputAsset: dto.chargebackAmountInInputAsset, }); if ( diff --git a/src/subdomains/core/history/controllers/transaction.controller.ts b/src/subdomains/core/history/controllers/transaction.controller.ts index d8bcd0b5d1..1ef91dfaeb 100644 --- a/src/subdomains/core/history/controllers/transaction.controller.ts +++ b/src/subdomains/core/history/controllers/transaction.controller.ts @@ -525,7 +525,13 @@ export class TransactionController { const inputCurrency = await this.transactionHelper.getRefundActive(transaction.refundTargetEntity); if (!inputCurrency.refundEnabled) throw new BadRequestException(`Refund for ${inputCurrency.name} not allowed`); - const refundDto = { chargebackAmount: refundData.refundAmount, chargebackAllowedDateUser: new Date() }; + const refundDto = { + chargebackAmount: refundData.refundAmount, + chargebackAllowedDateUser: new Date(), + chargebackAmountInInputAsset: refundData.refundPrice + ? refundData.refundPrice.invert().convert(refundData.refundAmount) + : undefined, + }; if (!targetEntity) { if (!dto.creditorData) throw new BadRequestException('Creditor data is required for bank refunds'); diff --git a/src/subdomains/core/history/dto/refund-data.dto.ts b/src/subdomains/core/history/dto/refund-data.dto.ts index 214cd750c6..39548379f8 100644 --- a/src/subdomains/core/history/dto/refund-data.dto.ts +++ b/src/subdomains/core/history/dto/refund-data.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger import { ActiveDto } from 'src/shared/models/active'; import { AssetDto } from 'src/shared/models/asset/dto/asset.dto'; import { FiatDto } from 'src/shared/models/fiat/dto/fiat.dto'; +import { Price } from 'src/subdomains/supporting/pricing/domain/entities/price'; export class RefundFeeDto { @ApiProperty({ description: 'Network fee in refundAsset' }) @@ -59,6 +60,9 @@ export class RefundDataDto { @ApiProperty({ oneOf: [{ $ref: getSchemaPath(AssetDto) }, { $ref: getSchemaPath(FiatDto) }] }) refundAsset: ActiveDto; + @ApiPropertyOptional({ type: Price, description: 'Price refund asset in input asset' }) + refundPrice?: Price; + @ApiPropertyOptional({ description: 'IBAN for bank tx or blockchain address for crypto tx' }) refundTarget?: string; diff --git a/src/subdomains/core/history/dto/refund-internal.dto.ts b/src/subdomains/core/history/dto/refund-internal.dto.ts index 357bcc187e..de0b8ccae0 100644 --- a/src/subdomains/core/history/dto/refund-internal.dto.ts +++ b/src/subdomains/core/history/dto/refund-internal.dto.ts @@ -45,6 +45,7 @@ export class BankTxRefund extends BaseRefund { chargebackCurrency?: string; chargebackOutput?: FiatOutput; creditorData?: CreditorData; + chargebackAmountInInputAsset?: number; } export class CheckoutTxRefund extends BaseRefund { diff --git a/src/subdomains/core/transaction/transaction-util.service.ts b/src/subdomains/core/transaction/transaction-util.service.ts index 9fb2eed606..2581a7bc29 100644 --- a/src/subdomains/core/transaction/transaction-util.service.ts +++ b/src/subdomains/core/transaction/transaction-util.service.ts @@ -32,6 +32,7 @@ export type RefundValidation = { refundIban?: string; refundUser?: User; chargebackAmount?: number; + chargebackAmountInInputAsset?: number; }; @Injectable() @@ -77,16 +78,23 @@ export class TransactionUtilService { throw new BadRequestException('Transaction is already returned'); if (entity instanceof BankTxReturn) { - if (dto.chargebackAmount && dto.chargebackAmount > entity.bankTx.amount) + if ( + dto.chargebackAmount && + ((dto.chargebackAmount > entity.bankTx.amount && !dto.chargebackAmountInInputAsset) || + dto.chargebackAmountInInputAsset > entity.bankTx.amount) + ) throw new BadRequestException('You can not refund more than the input amount'); return; } if (![CheckStatus.FAIL, CheckStatus.PENDING].includes(entity.amlCheck) || entity.outputAmount) throw new BadRequestException('Only failed or pending transactions are refundable'); + + const inputAmount = entity instanceof BuyCrypto && entity.bankTx ? entity.bankTx.amount : entity.inputAmount; if ( dto.chargebackAmount && - dto.chargebackAmount > (entity instanceof BuyCrypto && entity.bankTx ? entity.bankTx.amount : entity.inputAmount) + ((dto.chargebackAmount > inputAmount && !dto.chargebackAmountInInputAsset) || + dto.chargebackAmountInInputAsset > inputAmount) ) throw new BadRequestException('You can not refund more than the input amount'); } 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..50715d78df 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 @@ -170,7 +170,6 @@ export class BankTxReturnService { where: { id: buyCryptoId }, relations: { transaction: { userData: true }, bankTx: true, chargebackOutput: true }, }); - if (!bankTxReturn) throw new NotFoundException('BankTxReturn not found'); return this.refundBankTx(bankTxReturn, { @@ -190,6 +189,7 @@ export class BankTxReturnService { TransactionUtilService.validateRefund(bankTxReturn, { refundIban: chargebackIban, chargebackAmount, + chargebackAmountInInputAsset: dto.chargebackAmountInInputAsset, }); if ( diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 77cebaad32..3c365be2ff 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -408,7 +408,7 @@ export class TransactionHelper implements OnModuleInit { ? await this.fiatService.getFiatByName('EUR') : inputCurrency; - const price = + const chfPrice = refundEntity.manualChfPrice ?? (await this.pricingService.getPrice(PriceCurrency.CHF, inputCurrency, PriceValidity.PREFER_VALID)); @@ -419,7 +419,7 @@ export class TransactionHelper implements OnModuleInit { const chargebackFee = await this.feeService.getChargebackFee({ from: inputCurrency, - txVolume: price.invert().convert(inputAmount), + txVolume: chfPrice.invert().convert(inputAmount), paymentMethodIn: refundEntity.paymentMethodIn, bankIn, specialCodes: [], @@ -427,16 +427,16 @@ export class TransactionHelper implements OnModuleInit { userData, }); - const dfxFeeAmount = inputAmount * chargebackFee.rate + price.convert(chargebackFee.fixed); + const dfxFeeAmount = inputAmount * chargebackFee.rate + chfPrice.convert(chargebackFee.fixed); - let networkFeeAmount = price.convert(chargebackFee.network); + let networkFeeAmount = chfPrice.convert(chargebackFee.network); if (isAsset(inputCurrency) && inputCurrency.blockchain === Blockchain.SOLANA) networkFeeAmount += await this.getSolanaRentExemptionFee(inputCurrency); const bankFeeAmount = refundEntity.paymentMethodIn === FiatPaymentMethod.BANK - ? price.convert( + ? chfPrice.convert( chargebackFee.bankRate * inputAmount + chargebackFee.bankFixed + refundEntity.chargebackBankFee * 1.01, ) : 0; // Bank fee buffer 1% @@ -465,6 +465,7 @@ export class TransactionHelper implements OnModuleInit { inputAmount: Util.roundReadable(inputAmount, amountType), inputAsset, refundAmount: refundAmount > refundEntity.refundAmount ? refundEntity.refundAmount : refundAmount, + refundPrice, fee: { dfx: feeDfx, network: feeNetwork, From eb3b7469c22dc8b1d82db1550301e36812f9533b Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 10 Feb 2026 22:38:59 +0100 Subject: [PATCH 3/3] [NOTASK] Refactoring --- .../core/history/controllers/transaction.controller.ts | 10 +++------- src/subdomains/core/history/dto/refund-data.dto.ts | 3 +-- .../supporting/payment/services/transaction-helper.ts | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/subdomains/core/history/controllers/transaction.controller.ts b/src/subdomains/core/history/controllers/transaction.controller.ts index 1ef91dfaeb..35cba584ca 100644 --- a/src/subdomains/core/history/controllers/transaction.controller.ts +++ b/src/subdomains/core/history/controllers/transaction.controller.ts @@ -525,13 +525,7 @@ export class TransactionController { const inputCurrency = await this.transactionHelper.getRefundActive(transaction.refundTargetEntity); if (!inputCurrency.refundEnabled) throw new BadRequestException(`Refund for ${inputCurrency.name} not allowed`); - const refundDto = { - chargebackAmount: refundData.refundAmount, - chargebackAllowedDateUser: new Date(), - chargebackAmountInInputAsset: refundData.refundPrice - ? refundData.refundPrice.invert().convert(refundData.refundAmount) - : undefined, - }; + const refundDto = { chargebackAmount: refundData.refundAmount, chargebackAllowedDateUser: new Date() }; if (!targetEntity) { if (!dto.creditorData) throw new BadRequestException('Creditor data is required for bank refunds'); @@ -549,6 +543,7 @@ export class TransactionController { refundIban: dto.refundTarget ?? refundData.refundTarget, chargebackCurrency, creditorData: dto.creditorData, + chargebackAmountInInputAsset: refundData.refundPrice.invert().convert(refundData.refundAmount), ...refundDto, }); } @@ -577,6 +572,7 @@ export class TransactionController { refundIban: dto.refundTarget ?? refundData.refundTarget, chargebackCurrency, creditorData: dto.creditorData, + chargebackAmountInInputAsset: refundData.refundPrice.invert().convert(refundData.refundAmount), ...refundDto, }); } diff --git a/src/subdomains/core/history/dto/refund-data.dto.ts b/src/subdomains/core/history/dto/refund-data.dto.ts index 39548379f8..93d2d91975 100644 --- a/src/subdomains/core/history/dto/refund-data.dto.ts +++ b/src/subdomains/core/history/dto/refund-data.dto.ts @@ -60,8 +60,7 @@ export class RefundDataDto { @ApiProperty({ oneOf: [{ $ref: getSchemaPath(AssetDto) }, { $ref: getSchemaPath(FiatDto) }] }) refundAsset: ActiveDto; - @ApiPropertyOptional({ type: Price, description: 'Price refund asset in input asset' }) - refundPrice?: Price; + refundPrice: Price; @ApiPropertyOptional({ description: 'IBAN for bank tx or blockchain address for crypto tx' }) refundTarget?: string; diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 3c365be2ff..1b9d34315f 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -464,7 +464,7 @@ export class TransactionHelper implements OnModuleInit { expiryDate: Util.secondsAfter(Config.transactionRefundExpirySeconds), inputAmount: Util.roundReadable(inputAmount, amountType), inputAsset, - refundAmount: refundAmount > refundEntity.refundAmount ? refundEntity.refundAmount : refundAmount, + refundAmount: Math.min(refundEntity.refundAmount, refundAmount), refundPrice, fee: { dfx: feeDfx,