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..35cba584ca 100644 --- a/src/subdomains/core/history/controllers/transaction.controller.ts +++ b/src/subdomains/core/history/controllers/transaction.controller.ts @@ -543,6 +543,7 @@ export class TransactionController { refundIban: dto.refundTarget ?? refundData.refundTarget, chargebackCurrency, creditorData: dto.creditorData, + chargebackAmountInInputAsset: refundData.refundPrice.invert().convert(refundData.refundAmount), ...refundDto, }); } @@ -571,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 214cd750c6..93d2d91975 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,8 @@ export class RefundDataDto { @ApiProperty({ oneOf: [{ $ref: getSchemaPath(AssetDto) }, { $ref: getSchemaPath(FiatDto) }] }) refundAsset: ActiveDto; + 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 d6972a33c0..1b9d34315f 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% @@ -464,7 +464,8 @@ export class TransactionHelper implements OnModuleInit { expiryDate: Util.secondsAfter(Config.transactionRefundExpirySeconds), inputAmount: Util.roundReadable(inputAmount, amountType), inputAsset, - refundAmount, + refundAmount: Math.min(refundEntity.refundAmount, refundAmount), + refundPrice, fee: { dfx: feeDfx, network: feeNetwork,