Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEV-3493] userData in transaction #1870

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
15 changes: 15 additions & 0 deletions migration/1735574957300-addUserDataTransaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class addUserDataTransaction1735574957300 {
name = 'addUserDataTransaction1735574957300'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "dbo"."transaction" ADD "userDataId" int`);
await queryRunner.query(`ALTER TABLE "dbo"."transaction" ADD CONSTRAINT "FK_1e5036f71c59cd6e514280f2719" FOREIGN KEY ("userDataId") REFERENCES "user_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "dbo"."transaction" DROP CONSTRAINT "FK_1e5036f71c59cd6e514280f2719"`);
await queryRunner.query(`ALTER TABLE "dbo"."transaction" DROP COLUMN "userDataId"`);
}
}
8 changes: 4 additions & 4 deletions src/shared/models/asset/__mocks__/asset.entity.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.e
import { Asset, AssetCategory, AssetType } from '../asset.entity';

const defaultAsset: Partial<Asset> = {
name: 'dTSLA',
dexName: 'dTSLA',
blockchain: Blockchain.DEFICHAIN,
name: 'USDT',
dexName: 'USDT',
blockchain: Blockchain.ETHEREUM,
category: AssetCategory.PUBLIC,
type: AssetType.COIN,
type: AssetType.TOKEN,
};

export function createDefaultAsset(): Asset {
Expand Down
1 change: 1 addition & 0 deletions src/shared/services/process.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export enum Process {
PAYMENT_CONFIRMATIONS = 'PaymentConfirmations',
FIAT_OUTPUT_COMPLETE = 'FiatOutputComplete',
BLOCKCHAIN_FEE_UPDATE = 'BlockchainFeeUpdate',
TRANSACTION_USER_SYNC = 'TransactionUserSync',
}

type ProcessMap = { [p in Process]?: boolean };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,80 +6,40 @@ import { BuyCrypto, BuyCryptoStatus } from '../buy-crypto.entity';
import { createCustomBuyCryptoBatch } from './buy-crypto-batch.entity.mock';
import { createDefaultBuyCryptoFee } from './buy-crypto-fee.entity.mock';

const defaultBuyCrypto: Partial<BuyCrypto> = {
id: 1,
buy: createDefaultBuy(),
batch: createCustomBuyCryptoBatch({ transactions: [] }),
inputAmount: 100,
inputAsset: 'EUR',
inputReferenceAmount: 100,
inputReferenceAsset: 'EUR',
amountInChf: 120,
amountInEur: 100,
amlCheck: CheckStatus.PASS,
percentFee: 0.01,
percentFeeAmount: 1,
inputReferenceAmountMinusFee: 99,
outputReferenceAmount: 0.005,
outputReferenceAsset: createCustomAsset({ dexName: 'BTC' }),
outputAmount: 0.2,
outputAsset: createCustomAsset({ dexName: 'BTC' }),
txId: 'TX_ID_01',
outputDate: new Date(),
mailSendDate: new Date(),
refProvision: 0,
refFactor: 0,
isComplete: false,
fee: createDefaultBuyCryptoFee(),
status: BuyCryptoStatus.PENDING_LIQUIDITY,
transaction: createDefaultTransaction(),
chargebackAmount: null,
};

export function createDefaultBuyCrypto(): BuyCrypto {
return createCustomBuyCrypto({});
}

export function createCustomBuyCrypto(customValues: Partial<BuyCrypto>): BuyCrypto {
const {
id,
buy,
batch,
inputAmount,
inputAsset,
inputReferenceAmount,
inputReferenceAsset,
amountInChf,
amountInEur,
amlCheck,
percentFee,
percentFeeAmount,
absoluteFeeAmount,
inputReferenceAmountMinusFee,
outputReferenceAmount,
outputReferenceAsset,
outputAmount,
outputAsset,
txId,
outputDate,
recipientMail,
mailSendDate,
usedRef,
refProvision,
refFactor,
isComplete,
cryptoInput,
fee,
status,
transaction,
} = customValues;

const keys = Object.keys(customValues);
const entity = new BuyCrypto();

entity.id = keys.includes('id') ? id : undefined;
entity.buy = keys.includes('buy') ? buy : createDefaultBuy();
entity.batch = keys.includes('batch') ? batch : createCustomBuyCryptoBatch({ transactions: [entity] });
entity.inputAmount = keys.includes('inputAmount') ? inputAmount : 100;
entity.inputAsset = keys.includes('inputAsset') ? inputAsset : 'EUR';
entity.inputReferenceAmount = keys.includes('inputReferenceAmount') ? inputReferenceAmount : 100;
entity.inputReferenceAsset = keys.includes('inputReferenceAsset') ? inputReferenceAsset : 'EUR';
entity.amountInChf = keys.includes('amountInChf') ? amountInChf : 120;
entity.amountInEur = keys.includes('amountInEur') ? amountInEur : 100;
entity.amlCheck = keys.includes('amlCheck') ? amlCheck : CheckStatus.PASS;
entity.percentFee = keys.includes('percentFee') ? percentFee : 0.01;
entity.percentFeeAmount = keys.includes('percentFeeAmount') ? percentFeeAmount : 1;
entity.absoluteFeeAmount = keys.includes('absoluteFeeAmount') ? absoluteFeeAmount : null;
entity.inputReferenceAmountMinusFee = keys.includes('inputReferenceAmountMinusFee')
? inputReferenceAmountMinusFee
: 99;
entity.outputReferenceAmount = keys.includes('outputReferenceAmount') ? outputReferenceAmount : 0.005;
entity.outputReferenceAsset = keys.includes('outputReferenceAsset')
? outputReferenceAsset
: createCustomAsset({ dexName: 'BTC' });
entity.outputAmount = keys.includes('outputAmount') ? outputAmount : 0.2;
entity.outputAsset = keys.includes('outputAsset') ? outputAsset : createCustomAsset({ dexName: 'dTSLA' });
entity.txId = keys.includes('txId') ? txId : 'TX_ID_01';
entity.outputDate = keys.includes('outputDate') ? outputDate : new Date();
entity.recipientMail = keys.includes('recipientMail') ? recipientMail : '';
entity.mailSendDate = keys.includes('mailSendDate') ? mailSendDate : new Date();
entity.usedRef = keys.includes('usedRef') ? usedRef : '';
entity.refProvision = keys.includes('refProvision') ? refProvision : 0;
entity.refFactor = keys.includes('refFactor') ? refFactor : 0;
entity.isComplete = keys.includes('isComplete') ? isComplete : false;
entity.cryptoInput = keys.includes('cryptoInput') ? cryptoInput : undefined;
entity.fee = keys.includes('fee') ? fee : createDefaultBuyCryptoFee();
entity.status = keys.includes('status') ? status : BuyCryptoStatus.PENDING_LIQUIDITY;
entity.transaction = keys.includes('transaction') ? transaction : createDefaultTransaction();
return entity;
return Object.assign(new BuyCrypto(), { ...defaultBuyCrypto, ...customValues });
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class BuyCryptoService {
await this.createEntity(entity, {
type: TransactionTypeInternal.BUY_CRYPTO,
user: buy.user,
userData: buy.userData,
resetMailSendDate: true,
});
}
Expand Down Expand Up @@ -165,6 +166,7 @@ export class BuyCryptoService {
await this.createEntity(entity, {
type: TransactionTypeInternal.BUY_CRYPTO,
user: buy.user,
userData: buy.userData,
});
}

Expand All @@ -185,6 +187,7 @@ export class BuyCryptoService {
{
type: TransactionTypeInternal.CRYPTO_CRYPTO,
user: swap.user,
userData: swap.userData,
},
request,
);
Expand Down Expand Up @@ -601,7 +604,7 @@ export class BuyCryptoService {

// transaction
request = await this.getAndCompleteTxRequest(entity, request);
entity.transaction = await this.transactionService.update(entity.transaction.id, { ...dto, request });
entity.transaction = await this.transactionService.updateInternal(entity.transaction, { ...dto, request });

entity = await this.buyCryptoRepo.save(entity);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const defaultBuyHistory: BuyHistoryDto = {
date: new Date(),
isComplete: false,
txId: 'TX_ID_01',
txUrl: 'https://defiscan.live/transactions/TX_ID_01',
txUrl: 'https://etherscan.io/tx/TX_ID_01',
amlCheck: CheckStatus.PASS,
status: PaymentStatus.PENDING,
};
Expand Down
7 changes: 7 additions & 0 deletions src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.e
import { Asset } from 'src/shared/models/asset/asset.entity';
import { BuyCrypto } from 'src/subdomains/core/buy-crypto/process/entities/buy-crypto.entity';
import { Route } from 'src/subdomains/core/route/route.entity';
import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity';
import { User } from 'src/subdomains/generic/user/models/user/user.entity';
import { CryptoInput } from 'src/subdomains/supporting/payin/entities/crypto-input.entity';
import { ChildEntity, Column, JoinColumn, ManyToOne, OneToMany, OneToOne } from 'typeorm';
Expand Down Expand Up @@ -42,4 +43,10 @@ export class Swap extends DepositRoute {

@OneToMany(() => CryptoInput, (cryptoInput) => cryptoInput.route)
cryptoInputs: CryptoInput[];

// --- ENTITY METHODS --- //

get userData(): UserData {
return this.user.userData;
}
}
159 changes: 159 additions & 0 deletions src/subdomains/core/history/__tests__/transaction.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { createMock } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { JwtPayload } from 'src/shared/auth/jwt-payload.interface';
import { UserRole } from 'src/shared/auth/user-role.enum';
import { FiatService } from 'src/shared/models/fiat/fiat.service';
import { TestSharedModule } from 'src/shared/utils/test.shared.module';
import { TestUtil } from 'src/shared/utils/test.util';
import { BuyCryptoService } from 'src/subdomains/core/buy-crypto/process/services/buy-crypto.service';
import { BankDataService } from 'src/subdomains/generic/user/models/bank-data/bank-data.service';
import { UserDataService } from 'src/subdomains/generic/user/models/user-data/user-data.service';
import { BankTxReturnService } from 'src/subdomains/supporting/bank-tx/bank-tx-return/bank-tx-return.service';
import { createDefaultBankTx } from 'src/subdomains/supporting/bank-tx/bank-tx/__mocks__/bank-tx.entity.mock';
import { BankTxService } from 'src/subdomains/supporting/bank-tx/bank-tx/services/bank-tx.service';
import { createDefaultCryptoInput } from 'src/subdomains/supporting/payin/entities/__mocks__/crypto-input.entity.mock';
import { createCustomTransaction } from 'src/subdomains/supporting/payment/__mocks__/transaction.entity.mock';
import { FeeService } from 'src/subdomains/supporting/payment/services/fee.service';
import { SpecialExternalAccountService } from 'src/subdomains/supporting/payment/services/special-external-account.service';
import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service';
import { CheckStatus } from '../../aml/enums/check-status.enum';
import { createCustomBuyCrypto } from '../../buy-crypto/process/entities/__mocks__/buy-crypto.entity.mock';
import { BuyCryptoWebhookService } from '../../buy-crypto/process/services/buy-crypto-webhook.service';
import { BuyService } from '../../buy-crypto/routes/buy/buy.service';
import { RefRewardService } from '../../referral/reward/services/ref-reward.service';
import { createCustomBuyFiat } from '../../sell-crypto/process/__mocks__/buy-fiat.entity.mock';
import { BuyFiatService } from '../../sell-crypto/process/services/buy-fiat.service';
import { TransactionUtilService } from '../../transaction/transaction-util.service';
import { TransactionController } from '../controllers/transaction.controller';
import { HistoryService } from '../services/history.service';

describe('TransactionController', () => {
let controller: TransactionController;

let historyService: HistoryService;
let transactionService: TransactionService;
let buyCryptoWebhookService: BuyCryptoWebhookService;
let buyFiatService: BuyFiatService;
let refRewardService: RefRewardService;
let bankDataService: BankDataService;
let bankTxService: BankTxService;
let fiatService: FiatService;
let buyService: BuyService;
let buyCryptoService: BuyCryptoService;
let feeService: FeeService;
let transactionUtilService: TransactionUtilService;
let userDataService: UserDataService;
let bankTxReturnService: BankTxReturnService;
let specialExternalAccountService: SpecialExternalAccountService;

beforeEach(async () => {
historyService = createMock<HistoryService>();
transactionService = createMock<TransactionService>();
buyCryptoWebhookService = createMock<BuyCryptoWebhookService>();
buyFiatService = createMock<BuyFiatService>();
refRewardService = createMock<RefRewardService>();
bankDataService = createMock<BankDataService>();
bankTxService = createMock<BankTxService>();
fiatService = createMock<FiatService>();
buyService = createMock<BuyService>();
buyCryptoService = createMock<BuyCryptoService>();
feeService = createMock<FeeService>();
transactionUtilService = createMock<TransactionUtilService>();
userDataService = createMock<UserDataService>();
bankTxReturnService = createMock<BankTxReturnService>();
specialExternalAccountService = createMock<SpecialExternalAccountService>();

const module: TestingModule = await Test.createTestingModule({
imports: [TestSharedModule],
providers: [
TransactionController,
{ provide: HistoryService, useValue: historyService },
{ provide: TransactionService, useValue: transactionService },
{ provide: BuyCryptoWebhookService, useValue: buyCryptoWebhookService },
{ provide: BuyFiatService, useValue: buyFiatService },
{ provide: RefRewardService, useValue: refRewardService },
{ provide: BankDataService, useValue: bankDataService },
{ provide: BankTxService, useValue: bankTxService },
{ provide: FiatService, useValue: fiatService },
{ provide: BuyService, useValue: buyService },
{ provide: BuyCryptoService, useValue: buyCryptoService },
{ provide: FeeService, useValue: feeService },
{ provide: TransactionUtilService, useValue: transactionUtilService },
{ provide: UserDataService, useValue: userDataService },
{ provide: BankTxReturnService, useValue: bankTxReturnService },
{ provide: SpecialExternalAccountService, useValue: specialExternalAccountService },
TestUtil.provideConfig(),
],
}).compile();

controller = module.get<TransactionController>(TransactionController);
});

const jwt: JwtPayload = {
role: UserRole.ACCOUNT,
ip: '1.1.1.1',
account: 1,
};

it('should be defined', () => {
expect(controller).toBeDefined();
});

it('should return buyCrypto refund data', async () => {
jest.spyOn(transactionService, 'getTransactionById').mockResolvedValue(
createCustomTransaction({
buyCrypto: createCustomBuyCrypto({
amlCheck: CheckStatus.FAIL,
bankTx: createDefaultBankTx(),
}),
}),
);

jest.spyOn(specialExternalAccountService, 'getMultiAccountIbans').mockResolvedValue([]);
jest.spyOn(transactionUtilService, 'validateChargebackIban').mockResolvedValue(true);

await expect(controller.getTransactionRefund(jwt, '1')).resolves.toMatchObject({
fee: { network: 0, bank: 0 },
refundAmount: 100,
refundTarget: 'DE12500105170648489890',
});
});

it('should return cryptoCrypto refund data', async () => {
jest.spyOn(transactionService, 'getTransactionById').mockResolvedValue(
createCustomTransaction({
buyCrypto: createCustomBuyCrypto({
amlCheck: CheckStatus.FAIL,
cryptoInput: createDefaultCryptoInput(),
}),
}),
);

jest.spyOn(feeService, 'getBlockchainFee').mockResolvedValue(0.01);

await expect(controller.getTransactionRefund(jwt, '1')).resolves.toMatchObject({
fee: { network: 0.01, bank: 0 },
refundAmount: 99.99,
refundTarget: undefined,
});
});

it('should return buyFiat refund data', async () => {
jest.spyOn(transactionService, 'getTransactionById').mockResolvedValue(
createCustomTransaction({
buyFiat: createCustomBuyFiat({
amlCheck: CheckStatus.FAIL,
cryptoInput: createDefaultCryptoInput(),
}),
}),
);

jest.spyOn(feeService, 'getBlockchainFee').mockResolvedValue(0.01);

await expect(controller.getTransactionRefund(jwt, '1')).resolves.toMatchObject({
fee: { network: 0.01, bank: 0 },
refundAmount: 0.09,
refundTarget: undefined,
});
});
});
Loading
Loading