From 8d04a5eaf417acb077c428e9a86f9eca863ece41 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:30:49 +0200 Subject: [PATCH 01/10] [DEV-3957] partner fee --- src/subdomains/supporting/payment/dto/fee.dto.ts | 2 ++ .../supporting/payment/entities/fee.entity.ts | 1 + .../supporting/payment/services/fee.service.ts | 16 ++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/subdomains/supporting/payment/dto/fee.dto.ts b/src/subdomains/supporting/payment/dto/fee.dto.ts index d39f676cd9..1cf5c3c453 100644 --- a/src/subdomains/supporting/payment/dto/fee.dto.ts +++ b/src/subdomains/supporting/payment/dto/fee.dto.ts @@ -33,6 +33,8 @@ export class InternalBaseFeeDto extends BaseFeeDto { fees: Fee[]; bankRate: number; // bank fee rate bankFixed: number; // bank fixed fee + partnerRate: number; // partner fee rate + partnerFixed: number; // partner fixed rate } export class InternalFeeDto extends InternalBaseFeeDto { diff --git a/src/subdomains/supporting/payment/entities/fee.entity.ts b/src/subdomains/supporting/payment/entities/fee.entity.ts index 615a14615f..2a91f0fd44 100644 --- a/src/subdomains/supporting/payment/entities/fee.entity.ts +++ b/src/subdomains/supporting/payment/entities/fee.entity.ts @@ -15,6 +15,7 @@ export enum FeeType { BASE = 'Base', // Single use only, absolute base fee DISCOUNT = 'Discount', // Single use only, absolute discount RELATIVE_DISCOUNT = 'RelativeDiscount', // Single use only, relative discount + PARTNER = 'Partner', // Single use only, additive partner fee ADDITION = 'Addition', // Multiple use possible, additive fee CHARGEBACK = 'Chargeback', // Multiple use possible, additive fee CHARGEBACK_BANK = 'ChargebackBank', // Bank fee for chargebacks, multiple use possible, additive fee diff --git a/src/subdomains/supporting/payment/services/fee.service.ts b/src/subdomains/supporting/payment/services/fee.service.ts index 1975323bc7..e9856ea0f2 100644 --- a/src/subdomains/supporting/payment/services/fee.service.ts +++ b/src/subdomains/supporting/payment/services/fee.service.ts @@ -295,6 +295,12 @@ export class FeeService { (await this.getBlockchainFeeInChf(from, allowCachedBlockchainFee)) + (await this.getBlockchainFeeInChf(to, allowCachedBlockchainFee)); + // get partner fee + const partnerFee = Util.minObj( + fees.filter((fee) => fee.type === FeeType.PARTNER), + 'rate', + ); + // get min special fee const specialFee = Util.minObj( fees.filter((fee) => fee.type === FeeType.SPECIAL), @@ -308,6 +314,8 @@ export class FeeService { fixed: specialFee.fixed ?? 0, bankRate: 0, bankFixed: 0, + partnerRate: partnerFee?.rate ?? 0, + partnerFixed: partnerFee?.fixed ?? 0, payoutRefBonus: specialFee.payoutRefBonus, network: Math.min(specialFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -325,6 +333,8 @@ export class FeeService { fixed: customFee.fixed ?? 0, bankRate: 0, bankFixed: 0, + partnerRate: partnerFee?.rate ?? 0, + partnerFixed: partnerFee?.fixed ?? 0, payoutRefBonus: customFee.payoutRefBonus, network: Math.min(customFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -363,6 +373,8 @@ export class FeeService { fixed: baseFee.fixed, bankRate: combinedBankFeeRate, bankFixed: combinedBankFixedFee, + partnerRate: partnerFee?.rate ?? 0, + partnerFixed: partnerFee?.fixed ?? 0, payoutRefBonus: true, network: Math.min(baseFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -374,6 +386,8 @@ export class FeeService { fixed: Math.max(baseFee.fixed + combinedExtraFixedFee, 0), bankRate: combinedBankFeeRate, bankFixed: combinedBankFixedFee, + partnerRate: partnerFee?.rate ?? 0, + partnerFixed: partnerFee?.fixed ?? 0, payoutRefBonus: baseFee.payoutRefBonus && (discountFee?.payoutRefBonus ?? true) && @@ -417,6 +431,8 @@ export class FeeService { fixed: combinedChargebackFixedFee ?? 0, bankRate: combinedBankFeeRate, bankFixed: combinedBankFixedFee ?? 0, + partnerRate: 0, + partnerFixed: 0, network: Math.min(chargebackMinFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; } From a420c4c5de77848447e2420daea0b36622a38ac7 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:25:27 +0200 Subject: [PATCH 02/10] [DEV-3957] add partnerFeeAmount --- .../process/entities/buy-crypto.entity.ts | 4 ++++ .../history/mappers/transaction-dto.mapper.ts | 4 ++++ .../sell-crypto/process/buy-fiat.entity.ts | 4 ++++ .../supporting/payment/dto/fee.dto.ts | 3 +++ .../dto/transaction-helper/tx-spec.dto.ts | 1 + .../payment/services/transaction-helper.ts | 22 ++++++++++++++----- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts index 2e6fe4a68c..a45f969b40 100644 --- a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts +++ b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts @@ -147,6 +147,9 @@ export class BuyCrypto extends IEntity { @Column({ type: 'float', nullable: true }) bankFeeAmount?: number; //inputReferenceAsset + @Column({ type: 'float', nullable: true }) + partnerFeeAmount?: number; //inputReferenceAsset + @Column({ type: 'float', nullable: true }) percentFeeAmount?: number; //inputReferenceAsset @@ -480,6 +483,7 @@ export class BuyCrypto extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, + partnerFeeAmount: fee.partner, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/core/history/mappers/transaction-dto.mapper.ts b/src/subdomains/core/history/mappers/transaction-dto.mapper.ts index 7ae70f614a..704be6b6e8 100644 --- a/src/subdomains/core/history/mappers/transaction-dto.mapper.ts +++ b/src/subdomains/core/history/mappers/transaction-dto.mapper.ts @@ -345,6 +345,10 @@ export class TransactionDtoMapper { feeAmountType(entity.inputAssetEntity), ) : null, + partner: + entity.partnerFeeAmount != null + ? Util.roundReadable(entity.partnerFeeAmount * referencePrice, feeAmountType(entity.inputAssetEntity)) + : null, total: entity.totalFeeAmount != null ? Util.roundReadable(entity.totalFeeAmount * referencePrice, feeAmountType(entity.inputAssetEntity)) diff --git a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts index cc7f196aff..2cc46165c0 100644 --- a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts +++ b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts @@ -114,6 +114,9 @@ export class BuyFiat extends IEntity { @Column({ type: 'float', nullable: true }) bankFeeAmount?: number; //inputAsset + @Column({ type: 'float', nullable: true }) + partnerFeeAmount?: number; //inputAsset + @Column({ type: 'float', nullable: true }) percentFeeAmount?: number; //inputAsset @@ -301,6 +304,7 @@ export class BuyFiat extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, + partnerFeeAmount: fee.partner, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/supporting/payment/dto/fee.dto.ts b/src/subdomains/supporting/payment/dto/fee.dto.ts index 1cf5c3c453..d810e4af35 100644 --- a/src/subdomains/supporting/payment/dto/fee.dto.ts +++ b/src/subdomains/supporting/payment/dto/fee.dto.ts @@ -19,6 +19,9 @@ export class FeeDto extends BaseFeeDto { @ApiProperty({ description: 'DFX fee amount' }) dfx: number; + @ApiProperty({ description: 'Partner fee amount' }) + partner: number; + @ApiProperty({ description: 'Bank fee amount' }) bank: number; // final bank fee addition diff --git a/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts b/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts index 43f09232c2..5384c04bcf 100644 --- a/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts +++ b/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts @@ -11,6 +11,7 @@ export interface TxSpec { fee: { min: number; fixed: number; + partnerFixed: number; bankFixed: number; network: number; networkStart: number; diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index c8fe2173bc..4d6dd4fa69 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -223,15 +223,17 @@ export class TransactionHelper implements OnModuleInit { network: fee.network, networkStart: networkStartFee, bankFixed: fee.bankFixed, + partnerFixed: fee.partnerFixed, }, volume: { min: minSpecs.minVolume, max: Number.MAX_VALUE }, }; const sourceSpecs = await this.getSourceSpecs(fromReference, specs, PriceValidity.VALID_ONLY); - const { dfx, bank, total } = this.calculateTotalFee( + const { dfx, partner, bank, total } = this.calculateTotalFee( inputReferenceAmount, fee.rate, + fee.partnerRate, fee.bankRate, sourceSpecs, from, @@ -242,6 +244,7 @@ export class TransactionHelper implements OnModuleInit { ...sourceSpecs.fee, total, dfx, + partner, bank, }; } @@ -313,6 +316,7 @@ export class TransactionHelper implements OnModuleInit { min: specs.minFee, networkStart: networkStartFee, bankFixed: fee.bankFixed, + partnerFixed: fee.partnerFixed, }, volume: { min: specs.minVolume, @@ -327,6 +331,7 @@ export class TransactionHelper implements OnModuleInit { sourceAmount, targetAmount, fee.rate, + fee.partnerRate, fee.bankRate, sourceSpecs, targetSpecs, @@ -640,6 +645,7 @@ export class TransactionHelper implements OnModuleInit { inputAmount: number | undefined, outputAmount: number | undefined, feeRate: number, + partnerRate: number, bankFeeRate: number, sourceSpecs: TxSpec, targetSpecs: TxSpec, @@ -651,11 +657,12 @@ export class TransactionHelper implements OnModuleInit { const outputAmountSource = outputAmount && price.invert().convert(outputAmount); const sourceAmount = inputAmount ?? this.getInputAmount(outputAmountSource, feeRate, bankFeeRate, sourceSpecs); - const sourceFees = this.calculateTotalFee(sourceAmount, feeRate, bankFeeRate, sourceSpecs, from); + const sourceFees = this.calculateTotalFee(sourceAmount, feeRate, partnerRate, bankFeeRate, sourceSpecs, from); const targetAmount = outputAmount ?? price.convert(Math.max(inputAmount - sourceFees.total, 0)); const targetFees = { dfx: this.convertFee(sourceFees.dfx, price, to), + partner: this.convertFee(sourceFees.partner, price, to), total: this.convertFee(sourceFees.total, price, to), bank: this.convertFee(sourceFees.bank, price, to), }; @@ -715,6 +722,7 @@ export class TransactionHelper implements OnModuleInit { fee: { min: this.convertFee(fee.min, price, from), fixed: this.convertFee(fee.fixed, price, from), + partnerFixed: this.convertFee(fee.partnerFixed, price, from), bankFixed: this.convertFee(fee.bankFixed, price, from), network: this.convertFee(fee.network, price, from), networkStart: fee.networkStart != null ? this.convertFee(fee.networkStart, price, from) : undefined, @@ -733,6 +741,7 @@ export class TransactionHelper implements OnModuleInit { fee: { min: this.convertFee(fee.min, price, to), fixed: this.convertFee(fee.fixed, price, to), + partnerFixed: this.convertFee(fee.partnerFixed, price, to), bankFixed: this.convertFee(fee.bankFixed, price, to), network: this.convertFee(fee.network, price, to), networkStart: fee.networkStart != null ? this.convertFee(fee.networkStart, price, to) : undefined, @@ -747,16 +756,19 @@ export class TransactionHelper implements OnModuleInit { private calculateTotalFee( amount: number, rate: number, + partnerRate: number, bankRate: number, - { fee: { fixed, min, network, networkStart, bankFixed } }: TxSpec, + { fee: { fixed, min, network, networkStart, bankFixed, partnerFixed } }: TxSpec, roundingActive: Active, - ): { dfx: number; bank: number; total: number } { + ): { dfx: number; partner: number; bank: number; total: number } { const bank = amount * bankRate + bankFixed; const dfx = Math.max(amount * rate + fixed, min); - const total = dfx + bank + network + (networkStart ?? 0); + const partner = amount * partnerRate + partnerFixed; + const total = dfx + partner + bank + network + (networkStart ?? 0); return { dfx: Util.roundReadable(dfx, feeAmountType(roundingActive)), + partner: Util.roundReadable(partner, feeAmountType(roundingActive)), bank: Util.roundReadable(bank, feeAmountType(roundingActive)), total: Util.roundReadable(total, feeAmountType(roundingActive)), }; From bd5c211dcb48ac083c4ac14d51e72230d9a6d2fe Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:29:32 +0200 Subject: [PATCH 03/10] [DEV-3957] find partnerFee in default --- src/subdomains/supporting/payment/services/fee.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/subdomains/supporting/payment/services/fee.service.ts b/src/subdomains/supporting/payment/services/fee.service.ts index 7635e17ce6..5cca42a8a1 100644 --- a/src/subdomains/supporting/payment/services/fee.service.ts +++ b/src/subdomains/supporting/payment/services/fee.service.ts @@ -500,6 +500,7 @@ export class FeeService { FeeType.CHARGEBACK_BANK, FeeType.BANK, FeeType.SPECIAL, + FeeType.PARTNER, ].includes(f.type) && !f.specialCode) || discountFeeIds.includes(f.id) || From 0920502f2c1497002a92ba27d75b34689d11c4f2 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:51:35 +0100 Subject: [PATCH 04/10] [DEV-3957] fix dev build --- src/subdomains/supporting/payment/services/fee.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/subdomains/supporting/payment/services/fee.service.ts b/src/subdomains/supporting/payment/services/fee.service.ts index 968279fa8e..479b59f856 100644 --- a/src/subdomains/supporting/payment/services/fee.service.ts +++ b/src/subdomains/supporting/payment/services/fee.service.ts @@ -446,6 +446,8 @@ export class FeeService { fixed: specialFee.fixed ?? 0, bankRate: 0, bankFixed: 0, + partnerRate: 0, + partnerFixed: 0, network: Math.min(specialFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -462,6 +464,8 @@ export class FeeService { fixed: customFee.fixed ?? 0, bankRate: 0, bankFixed: 0, + partnerRate: 0, + partnerFixed: 0, network: Math.min(customFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; From 8a6fbe92b439008dac0a4176535fa6d8c44bcbef Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:07:35 +0100 Subject: [PATCH 05/10] [DEV-3957] Renaming to platform fee --- .../core/buy-crypto/process/entities/buy-crypto.entity.ts | 2 +- src/subdomains/core/history/mappers/transaction-dto.mapper.ts | 2 +- src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts | 2 +- src/subdomains/supporting/payment/dto/fee.dto.ts | 4 ++-- .../supporting/payment/services/transaction-helper.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts index be66aee7e0..2f04590af1 100644 --- a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts +++ b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts @@ -483,7 +483,7 @@ export class BuyCrypto extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, - partnerFeeAmount: fee.partner, + partnerFeeAmount: fee.platform, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/core/history/mappers/transaction-dto.mapper.ts b/src/subdomains/core/history/mappers/transaction-dto.mapper.ts index 704be6b6e8..0ef9b4fa18 100644 --- a/src/subdomains/core/history/mappers/transaction-dto.mapper.ts +++ b/src/subdomains/core/history/mappers/transaction-dto.mapper.ts @@ -345,7 +345,7 @@ export class TransactionDtoMapper { feeAmountType(entity.inputAssetEntity), ) : null, - partner: + platform: entity.partnerFeeAmount != null ? Util.roundReadable(entity.partnerFeeAmount * referencePrice, feeAmountType(entity.inputAssetEntity)) : null, diff --git a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts index 2cc46165c0..d890186f41 100644 --- a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts +++ b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts @@ -304,7 +304,7 @@ export class BuyFiat extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, - partnerFeeAmount: fee.partner, + partnerFeeAmount: fee.platform, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/supporting/payment/dto/fee.dto.ts b/src/subdomains/supporting/payment/dto/fee.dto.ts index d810e4af35..3677add42f 100644 --- a/src/subdomains/supporting/payment/dto/fee.dto.ts +++ b/src/subdomains/supporting/payment/dto/fee.dto.ts @@ -19,8 +19,8 @@ export class FeeDto extends BaseFeeDto { @ApiProperty({ description: 'DFX fee amount' }) dfx: number; - @ApiProperty({ description: 'Partner fee amount' }) - partner: number; + @ApiProperty({ description: 'Platform fee amount' }) + platform: number; @ApiProperty({ description: 'Bank fee amount' }) bank: number; // final bank fee addition diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 5aee54b243..d32fce3860 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -244,7 +244,7 @@ export class TransactionHelper implements OnModuleInit { ...sourceSpecs.fee, total, dfx, - partner, + platform: partner, bank, }; } From 03039fca16bfecd8fdec77705817e74febfb9c38 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:10:42 +0100 Subject: [PATCH 06/10] [DEV-3957] fix build --- .../supporting/payment/services/transaction-helper.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index d32fce3860..9ce668dd13 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -230,7 +230,7 @@ export class TransactionHelper implements OnModuleInit { const sourceSpecs = await this.getSourceSpecs(fromReference, specs, PriceValidity.VALID_ONLY); - const { dfx, partner, bank, total } = this.calculateTotalFee( + const { dfx, platform, bank, total } = this.calculateTotalFee( inputReferenceAmount, fee.rate, fee.partnerRate, @@ -244,7 +244,7 @@ export class TransactionHelper implements OnModuleInit { ...sourceSpecs.fee, total, dfx, - platform: partner, + platform, bank, }; } @@ -665,7 +665,7 @@ export class TransactionHelper implements OnModuleInit { const targetAmount = outputAmount ?? price.convert(Math.max(inputAmount - sourceFees.total, 0)); const targetFees = { dfx: this.convertFee(sourceFees.dfx, price, to), - partner: this.convertFee(sourceFees.partner, price, to), + platform: this.convertFee(sourceFees.platform, price, to), total: this.convertFee(sourceFees.total, price, to), bank: this.convertFee(sourceFees.bank, price, to), }; @@ -763,7 +763,7 @@ export class TransactionHelper implements OnModuleInit { bankRate: number, { fee: { fixed, min, network, networkStart, bankFixed, partnerFixed } }: TxSpec, roundingActive: Active, - ): { dfx: number; partner: number; bank: number; total: number } { + ): { dfx: number; platform: number; bank: number; total: number } { const bank = amount * bankRate + bankFixed; const dfx = Math.max(amount * rate + fixed, min); const partner = amount * partnerRate + partnerFixed; @@ -771,7 +771,7 @@ export class TransactionHelper implements OnModuleInit { return { dfx: Util.roundReadable(dfx, feeAmountType(roundingActive)), - partner: Util.roundReadable(partner, feeAmountType(roundingActive)), + platform: Util.roundReadable(partner, feeAmountType(roundingActive)), bank: Util.roundReadable(bank, feeAmountType(roundingActive)), total: Util.roundReadable(total, feeAmountType(roundingActive)), }; From ebefd9a8070e931a590a729ba2c7cfb795f07286 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:13:37 +0100 Subject: [PATCH 07/10] [DEV-3957] add migration --- ...24-AddBuyCryptoBuyFiatPlatformFeeAmount.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 migration/1761754328324-AddBuyCryptoBuyFiatPlatformFeeAmount.js diff --git a/migration/1761754328324-AddBuyCryptoBuyFiatPlatformFeeAmount.js b/migration/1761754328324-AddBuyCryptoBuyFiatPlatformFeeAmount.js new file mode 100644 index 0000000000..5820cabaa0 --- /dev/null +++ b/migration/1761754328324-AddBuyCryptoBuyFiatPlatformFeeAmount.js @@ -0,0 +1,21 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class AddBuyCryptoBuyFiatPlatformFeeAmount1761754328324 { + name = 'AddBuyCryptoBuyFiatPlatformFeeAmount1761754328324' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "buy_fiat" ADD "partnerFeeAmount" float`); + await queryRunner.query(`ALTER TABLE "buy_crypto" ADD "partnerFeeAmount" float`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "buy_crypto" DROP COLUMN "partnerFeeAmount"`); + await queryRunner.query(`ALTER TABLE "buy_fiat" DROP COLUMN "partnerFeeAmount"`); + } +} From 80325029218c9cb821f5c01a91ff8c8a073bb884 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:30:42 +0100 Subject: [PATCH 08/10] [DEV-3957] Refactoring (#2534) --- .../process/entities/buy-crypto.entity.ts | 6 +- .../buy-crypto/routes/buy/buy.controller.ts | 2 +- .../buy-crypto/routes/swap/swap.controller.ts | 2 +- .../__tests__/transaction-helper.spec.ts | 18 +-- .../sell-crypto/process/buy-fiat.entity.ts | 6 +- .../core/sell-crypto/route/sell.controller.ts | 2 +- .../generic/user/models/user/user.service.ts | 4 +- .../payment/__mocks__/fee.dto.mock.ts | 35 +++++ .../__mocks__/internal-fee.dto.mock.ts | 37 ----- .../supporting/payment/dto/fee.dto.ts | 67 ++++++--- .../dto/transaction-helper/tx-spec.dto.ts | 8 +- .../payment/services/fee.service.ts | 86 +++++------- .../payment/services/transaction-helper.ts | 130 ++++++++---------- 13 files changed, 202 insertions(+), 201 deletions(-) create mode 100644 src/subdomains/supporting/payment/__mocks__/fee.dto.mock.ts delete mode 100644 src/subdomains/supporting/payment/__mocks__/internal-fee.dto.mock.ts diff --git a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts index 2f04590af1..10a20fcd59 100644 --- a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts +++ b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts @@ -21,7 +21,7 @@ import { FiatOutput } from 'src/subdomains/supporting/fiat-output/fiat-output.en import { CheckoutTx } from 'src/subdomains/supporting/fiat-payin/entities/checkout-tx.entity'; import { MailTranslationKey } from 'src/subdomains/supporting/notification/factories/mail.factory'; import { CryptoInput } from 'src/subdomains/supporting/payin/entities/crypto-input.entity'; -import { FeeDto, InternalFeeDto } from 'src/subdomains/supporting/payment/dto/fee.dto'; +import { InternalFeeDto } from 'src/subdomains/supporting/payment/dto/fee.dto'; import { CryptoPaymentMethod, FiatPaymentMethod, @@ -463,7 +463,7 @@ export class BuyCrypto extends IEntity { } setFeeAndFiatReference( - fee: InternalFeeDto & FeeDto, + fee: InternalFeeDto, minFeeAmountFiat: number, totalFeeAmountChf: number, ): UpdateResult { @@ -483,7 +483,7 @@ export class BuyCrypto extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, - partnerFeeAmount: fee.platform, + partnerFeeAmount: fee.partner, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts b/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts index c1b9567c3d..bfe7e81d58 100644 --- a/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts +++ b/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts @@ -245,7 +245,7 @@ export class BuyController { annualVolume: buy.annualVolume, bankUsage: buy.active ? buy.bankUsage : undefined, asset: AssetDtoMapper.toDto(buy.asset), - fee: Util.round(fee.rate * 100, Config.defaultPercentageDecimal), + fee: Util.round(fee.dfx.rate * 100, Config.defaultPercentageDecimal), minDeposits: [minDeposit], minFee: { amount: fee.network, asset: 'CHF' }, }; diff --git a/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts b/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts index bacf4dce66..ba60a2c977 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts @@ -217,7 +217,7 @@ export class SwapController { deposit: swap.active ? DepositDtoMapper.entityToDto(swap.deposit) : undefined, asset: AssetDtoMapper.toDto(swap.asset), blockchain: swap.deposit.blockchainList[0], - fee: Util.round(fee.rate * 100, Config.defaultPercentageDecimal), + fee: Util.round(fee.dfx.rate * 100, Config.defaultPercentageDecimal), minDeposits: [minDeposit], minFee: { amount: fee.network, asset: 'CHF' }, }; diff --git a/src/subdomains/core/history/__tests__/transaction-helper.spec.ts b/src/subdomains/core/history/__tests__/transaction-helper.spec.ts index 0548b46a2d..7f75b78059 100644 --- a/src/subdomains/core/history/__tests__/transaction-helper.spec.ts +++ b/src/subdomains/core/history/__tests__/transaction-helper.spec.ts @@ -16,9 +16,9 @@ import { CardBankName, IbanBankName } from 'src/subdomains/supporting/bank/bank/ import { createDefaultCheckoutTx } from 'src/subdomains/supporting/fiat-payin/__mocks__/checkout-tx.entity.mock'; import { createDefaultCryptoInput } from 'src/subdomains/supporting/payin/entities/__mocks__/crypto-input.entity.mock'; import { - createCustomInternalChargebackFeeDto, - createInternalChargebackFeeDto, -} from 'src/subdomains/supporting/payment/__mocks__/internal-fee.dto.mock'; + createChargebackFeeInfo, + createCustomChargebackFeeInfo, +} from 'src/subdomains/supporting/payment/__mocks__/fee.dto.mock'; import { createCustomTransaction } from 'src/subdomains/supporting/payment/__mocks__/transaction.entity.mock'; import { TransactionSpecificationRepository } from 'src/subdomains/supporting/payment/repositories/transaction-specification.repository'; import { FeeService } from 'src/subdomains/supporting/payment/services/fee.service'; @@ -96,7 +96,7 @@ describe('TransactionHelper', () => { }); jest.spyOn(fiatService, 'getFiatByName').mockResolvedValue(createCustomFiat({ name: 'CHF' })); - jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createInternalChargebackFeeDto()); + jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createChargebackFeeInfo()); jest .spyOn(pricingService, 'getPrice') .mockResolvedValue(createCustomPrice({ source: 'CHF', target: 'CHF', price: 1 })); @@ -126,7 +126,7 @@ describe('TransactionHelper', () => { jest.spyOn(feeService, 'getBlockchainFee').mockResolvedValue(0.01); jest.spyOn(fiatService, 'getFiatByName').mockResolvedValue(createDefaultFiat()); - jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createInternalChargebackFeeDto()); + jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createChargebackFeeInfo()); jest.spyOn(pricingService, 'getPrice').mockResolvedValue(createCustomPrice({ price: 1 })); await expect( @@ -154,9 +154,7 @@ describe('TransactionHelper', () => { jest.spyOn(feeService, 'getBlockchainFee').mockResolvedValue(0.01); jest.spyOn(fiatService, 'getFiatByName').mockResolvedValue(createDefaultFiat()); - jest - .spyOn(feeService, 'getChargebackFee') - .mockResolvedValue(createCustomInternalChargebackFeeDto({ network: 0.01 })); + jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createCustomChargebackFeeInfo({ network: 0.01 })); jest.spyOn(pricingService, 'getPrice').mockResolvedValue(createCustomPrice({ price: 1 })); await expect( @@ -184,9 +182,7 @@ describe('TransactionHelper', () => { jest.spyOn(feeService, 'getBlockchainFee').mockResolvedValue(0.01); jest.spyOn(fiatService, 'getFiatByName').mockResolvedValue(createDefaultFiat()); - jest - .spyOn(feeService, 'getChargebackFee') - .mockResolvedValue(createCustomInternalChargebackFeeDto({ network: 0.01 })); + jest.spyOn(feeService, 'getChargebackFee').mockResolvedValue(createCustomChargebackFeeInfo({ network: 0.01 })); jest.spyOn(pricingService, 'getPrice').mockResolvedValue(createCustomPrice({ price: 1 })); await expect( diff --git a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts index d890186f41..8f9cd16b50 100644 --- a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts +++ b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts @@ -13,7 +13,7 @@ import { Wallet } from 'src/subdomains/generic/user/models/wallet/wallet.entity' import { BankTx } from 'src/subdomains/supporting/bank-tx/bank-tx/entities/bank-tx.entity'; import { MailTranslationKey } from 'src/subdomains/supporting/notification/factories/mail.factory'; import { CryptoInput } from 'src/subdomains/supporting/payin/entities/crypto-input.entity'; -import { FeeDto, InternalFeeDto } from 'src/subdomains/supporting/payment/dto/fee.dto'; +import { InternalFeeDto } from 'src/subdomains/supporting/payment/dto/fee.dto'; import { CryptoPaymentMethod, FiatPaymentMethod, @@ -284,7 +284,7 @@ export class BuyFiat extends IEntity { } setFeeAndFiatReference( - fee: InternalFeeDto & FeeDto, + fee: InternalFeeDto, minFeeAmountFiat: number, totalFeeAmountChf: number, ): UpdateResult { @@ -304,7 +304,7 @@ export class BuyFiat extends IEntity { totalFeeAmountChf, blockchainFee: fee.network, bankFeeAmount: fee.bank, - partnerFeeAmount: fee.platform, + partnerFeeAmount: fee.partner, inputReferenceAmountMinusFee, usedRef, refProvision, diff --git a/src/subdomains/core/sell-crypto/route/sell.controller.ts b/src/subdomains/core/sell-crypto/route/sell.controller.ts index c7066b6906..cc1195a9e7 100644 --- a/src/subdomains/core/sell-crypto/route/sell.controller.ts +++ b/src/subdomains/core/sell-crypto/route/sell.controller.ts @@ -217,7 +217,7 @@ export class SellController { fiat: FiatDtoMapper.toDto(sell.fiat), currency: FiatDtoMapper.toDto(sell.fiat), deposit: sell.active ? DepositDtoMapper.entityToDto(sell.deposit) : undefined, - fee: Util.round(fee.rate * 100, Config.defaultPercentageDecimal), + fee: Util.round(fee.dfx.rate * 100, Config.defaultPercentageDecimal), blockchain: sell.deposit.blockchainList[0], minFee: { amount: fee.network, asset: 'CHF' }, minDeposits: [minDeposit], diff --git a/src/subdomains/generic/user/models/user/user.service.ts b/src/subdomains/generic/user/models/user/user.service.ts index fc56adb071..1c3489e285 100644 --- a/src/subdomains/generic/user/models/user/user.service.ts +++ b/src/subdomains/generic/user/models/user/user.service.ts @@ -24,7 +24,7 @@ import { HistoryFilter, HistoryFilterKey } from 'src/subdomains/core/history/dto import { KycInputDataDto } from 'src/subdomains/generic/kyc/dto/input/kyc-data.dto'; import { UserDataService } from 'src/subdomains/generic/user/models/user-data/user-data.service'; import { CardBankName, IbanBankName } from 'src/subdomains/supporting/bank/bank/dto/bank.dto'; -import { InternalFeeDto } from 'src/subdomains/supporting/payment/dto/fee.dto'; +import { FeeInfo } from 'src/subdomains/supporting/payment/dto/fee.dto'; import { PaymentMethod } from 'src/subdomains/supporting/payment/dto/payment-method.enum'; import { FeeService } from 'src/subdomains/supporting/payment/services/fee.service'; import { Between, FindOptionsRelations, Not } from 'typeorm'; @@ -431,7 +431,7 @@ export class UserService { bankOut: CardBankName | IbanBankName, from: Active, to: Active, - ): Promise { + ): Promise { const user = await this.getUser(userId, { userData: true }); if (!user) throw new NotFoundException('User not found'); diff --git a/src/subdomains/supporting/payment/__mocks__/fee.dto.mock.ts b/src/subdomains/supporting/payment/__mocks__/fee.dto.mock.ts new file mode 100644 index 0000000000..25201d62a8 --- /dev/null +++ b/src/subdomains/supporting/payment/__mocks__/fee.dto.mock.ts @@ -0,0 +1,35 @@ +import { FeeInfo } from '../dto/fee.dto'; +import { createDefaultFee } from './fee.entity.mock'; + +const defaultFeeInfo: FeeInfo = { + fees: [createDefaultFee()], + dfx: { fixed: 0, rate: 0.01 }, + bank: { fixed: 0, rate: 0 }, + partner: { fixed: 0, rate: 0 }, + network: 0, + payoutRefBonus: false, +}; + +const defaultChargebackFeeInfo: FeeInfo = { + fees: [createDefaultFee()], + dfx: { fixed: 0, rate: 0 }, + bank: { fixed: 0, rate: 0 }, + partner: { fixed: 0, rate: 0 }, + network: 0, + payoutRefBonus: false, +}; + +export function createFeeInfo(): FeeInfo { + return createCustomFeeInfo({}); +} + +export function createCustomFeeInfo(customValues: Partial): FeeInfo { + return { ...defaultFeeInfo, ...customValues }; +} +export function createChargebackFeeInfo(): FeeInfo { + return createCustomChargebackFeeInfo({}); +} + +export function createCustomChargebackFeeInfo(customValues: Partial): FeeInfo { + return { ...defaultChargebackFeeInfo, ...customValues }; +} diff --git a/src/subdomains/supporting/payment/__mocks__/internal-fee.dto.mock.ts b/src/subdomains/supporting/payment/__mocks__/internal-fee.dto.mock.ts deleted file mode 100644 index d8c1612b2b..0000000000 --- a/src/subdomains/supporting/payment/__mocks__/internal-fee.dto.mock.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { InternalChargebackFeeDto, InternalFeeDto } from '../dto/fee.dto'; -import { createDefaultFee } from './fee.entity.mock'; - -const defaultInternalFeeDto: Partial = { - fees: [createDefaultFee()], - bankFixed: 0, - bankRate: 0, - fixed: 0, - network: 0, - payoutRefBonus: false, - rate: 0.01, -}; - -const defaultInternalChargebackFeeDto: Partial = { - fees: [createDefaultFee()], - fixed: 0, - network: 0, - rate: 0, - bankFixed: 0, - bankRate: 0, -}; - -export function createInternalFeeDto(): InternalFeeDto { - return createCustomInternalFeeDto({}); -} - -export function createCustomInternalFeeDto(customValues: Partial): InternalFeeDto { - return Object.assign(new InternalFeeDto(), { ...defaultInternalFeeDto, ...customValues }); -} - -export function createInternalChargebackFeeDto(): InternalChargebackFeeDto { - return createCustomInternalChargebackFeeDto({}); -} - -export function createCustomInternalChargebackFeeDto(customValues: Partial): InternalChargebackFeeDto { - return Object.assign(new InternalChargebackFeeDto(), { ...defaultInternalChargebackFeeDto, ...customValues }); -} diff --git a/src/subdomains/supporting/payment/dto/fee.dto.ts b/src/subdomains/supporting/payment/dto/fee.dto.ts index 3677add42f..1120ccf19b 100644 --- a/src/subdomains/supporting/payment/dto/fee.dto.ts +++ b/src/subdomains/supporting/payment/dto/fee.dto.ts @@ -1,23 +1,25 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Fee } from '../entities/fee.entity'; +import { TxSpec } from './transaction-helper/tx-spec.dto'; + +export class FeeDto { + @ApiProperty({ description: 'Minimum fee amount' }) + min: number; -export class BaseFeeDto { @ApiProperty({ description: 'Fee rate' }) rate: number; // final fee rate @ApiProperty({ description: 'Fixed fee amount' }) fixed: number; // final fixed fee + @ApiProperty({ description: 'DFX fee amount' }) + dfx: number; + @ApiProperty({ description: 'Network fee amount' }) network: number; // final network fee -} -export class FeeDto extends BaseFeeDto { - @ApiProperty({ description: 'Minimum fee amount' }) - min: number; - - @ApiProperty({ description: 'DFX fee amount' }) - dfx: number; + @ApiPropertyOptional({ description: 'Network start fee' }) + networkStart?: number; @ApiProperty({ description: 'Platform fee amount' }) platform: number; @@ -27,21 +29,52 @@ export class FeeDto extends BaseFeeDto { @ApiProperty({ description: 'Total fee amount (DFX + bank + network fee)' }) total: number; +} - @ApiPropertyOptional({ description: 'Network start fee' }) +export interface InternalFeeDto { + fees: Fee[]; + min: number; + rate: number; + fixed: number; + bank: number; + partner: number; + network: number; networkStart?: number; + total: number; + payoutRefBonus: boolean; } -export class InternalBaseFeeDto extends BaseFeeDto { - fees: Fee[]; - bankRate: number; // bank fee rate - bankFixed: number; // bank fixed fee - partnerRate: number; // partner fee rate - partnerFixed: number; // partner fixed rate +export interface FeeAmountsDto { + dfx: number; + bank: number; + partner: number; + total: number; } -export class InternalFeeDto extends InternalBaseFeeDto { +export interface FeeInfo { + fees: Fee[]; + dfx: FeeSpec; + bank: FeeSpec; + partner: FeeSpec; + network: number; payoutRefBonus: boolean; } -export class InternalChargebackFeeDto extends InternalBaseFeeDto {} +export interface FeeSpec { + rate: number; + fixed: number; +} + +export function toFeeDto(amounts: FeeAmountsDto, spec: TxSpec): FeeDto { + return Object.assign(new FeeDto(), { + min: spec.fee.min, + rate: spec.fee.dfx.rate, + fixed: spec.fee.dfx.fixed, + network: spec.fee.network, + networkStart: spec.fee.networkStart, + dfx: amounts.dfx, + platform: amounts.partner, + bank: amounts.bank, + total: amounts.total, + }); +} diff --git a/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts b/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts index 5384c04bcf..a78a76594d 100644 --- a/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts +++ b/src/subdomains/supporting/payment/dto/transaction-helper/tx-spec.dto.ts @@ -1,3 +1,5 @@ +import { FeeSpec } from '../fee.dto'; + export interface TxMinSpec { minVolume: number; minFee: number; @@ -10,9 +12,9 @@ export interface TxSpec { }; fee: { min: number; - fixed: number; - partnerFixed: number; - bankFixed: number; + dfx: FeeSpec; + partner: FeeSpec; + bank: FeeSpec; network: number; networkStart: number; }; diff --git a/src/subdomains/supporting/payment/services/fee.service.ts b/src/subdomains/supporting/payment/services/fee.service.ts index 479b59f856..339538067d 100644 --- a/src/subdomains/supporting/payment/services/fee.service.ts +++ b/src/subdomains/supporting/payment/services/fee.service.ts @@ -22,7 +22,7 @@ import { BankService } from '../../bank/bank/bank.service'; import { CardBankName, IbanBankName } from '../../bank/bank/dto/bank.dto'; import { PayoutService } from '../../payout/services/payout.service'; import { PriceCurrency, PriceValidity, PricingService } from '../../pricing/services/pricing.service'; -import { InternalChargebackFeeDto, InternalFeeDto } from '../dto/fee.dto'; +import { FeeInfo } from '../dto/fee.dto'; import { CreateFeeDto } from '../dto/input/create-fee.dto'; import { FiatPaymentMethod, PaymentMethod } from '../dto/payment-method.enum'; import { Fee, FeeType } from '../entities/fee.entity'; @@ -210,7 +210,7 @@ export class FeeService { return fee; } - async getChargebackFee(request: OptionalFeeRequest): Promise { + async getChargebackFee(request: OptionalFeeRequest): Promise { const userFees = await this.getValidFees(request); try { @@ -226,7 +226,7 @@ export class FeeService { } } - async getUserFee(request: UserFeeRequest): Promise { + async getUserFee(request: UserFeeRequest): Promise { const userFees = await this.getValidFees(request); try { @@ -244,7 +244,7 @@ export class FeeService { } } - async getDefaultFee(request: FeeRequestBase, accountType = AccountType.PERSONAL): Promise { + async getDefaultFee(request: FeeRequestBase, accountType = AccountType.PERSONAL): Promise { const defaultFees = await this.getValidFees({ ...request, accountType }); try { @@ -307,7 +307,7 @@ export class FeeService { allowCachedBlockchainFee: boolean, paymentMethodIn: PaymentMethod, userDataId?: number, - ): Promise { + ): Promise { const blockchainFee = (await this.getBlockchainFeeInChf(from, allowCachedBlockchainFee)) + (await this.getBlockchainFeeInChf(to, allowCachedBlockchainFee)); @@ -317,6 +317,7 @@ export class FeeService { fees.filter((fee) => fee.type === FeeType.PARTNER), 'rate', ); + const partnerFeeSpec = { rate: partnerFee?.rate ?? 0, fixed: partnerFee?.fixed ?? 0 }; // get min special fee const specialFee = Util.minObj( @@ -327,12 +328,9 @@ export class FeeService { if (specialFee) return { fees: [specialFee], - rate: specialFee.rate, - fixed: specialFee.fixed ?? 0, - bankRate: 0, - bankFixed: 0, - partnerRate: partnerFee?.rate ?? 0, - partnerFixed: partnerFee?.fixed ?? 0, + dfx: { rate: specialFee.rate, fixed: specialFee.fixed ?? 0 }, + bank: { rate: 0, fixed: 0 }, + partner: partnerFeeSpec, payoutRefBonus: specialFee.payoutRefBonus, network: Math.min(specialFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -346,14 +344,11 @@ export class FeeService { if (customFee) return { fees: [customFee], - rate: customFee.rate, - fixed: customFee.fixed ?? 0, - bankRate: 0, - bankFixed: 0, - partnerRate: partnerFee?.rate ?? 0, - partnerFixed: partnerFee?.fixed ?? 0, - payoutRefBonus: customFee.payoutRefBonus, + dfx: { rate: customFee.rate, fixed: customFee.fixed ?? 0 }, + bank: { rate: 0, fixed: 0 }, + partner: partnerFeeSpec, network: Math.min(customFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), + payoutRefBonus: customFee.payoutRefBonus, }; // get min base fee @@ -381,6 +376,8 @@ export class FeeService { const combinedBankFeeRate = Util.sumObjValue(bankFees, 'rate'); const combinedBankFixedFee = Util.sumObjValue(bankFees, 'fixed'); + const bankFeeSpec = { rate: combinedBankFeeRate, fixed: combinedBankFixedFee }; + const combinedExtraFeeRate = Util.sumObjValue(additiveFees, 'rate') - (discountFee?.rate ?? 0); const combinedExtraFixedFee = Util.sumObjValue(additiveFees, 'fixed') - (discountFee?.fixed ?? 0); @@ -389,12 +386,9 @@ export class FeeService { this.logger.warn(`Discount is higher than base fee for user data ${userDataId}`); return { fees: [baseFee], - rate: baseFee.rate, - fixed: baseFee.fixed, - bankRate: combinedBankFeeRate, - bankFixed: combinedBankFixedFee, - partnerRate: partnerFee?.rate ?? 0, - partnerFixed: partnerFee?.fixed ?? 0, + dfx: { rate: baseFee.rate, fixed: baseFee.fixed }, + bank: bankFeeSpec, + partner: partnerFeeSpec, payoutRefBonus: true, network: Math.min(baseFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), }; @@ -402,12 +396,9 @@ export class FeeService { return { fees: [baseFee, discountFee, ...additiveFees].filter((e) => e != null), - rate: baseFee.rate + combinedExtraFeeRate, - fixed: Math.max(baseFee.fixed + combinedExtraFixedFee, 0), - bankRate: combinedBankFeeRate, - bankFixed: combinedBankFixedFee, - partnerRate: partnerFee?.rate ?? 0, - partnerFixed: partnerFee?.fixed ?? 0, + dfx: { rate: baseFee.rate + combinedExtraFeeRate, fixed: Math.max(baseFee.fixed + combinedExtraFixedFee, 0) }, + bank: bankFeeSpec, + partner: partnerFeeSpec, payoutRefBonus: baseFee.payoutRefBonus && (discountFee?.payoutRefBonus ?? true) && @@ -430,7 +421,7 @@ export class FeeService { from: Active, allowCachedBlockchainFee: boolean, paymentMethodIn: PaymentMethod, - ): Promise { + ): Promise { const blockchainFee = await this.getBlockchainFeeInChf(from, allowCachedBlockchainFee); // get min special fee @@ -442,13 +433,11 @@ export class FeeService { if (specialFee) return { fees: [specialFee], - rate: specialFee.rate, - fixed: specialFee.fixed ?? 0, - bankRate: 0, - bankFixed: 0, - partnerRate: 0, - partnerFixed: 0, + dfx: { rate: specialFee.rate, fixed: specialFee.fixed ?? 0 }, + bank: { rate: 0, fixed: 0 }, + partner: { rate: 0, fixed: 0 }, network: Math.min(specialFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), + payoutRefBonus: false, }; // get min custom fee @@ -460,13 +449,11 @@ export class FeeService { if (customFee) return { fees: [customFee], - rate: customFee.rate, - fixed: customFee.fixed ?? 0, - bankRate: 0, - bankFixed: 0, - partnerRate: 0, - partnerFixed: 0, + dfx: { rate: customFee.rate, fixed: customFee.fixed ?? 0 }, + bank: { rate: 0, fixed: 0 }, + partner: { rate: 0, fixed: 0 }, network: Math.min(customFee.blockchainFactor * blockchainFee, Config.maxBlockchainFee), + payoutRefBonus: false, }; // get chargeback fees @@ -494,16 +481,17 @@ export class FeeService { if (!baseFee) throw new InternalServerErrorException('Chargeback base fee is missing'); return { fees: [baseFee, ...additiveFees], - rate: baseFee.rate + combinedAdditiveChargebackFeeRate, - fixed: (baseFee.fixed ?? 0) + (combinedAdditiveChargebackFixedFee ?? 0), - bankRate: combinedBankFeeRate, - bankFixed: combinedBankFixedFee ?? 0, - partnerRate: 0, - partnerFixed: 0, + dfx: { + rate: baseFee.rate + combinedAdditiveChargebackFeeRate, + fixed: (baseFee.fixed ?? 0) + (combinedAdditiveChargebackFixedFee ?? 0), + }, + bank: { rate: combinedBankFeeRate, fixed: combinedBankFixedFee ?? 0 }, + partner: { rate: 0, fixed: 0 }, network: Math.min( (baseFee.blockchainFactor + combinedAdditiveChargebackBlockchainFee) * blockchainFee, Config.maxBlockchainFee, ), + payoutRefBonus: false, }; } diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 9ce668dd13..e20dbf7864 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -1,4 +1,4 @@ -import { BadRequestException, ForbiddenException, Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; +import { BadRequestException, ForbiddenException, forwardRef, Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { CronExpression } from '@nestjs/schedule'; import { Config, Environment } from 'src/config/config'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; @@ -40,7 +40,7 @@ import { BankTx } from '../../bank-tx/bank-tx/entities/bank-tx.entity'; import { CardBankName, IbanBankName } from '../../bank/bank/dto/bank.dto'; import { CryptoInput, PayInConfirmationType } from '../../payin/entities/crypto-input.entity'; import { PriceCurrency, PriceValidity, PricingService } from '../../pricing/services/pricing.service'; -import { FeeDto, InternalFeeDto } from '../dto/fee.dto'; +import { FeeAmountsDto, FeeInfo, FeeSpec, InternalFeeDto, toFeeDto } from '../dto/fee.dto'; import { FiatPaymentMethod, PaymentMethod } from '../dto/payment-method.enum'; import { QuoteError } from '../dto/transaction-helper/quote-error.enum'; import { TargetEstimation, TransactionDetails } from '../dto/transaction-helper/transaction-details.dto'; @@ -197,7 +197,7 @@ export class TransactionHelper implements OnModuleInit { bankIn: CardBankName | IbanBankName | undefined, bankOut: CardBankName | IbanBankName | undefined, user: User, - ): Promise { + ): Promise { // get fee const [fee, networkStartFee] = await this.getAllFees( user, @@ -219,33 +219,26 @@ export class TransactionHelper implements OnModuleInit { const specs: TxSpec = { fee: { min: minSpecs.minFee, - fixed: fee.fixed, network: fee.network, networkStart: networkStartFee, - bankFixed: fee.bankFixed, - partnerFixed: fee.partnerFixed, + dfx: fee.dfx, + bank: fee.bank, + partner: fee.partner, }, volume: { min: minSpecs.minVolume, max: Number.MAX_VALUE }, }; const sourceSpecs = await this.getSourceSpecs(fromReference, specs, PriceValidity.VALID_ONLY); - const { dfx, platform, bank, total } = this.calculateTotalFee( - inputReferenceAmount, - fee.rate, - fee.partnerRate, - fee.bankRate, - sourceSpecs, - from, - ); + const amounts = this.calculateTotalFee(inputReferenceAmount, sourceSpecs, from); + + const feeDto = toFeeDto(amounts, sourceSpecs); return { - ...fee, - ...sourceSpecs.fee, - total, - dfx, - platform, - bank, + ...feeDto, + fees: fee.fees, + partner: amounts.partner, + payoutRefBonus: fee.payoutRefBonus, }; } @@ -311,12 +304,12 @@ export class TransactionHelper implements OnModuleInit { // target estimation const extendedSpecs: TxSpec = { fee: { - network: fee.network, - fixed: fee.fixed, min: specs.minFee, + network: fee.network, networkStart: networkStartFee, - bankFixed: fee.bankFixed, - partnerFixed: fee.partnerFixed, + dfx: fee.dfx, + bank: fee.bank, + partner: fee.partner, }, volume: { min: specs.minVolume, @@ -330,9 +323,6 @@ export class TransactionHelper implements OnModuleInit { const target = await this.getTargetEstimation( sourceAmount, targetAmount, - fee.rate, - fee.partnerRate, - fee.bankRate, sourceSpecs, targetSpecs, from, @@ -422,12 +412,12 @@ export class TransactionHelper implements OnModuleInit { userData, }); - const dfxFeeAmount = inputAmount * chargebackFee.rate + price.convert(chargebackFee.fixed); + const dfxFeeAmount = inputAmount * chargebackFee.dfx.rate + price.convert(chargebackFee.dfx.fixed); const networkFeeAmount = price.convert(chargebackFee.network); const bankFeeAmount = refundEntity.paymentMethodIn === FiatPaymentMethod.BANK ? price.convert( - chargebackFee.bankRate * inputAmount + chargebackFee.bankFixed + refundEntity.chargebackBankFee * 1.01, + chargebackFee.bank.rate * inputAmount + chargebackFee.bank.fixed + refundEntity.chargebackBankFee * 1.01, ) : 0; // Bank fee buffer 1% @@ -539,7 +529,7 @@ export class TransactionHelper implements OnModuleInit { specialCodes: string[], exactPrice: boolean, allowCachedBlockchainFee: boolean, - ): Promise<[InternalFeeDto, number]> { + ): Promise<[FeeInfo, number]> { const [fee, networkStartFee] = await Promise.all([ this.getTxFee( user, @@ -626,7 +616,7 @@ export class TransactionHelper implements OnModuleInit { txVolumeChf: number, specialCodes: string[], allowCachedBlockchainFee: boolean, - ): Promise { + ): Promise { const feeRequest: UserFeeRequest = { user, wallet, @@ -647,9 +637,6 @@ export class TransactionHelper implements OnModuleInit { private async getTargetEstimation( inputAmount: number | undefined, outputAmount: number | undefined, - feeRate: number, - partnerRate: number, - bankFeeRate: number, sourceSpecs: TxSpec, targetSpecs: TxSpec, from: Active, @@ -659,15 +646,15 @@ export class TransactionHelper implements OnModuleInit { const price = await this.pricingService.getPrice(from, to, priceValidity); const outputAmountSource = outputAmount && price.invert().convert(outputAmount); - const sourceAmount = inputAmount ?? this.getInputAmount(outputAmountSource, feeRate, bankFeeRate, sourceSpecs); - const sourceFees = this.calculateTotalFee(sourceAmount, feeRate, partnerRate, bankFeeRate, sourceSpecs, from); + const sourceAmount = inputAmount ?? this.getInputAmount(outputAmountSource, sourceSpecs); + const sourceFees = this.calculateTotalFee(sourceAmount, sourceSpecs, from); const targetAmount = outputAmount ?? price.convert(Math.max(inputAmount - sourceFees.total, 0)); - const targetFees = { + const targetFees: FeeAmountsDto = { dfx: this.convertFee(sourceFees.dfx, price, to), - platform: this.convertFee(sourceFees.platform, price, to), - total: this.convertFee(sourceFees.total, price, to), bank: this.convertFee(sourceFees.bank, price, to), + partner: this.convertFee(sourceFees.partner, price, to), + total: this.convertFee(sourceFees.total, price, to), }; return { @@ -678,27 +665,19 @@ export class TransactionHelper implements OnModuleInit { estimatedAmount: Util.roundReadable(targetAmount, amountType(to)), exactPrice: price.isValid, priceSteps: price.steps, - feeSource: { - rate: feeRate, - ...sourceSpecs.fee, - ...sourceFees, - }, - feeTarget: { - rate: feeRate, - ...targetSpecs.fee, - ...targetFees, - }, + feeSource: toFeeDto(sourceFees, sourceSpecs), + feeTarget: toFeeDto(targetFees, targetSpecs), }; } private getInputAmount( outputAmount: number, - rate: number, - bankRate: number, - { fee: { min, fixed, network, bankFixed, networkStart } }: TxSpec, + { fee: { min, network, dfx, bank, partner, networkStart } }: TxSpec, ): number { - const inputAmountNormal = (outputAmount + fixed + network + bankFixed + networkStart) / (1 - (rate + bankRate)); - const inputAmountWithMinFee = outputAmount + network + bankFixed + networkStart + min; + const inputAmountNormal = + (outputAmount + dfx.fixed + bank.fixed + partner.fixed + network + networkStart) / + (1 - (dfx.rate + bank.rate + partner.rate)); + const inputAmountWithMinFee = outputAmount + network + bank.fixed + partner.fixed + networkStart + min; return Math.max(inputAmountNormal, inputAmountWithMinFee); } @@ -724,9 +703,9 @@ export class TransactionHelper implements OnModuleInit { return { fee: { min: this.convertFee(fee.min, price, from), - fixed: this.convertFee(fee.fixed, price, from), - partnerFixed: this.convertFee(fee.partnerFixed, price, from), - bankFixed: this.convertFee(fee.bankFixed, price, from), + dfx: this.convertFeeSpec(fee.dfx, price, from), + bank: this.convertFeeSpec(fee.bank, price, from), + partner: this.convertFeeSpec(fee.partner, price, from), network: this.convertFee(fee.network, price, from), networkStart: fee.networkStart != null ? this.convertFee(fee.networkStart, price, from) : undefined, }, @@ -743,9 +722,9 @@ export class TransactionHelper implements OnModuleInit { return { fee: { min: this.convertFee(fee.min, price, to), - fixed: this.convertFee(fee.fixed, price, to), - partnerFixed: this.convertFee(fee.partnerFixed, price, to), - bankFixed: this.convertFee(fee.bankFixed, price, to), + dfx: this.convertFeeSpec(fee.dfx, price, to), + bank: this.convertFeeSpec(fee.bank, price, to), + partner: this.convertFeeSpec(fee.partner, price, to), network: this.convertFee(fee.network, price, to), networkStart: fee.networkStart != null ? this.convertFee(fee.networkStart, price, to) : undefined, }, @@ -758,30 +737,35 @@ export class TransactionHelper implements OnModuleInit { private calculateTotalFee( amount: number, - rate: number, - partnerRate: number, - bankRate: number, - { fee: { fixed, min, network, networkStart, bankFixed, partnerFixed } }: TxSpec, + { fee: { min, network, networkStart, dfx, bank, partner } }: TxSpec, roundingActive: Active, - ): { dfx: number; platform: number; bank: number; total: number } { - const bank = amount * bankRate + bankFixed; - const dfx = Math.max(amount * rate + fixed, min); - const partner = amount * partnerRate + partnerFixed; - const total = dfx + partner + bank + network + (networkStart ?? 0); + ): FeeAmountsDto { + const dfxAmount = Math.max(this.calculateFee(amount, dfx), min); + const bankAmount = this.calculateFee(amount, bank); + const partnerAmount = this.calculateFee(amount, partner); + const totalAmount = dfxAmount + partnerAmount + bankAmount + network + (networkStart ?? 0); return { - dfx: Util.roundReadable(dfx, feeAmountType(roundingActive)), - platform: Util.roundReadable(partner, feeAmountType(roundingActive)), - bank: Util.roundReadable(bank, feeAmountType(roundingActive)), - total: Util.roundReadable(total, feeAmountType(roundingActive)), + dfx: Util.roundReadable(dfxAmount, feeAmountType(roundingActive)), + bank: Util.roundReadable(bankAmount, feeAmountType(roundingActive)), + partner: Util.roundReadable(partnerAmount, feeAmountType(roundingActive)), + total: Util.roundReadable(totalAmount, feeAmountType(roundingActive)), }; } + private calculateFee(amount: number, spec: FeeSpec): number { + return amount * spec.rate + spec.fixed; + } + private convert(amount: number, price: Price, roundingActive: Active): number { const targetAmount = price.convert(amount); return Util.roundReadable(targetAmount, amountType(roundingActive)); } + private convertFeeSpec(spec: FeeSpec, price: Price, roundingActive: Active): FeeSpec { + return { rate: spec.rate, fixed: this.convertFee(spec.fixed, price, roundingActive) }; + } + private convertFee(amount: number, price: Price, roundingActive: Active): number { const targetAmount = price.convert(amount); return Util.roundReadable(targetAmount, feeAmountType(roundingActive)); From 45f7893a44caf8f9a1f41f340a77b6ba206dbcbc Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:29:53 +0100 Subject: [PATCH 09/10] [DEV-3957] partner fee payout --- .../process/entities/buy-crypto.entity.ts | 15 +++++++++++---- .../process/services/buy-crypto.service.ts | 19 +++++++++++++++++-- .../sell-crypto/process/buy-fiat.entity.ts | 15 +++++++++++---- .../process/services/buy-fiat.service.ts | 19 +++++++++++++++++-- .../generic/user/models/user/user.entity.ts | 7 ------- .../user/models/wallet/wallet.entity.ts | 5 ++++- .../payment/services/fee.service.ts | 2 +- 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts index 10a20fcd59..31d83b3be8 100644 --- a/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts +++ b/src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity.ts @@ -27,6 +27,7 @@ import { FiatPaymentMethod, PaymentMethod, } from 'src/subdomains/supporting/payment/dto/payment-method.enum'; +import { FeeType } from 'src/subdomains/supporting/payment/entities/fee.entity'; import { SpecialExternalAccount } from 'src/subdomains/supporting/payment/entities/special-external-account.entity'; import { Transaction } from 'src/subdomains/supporting/payment/entities/transaction.entity'; import { Price, PriceStep } from 'src/subdomains/supporting/pricing/domain/entities/price'; @@ -118,6 +119,9 @@ export class BuyCrypto extends IEntity { @Column({ length: 256, nullable: true }) usedRef?: string; + @Column({ length: 256, nullable: true }) + usedPartnerFeeRef?: string; + @Column({ type: 'float', nullable: true }) refProvision?: number; @@ -467,7 +471,7 @@ export class BuyCrypto extends IEntity { minFeeAmountFiat: number, totalFeeAmountChf: number, ): UpdateResult { - const { usedRef, refProvision } = this.user.specifiedRef; + const partnerFee = fee.partner ? fee.fees.find((f) => f.type === FeeType.PARTNER) : undefined; const inputReferenceAmountMinusFee = this.inputReferenceAmount - fee.total; const update: Partial = @@ -484,10 +488,11 @@ export class BuyCrypto extends IEntity { blockchainFee: fee.network, bankFeeAmount: fee.bank, partnerFeeAmount: fee.partner, + usedPartnerFeeRef: fee.partner ? partnerFee.wallet.owner.ref : undefined, inputReferenceAmountMinusFee, - usedRef, - refProvision, - refFactor: !fee.payoutRefBonus || usedRef === Config.defaultRef ? 0 : 1, + usedRef: this.user.usedRef, + refProvision: this.user.refFeePercent, + refFactor: !fee.payoutRefBonus || this.user.usedRef === Config.defaultRef ? 0 : 1, usedFees: fee.fees?.map((fee) => fee.id).join(';'), networkStartFeeAmount: fee.networkStart, status: this.status === BuyCryptoStatus.WAITING_FOR_LOWER_FEE ? BuyCryptoStatus.CREATED : undefined, @@ -578,6 +583,8 @@ export class BuyCrypto extends IEntity { chargebackAllowedBy: null, chargebackOutput: null, priceDefinitionAllowedDate: null, + partnerFeeAmount: null, + usedPartnerFeeRef: null, }; Object.assign(this, update); 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 fa7ebea0de..c9d331a74f 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 @@ -873,13 +873,16 @@ export class BuyCryptoService { for (const ref of refs) { const { volume: buyCryptoVolume, credit: buyCryptoCredit } = await this.getRefVolume(ref); + const { volume: buyCryptoPartnerVolume, credit: buyCryptoPartnerCredit } = await this.getPartnerFeeRefVolume(ref); const { volume: buyFiatVolume, credit: buyFiatCredit } = await this.buyFiatService.getRefVolume(ref); + const { volume: buyFiatPartnerVolume, credit: buyFiatPartnerCredit } = + await this.buyFiatService.getPartnerFeeRefVolume(ref); const { volume: manualVolume, credit: manualCredit } = await this.transactionService.getManualRefVolume(ref); await this.userService.updateRefVolume( ref, - buyCryptoVolume + buyFiatVolume + manualVolume, - buyCryptoCredit + buyFiatCredit + manualCredit, + buyCryptoVolume + buyCryptoPartnerVolume + buyFiatVolume + buyFiatPartnerVolume + manualVolume, + buyCryptoCredit + buyCryptoPartnerCredit + buyFiatCredit + buyFiatPartnerCredit + manualCredit, ); } } @@ -896,6 +899,18 @@ export class BuyCryptoService { return { volume: volume ?? 0, credit: credit ?? 0 }; } + async getPartnerFeeRefVolume(ref: string): Promise<{ volume: number; credit: number }> { + const { volume, credit } = await this.buyCryptoRepo + .createQueryBuilder('buyCrypto') + .select('SUM((partnerFeeAmount * (amountInEur/amountInChf )) / (refProvision * 0.01))', 'volume') + .addSelect('SUM(partnerFeeAmount * (amountInEur/amountInChf ))', 'credit') + .where('usedPartnerFeeRef = :ref', { ref }) + .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) + .getRawOne<{ volume: number; credit: number }>(); + + return { volume: volume ?? 0, credit: credit ?? 0 }; + } + // Admin Support Tool methods async getAllRefTransactions(refCodes: string[]): Promise { diff --git a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts index 8f9cd16b50..c445af1390 100644 --- a/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts +++ b/src/subdomains/core/sell-crypto/process/buy-fiat.entity.ts @@ -19,6 +19,7 @@ import { FiatPaymentMethod, PaymentMethod, } from 'src/subdomains/supporting/payment/dto/payment-method.enum'; +import { FeeType } from 'src/subdomains/supporting/payment/entities/fee.entity'; import { SpecialExternalAccount } from 'src/subdomains/supporting/payment/entities/special-external-account.entity'; import { PriceStep } from 'src/subdomains/supporting/pricing/domain/entities/price'; import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; @@ -85,6 +86,9 @@ export class BuyFiat extends IEntity { @Column({ length: 256, nullable: true }) usedRef?: string; + @Column({ length: 256, nullable: true }) + usedPartnerFeeRef?: string; + @Column({ type: 'float', nullable: true }) refProvision?: number; @@ -288,7 +292,7 @@ export class BuyFiat extends IEntity { minFeeAmountFiat: number, totalFeeAmountChf: number, ): UpdateResult { - const { usedRef, refProvision } = this.user.specifiedRef; + const partnerFee = fee.partner ? fee.fees.find((f) => f.type === FeeType.PARTNER) : undefined; const inputReferenceAmountMinusFee = this.inputReferenceAmount - fee.total; const update: Partial = @@ -305,10 +309,11 @@ export class BuyFiat extends IEntity { blockchainFee: fee.network, bankFeeAmount: fee.bank, partnerFeeAmount: fee.partner, + usedPartnerFeeRef: fee.partner ? partnerFee.wallet.owner.ref : undefined, inputReferenceAmountMinusFee, - usedRef, - refProvision, - refFactor: !fee.payoutRefBonus || usedRef === Config.defaultRef ? 0 : 1, + usedRef: this.user.usedRef, + refProvision: this.user.refFeePercent, + refFactor: !fee.payoutRefBonus || this.user.usedRef === Config.defaultRef ? 0 : 1, usedFees: fee.fees?.map((fee) => fee.id).join(';'), }; @@ -471,6 +476,8 @@ export class BuyFiat extends IEntity { chargebackAllowedDateUser: null, chargebackAmount: null, chargebackAllowedBy: null, + partnerFeeAmount: null, + usedPartnerFeeRef: null, }; Object.assign(this, update); diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index 2ffad9fc9b..904acde01e 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -470,13 +470,16 @@ export class BuyFiatService { for (const ref of refs) { const { volume: buyFiatVolume, credit: buyFiatCredit } = await this.getRefVolume(ref); + const { volume: buyFiatPartnerVolume, credit: buyFiatPartnerCredit } = await this.getPartnerFeeRefVolume(ref); const { volume: buyCryptoVolume, credit: buyCryptoCredit } = await this.buyCryptoService.getRefVolume(ref); + const { volume: buyCryptoPartnerVolume, credit: buyCryptoPartnerCredit } = + await this.buyCryptoService.getPartnerFeeRefVolume(ref); const { volume: manualVolume, credit: manualCredit } = await this.transactionService.getManualRefVolume(ref); await this.userService.updateRefVolume( ref, - buyFiatVolume + buyCryptoVolume + manualVolume, - buyFiatCredit + buyCryptoCredit + manualCredit, + buyFiatVolume + buyFiatPartnerVolume + buyCryptoVolume + buyCryptoPartnerVolume + manualVolume, + buyFiatCredit + buyFiatPartnerCredit + buyCryptoCredit + buyCryptoPartnerCredit + manualCredit, ); } } @@ -493,6 +496,18 @@ export class BuyFiatService { return { volume: volume ?? 0, credit: credit ?? 0 }; } + async getPartnerFeeRefVolume(ref: string): Promise<{ volume: number; credit: number }> { + const { volume, credit } = await this.buyFiatRepo + .createQueryBuilder('buyFiat') + .select('SUM((partnerFeeAmount * (amountInEur/amountInChf )) / (refProvision * 0.01))', 'volume') + .addSelect('SUM(partnerFeeAmount * (amountInEur/amountInChf ))', 'credit') + .where('usedPartnerFeeRef = :ref', { ref }) + .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) + .getRawOne<{ volume: number; credit: number }>(); + + return { volume: volume ?? 0, credit: credit ?? 0 }; + } + // Statistics async getTransactions(dateFrom: Date = new Date(0), dateTo: Date = new Date()): Promise { diff --git a/src/subdomains/generic/user/models/user/user.entity.ts b/src/subdomains/generic/user/models/user/user.entity.ts index 8ddea97a29..0c7c1b6c97 100644 --- a/src/subdomains/generic/user/models/user/user.entity.ts +++ b/src/subdomains/generic/user/models/user/user.entity.ts @@ -1,4 +1,3 @@ -import { Config } from 'src/config/config'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; import { CryptoService } from 'src/integration/blockchain/shared/services/crypto.service'; import { UserRole } from 'src/shared/auth/user-role.enum'; @@ -191,12 +190,6 @@ export class User extends IEntity { return [this.id, update]; } - get specifiedRef(): { usedRef: string; refProvision: number } { - return this.wallet?.name === 'CakeWallet' - ? { usedRef: '160-195', refProvision: 2 } - : { usedRef: this.usedRef, refProvision: this.usedRef === Config.defaultRef ? 0 : this.refFeePercent }; - } - get blockchains(): Blockchain[] { // wallet name / blockchain map const customChains = { diff --git a/src/subdomains/generic/user/models/wallet/wallet.entity.ts b/src/subdomains/generic/user/models/wallet/wallet.entity.ts index 8b4cfabea2..0a913dca56 100644 --- a/src/subdomains/generic/user/models/wallet/wallet.entity.ts +++ b/src/subdomains/generic/user/models/wallet/wallet.entity.ts @@ -3,7 +3,7 @@ import { AmlRule } from 'src/subdomains/core/aml/enums/aml-rule.enum'; import { KycStepType } from 'src/subdomains/generic/kyc/enums/kyc.enum'; import { User } from 'src/subdomains/generic/user/models/user/user.entity'; import { MailContextType } from 'src/subdomains/supporting/notification/enums'; -import { Column, Entity, Index, OneToMany } from 'typeorm'; +import { Column, Entity, Index, ManyToOne, OneToMany } from 'typeorm'; import { WebhookType } from '../../services/webhook/dto/webhook.dto'; import { KycType } from '../user-data/user-data.enum'; @@ -21,6 +21,9 @@ export enum WebhookConfigOption { @Entity() export class Wallet extends IEntity { + @ManyToOne(() => User, { nullable: true }) + owner?: User; + @Column({ length: 256, nullable: true }) @Index({ unique: true, where: 'address IS NOT NULL' }) address?: string; diff --git a/src/subdomains/supporting/payment/services/fee.service.ts b/src/subdomains/supporting/payment/services/fee.service.ts index 339538067d..e31b6c678f 100644 --- a/src/subdomains/supporting/payment/services/fee.service.ts +++ b/src/subdomains/supporting/payment/services/fee.service.ts @@ -297,7 +297,7 @@ export class FeeService { } private async getAllFees(): Promise { - return this.feeRepo.findCached('all'); + return this.feeRepo.findCached('all', { relations: { wallet: { owner: true } } }); } private async calculateFee( From 1de9c620aa09dce8cf87907d5177d6f26b392c33 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:16:58 +0100 Subject: [PATCH 10/10] [DEV-3957] add partnerRef cols --- .../process/services/buy-crypto.service.ts | 8 +++++--- .../referral/reward/services/ref-reward.service.ts | 2 +- .../process/services/buy-fiat.service.ts | 8 +++++--- .../user/models/user/dto/user-dto.mapper.ts | 4 ++-- .../generic/user/models/user/user.entity.ts | 14 ++++++++++++++ .../generic/user/models/user/user.service.ts | 14 +++++++++++--- 6 files changed, 38 insertions(+), 12 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 c9d331a74f..b445424f91 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 @@ -881,8 +881,10 @@ export class BuyCryptoService { await this.userService.updateRefVolume( ref, - buyCryptoVolume + buyCryptoPartnerVolume + buyFiatVolume + buyFiatPartnerVolume + manualVolume, - buyCryptoCredit + buyCryptoPartnerCredit + buyFiatCredit + buyFiatPartnerCredit + manualCredit, + buyCryptoVolume + buyFiatVolume + manualVolume, + buyCryptoCredit + buyFiatCredit + manualCredit, + buyCryptoPartnerVolume + buyFiatPartnerVolume, + buyCryptoPartnerCredit + buyFiatPartnerCredit, ); } } @@ -902,7 +904,7 @@ export class BuyCryptoService { async getPartnerFeeRefVolume(ref: string): Promise<{ volume: number; credit: number }> { const { volume, credit } = await this.buyCryptoRepo .createQueryBuilder('buyCrypto') - .select('SUM((partnerFeeAmount * (amountInEur/amountInChf )) / (refProvision * 0.01))', 'volume') + .select('SUM(amountInEur)', 'volume') .addSelect('SUM(partnerFeeAmount * (amountInEur/amountInChf ))', 'credit') .where('usedPartnerFeeRef = :ref', { ref }) .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) diff --git a/src/subdomains/core/referral/reward/services/ref-reward.service.ts b/src/subdomains/core/referral/reward/services/ref-reward.service.ts index bbc65b4429..b89f6dd165 100644 --- a/src/subdomains/core/referral/reward/services/ref-reward.service.ts +++ b/src/subdomains/core/referral/reward/services/ref-reward.service.ts @@ -146,7 +146,7 @@ export class RefRewardService { : await this.assetService.getNativeAsset(blockchain); for (const user of users) { - const refCreditEur = user.refCredit - user.paidRefCredit; + const refCreditEur = user.completeRefCredit - user.paidRefCredit; const minCredit = PayoutLimits[blockchain]; if (!(refCreditEur >= minCredit)) continue; diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index 904acde01e..f20cc3599e 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -478,8 +478,10 @@ export class BuyFiatService { await this.userService.updateRefVolume( ref, - buyFiatVolume + buyFiatPartnerVolume + buyCryptoVolume + buyCryptoPartnerVolume + manualVolume, - buyFiatCredit + buyFiatPartnerCredit + buyCryptoCredit + buyCryptoPartnerCredit + manualCredit, + buyFiatVolume + buyCryptoVolume + manualVolume, + buyFiatCredit + buyCryptoCredit + manualCredit, + buyFiatPartnerVolume + buyCryptoPartnerVolume, + buyFiatPartnerCredit + buyCryptoPartnerCredit, ); } } @@ -499,7 +501,7 @@ export class BuyFiatService { async getPartnerFeeRefVolume(ref: string): Promise<{ volume: number; credit: number }> { const { volume, credit } = await this.buyFiatRepo .createQueryBuilder('buyFiat') - .select('SUM((partnerFeeAmount * (amountInEur/amountInChf )) / (refProvision * 0.01))', 'volume') + .select('SUM(amountInEur)', 'volume') .addSelect('SUM(partnerFeeAmount * (amountInEur/amountInChf ))', 'credit') .where('usedPartnerFeeRef = :ref', { ref }) .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) diff --git a/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts b/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts index 543885146e..ddedc42e0a 100644 --- a/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts +++ b/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts @@ -72,8 +72,8 @@ export class UserDtoMapper { const dto: ReferralDto = { code: user.ref, commission: Util.round(user.refFeePercent / 100, 4), - volume: user.refVolume, - credit: user.refCredit, + volume: user.completeRefVolume, + credit: user.completeRefCredit, paidCredit: user.paidRefCredit, userCount: userCount, activeUserCount: activeUserCount, diff --git a/src/subdomains/generic/user/models/user/user.entity.ts b/src/subdomains/generic/user/models/user/user.entity.ts index 0c7c1b6c97..2311b20b62 100644 --- a/src/subdomains/generic/user/models/user/user.entity.ts +++ b/src/subdomains/generic/user/models/user/user.entity.ts @@ -122,6 +122,12 @@ export class User extends IEntity { @Column({ type: 'float', default: 0 }) refCredit: number; // EUR + @Column({ type: 'float', default: 0 }) + partnerRefVolume: number; // EUR + + @Column({ type: 'float', default: 0 }) + partnerRefCredit: number; // EUR + @Column({ type: 'float', default: 0 }) paidRefCredit: number; // EUR @@ -210,6 +216,14 @@ export class User extends IEntity { get isDeleted(): boolean { return this.status === UserStatus.DELETED; } + + get completeRefVolume(): number { + return this.refVolume + this.partnerRefVolume; + } + + get completeRefCredit(): number { + return this.refCredit + this.partnerRefCredit; + } } export const UserSupportUpdateCols = ['status', 'setRef']; diff --git a/src/subdomains/generic/user/models/user/user.service.ts b/src/subdomains/generic/user/models/user/user.service.ts index 1c3489e285..0aa65fcd7e 100644 --- a/src/subdomains/generic/user/models/user/user.service.ts +++ b/src/subdomains/generic/user/models/user/user.service.ts @@ -505,12 +505,20 @@ export class UserService { }; } - async updateRefVolume(ref: string, volume: number, credit: number): Promise { + async updateRefVolume( + ref: string, + volume: number, + credit: number, + partnerVolume?: number, + partnerCredit?: number, + ): Promise { await this.userRepo.update( { ref }, { refVolume: Util.round(volume, Config.defaultVolumeDecimal), refCredit: Util.round(credit, Config.defaultVolumeDecimal), + partnerRefVolume: Util.round(partnerVolume, Config.defaultVolumeDecimal), + partnerRefCredit: Util.round(partnerCredit, Config.defaultVolumeDecimal), }, ); } @@ -609,8 +617,8 @@ export class UserService { return { ref: user.ref, refFeePercent: user.refFeePercent, - refVolume: user.refVolume, - refCredit: user.refCredit, + refVolume: user.completeRefVolume, + refCredit: user.completeRefCredit, paidRefCredit: user.paidRefCredit, ...(await this.getRefUserCounts(user)), };