mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-07-09 22:57:26 +00:00
feat: add transaction, card , and account entities
This commit is contained in:
@ -1,8 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Card } from './entities';
|
||||
import { Account } from './entities/account.entity';
|
||||
import { Transaction } from './entities/transaction.entity';
|
||||
import { CardRepository } from './repositories';
|
||||
import { AccountRepository } from './repositories/account.repository';
|
||||
import { TransactionRepository } from './repositories/transaction.repository';
|
||||
import { CardService } from './services';
|
||||
import { AccountService } from './services/account.service';
|
||||
import { TransactionService } from './services/transaction.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Card])],
|
||||
imports: [TypeOrmModule.forFeature([Card, Account, Transaction])],
|
||||
providers: [
|
||||
CardService,
|
||||
CardRepository,
|
||||
TransactionService,
|
||||
TransactionRepository,
|
||||
AccountService,
|
||||
AccountRepository,
|
||||
],
|
||||
exports: [CardService, TransactionService],
|
||||
})
|
||||
export class CardModule {}
|
||||
|
31
src/card/entities/account.entity.ts
Normal file
31
src/card/entities/account.entity.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Column, CreateDateColumn, Entity, Index, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { Card } from './card.entity';
|
||||
import { Transaction } from './transaction.entity';
|
||||
|
||||
@Entity('accounts')
|
||||
export class Account {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column('varchar', { length: 255, nullable: false, unique: true, name: 'account_reference' })
|
||||
@Index({ unique: true })
|
||||
accountReference!: string;
|
||||
|
||||
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
||||
currency!: string;
|
||||
|
||||
@Column('decimal', { precision: 10, scale: 2, default: 0.0, name: 'balance' })
|
||||
balance!: number;
|
||||
|
||||
@OneToMany(() => Card, (card) => card.account, { cascade: true })
|
||||
cards!: Card[];
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.account, { cascade: true })
|
||||
transactions!: Transaction[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
|
||||
updatedAt!: Date;
|
||||
}
|
@ -5,13 +5,16 @@ import {
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { Customer } from '~/customer/entities';
|
||||
import { CardColors, CardIssuers, CardScheme, CardStatus, CustomerType } from '../enums';
|
||||
import { Account } from './account.entity';
|
||||
import { Transaction } from './transaction.entity';
|
||||
|
||||
@Entity()
|
||||
@Entity('cards')
|
||||
export class Card {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
@ -20,14 +23,14 @@ export class Card {
|
||||
@Column({ name: 'card_reference', nullable: false, type: 'varchar' })
|
||||
cardReference!: string;
|
||||
|
||||
@Column({ length: 5, name: 'first_five_digits', nullable: false, type: 'varchar' })
|
||||
firstFiveDigits!: string;
|
||||
@Column({ length: 6, name: 'first_six_digits', nullable: false, type: 'varchar' })
|
||||
firstSixDigits!: string;
|
||||
|
||||
@Column({ length: 4, name: 'last_four_digits', nullable: false, type: 'varchar' })
|
||||
lastFourDigits!: string;
|
||||
|
||||
@Column({ type: 'date', nullable: false })
|
||||
expiry!: Date;
|
||||
@Column({ type: 'varchar', nullable: false })
|
||||
expiry!: string;
|
||||
|
||||
@Column({ type: 'varchar', nullable: false, name: 'customer_type' })
|
||||
customerType!: CustomerType;
|
||||
@ -50,6 +53,9 @@ export class Card {
|
||||
@Column({ type: 'uuid', name: 'parent_id', nullable: true })
|
||||
parentId?: string;
|
||||
|
||||
@Column({ type: 'uuid', name: 'account_id', nullable: false })
|
||||
accountId!: string;
|
||||
|
||||
@ManyToOne(() => Customer, (customer) => customer.childCards)
|
||||
@JoinColumn({ name: 'parent_id' })
|
||||
parentCustomer?: Customer;
|
||||
@ -58,6 +64,13 @@ export class Card {
|
||||
@JoinColumn({ name: 'customer_id' })
|
||||
customer!: Customer;
|
||||
|
||||
@ManyToOne(() => Account, (account) => account.cards, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'account_id' })
|
||||
account!: Account;
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.card, { cascade: true })
|
||||
transactions!: Transaction[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
||||
createdAt!: Date;
|
||||
|
||||
|
62
src/card/entities/transaction.entity.ts
Normal file
62
src/card/entities/transaction.entity.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { TransactionType } from '../enums';
|
||||
import { Account } from './account.entity';
|
||||
import { Card } from './card.entity';
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ name: 'card_reference', nullable: true, type: 'varchar' })
|
||||
cardReference!: string;
|
||||
|
||||
@Column({ name: 'transaction_type', type: 'varchar', default: TransactionType.EXTERNAL })
|
||||
transactionType!: TransactionType;
|
||||
|
||||
@Column({ name: 'account_reference', nullable: true, type: 'varchar' })
|
||||
accountReference!: string;
|
||||
|
||||
@Column({ name: 'transaction_id', unique: true, nullable: true, type: 'varchar' })
|
||||
transactionId!: string;
|
||||
|
||||
@Column({ name: 'card_masked_number', nullable: true, type: 'varchar' })
|
||||
cardMaskedNumber!: string;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', name: 'transaction_date', nullable: true })
|
||||
transactionDate!: Date;
|
||||
|
||||
@Column({ name: 'rrn', nullable: true, type: 'varchar' })
|
||||
rrn!: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, name: 'transaction_amount' })
|
||||
transactionAmount!: number;
|
||||
|
||||
@Column({ type: 'varchar', name: 'transaction_currency' })
|
||||
transactionCurrency!: string;
|
||||
|
||||
@Column({ type: 'decimal', name: 'billing_amount', precision: 12, scale: 2 })
|
||||
billingAmount!: number;
|
||||
|
||||
@Column({ type: 'decimal', name: 'settlement_amount', precision: 12, scale: 2 })
|
||||
settlementAmount!: number;
|
||||
|
||||
@Column({ type: 'decimal', name: 'fees', precision: 12, scale: 2 })
|
||||
fees!: number;
|
||||
|
||||
@Column({ name: 'card_id', type: 'uuid', nullable: true })
|
||||
cardId!: string;
|
||||
|
||||
@Column({ name: 'account_id', type: 'uuid', nullable: true })
|
||||
accountId!: string;
|
||||
|
||||
@ManyToOne(() => Card, (card) => card.transactions, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn({ name: 'card_id' })
|
||||
card!: Card;
|
||||
|
||||
@ManyToOne(() => Account, (account) => account.transactions, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn({ name: 'account_id' })
|
||||
account!: Account;
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
||||
createdAt!: Date;
|
||||
}
|
@ -3,3 +3,4 @@ export * from './card-issuers.enum';
|
||||
export * from './card-scheme.enum';
|
||||
export * from './card-status.enum';
|
||||
export * from './customer-type.enum';
|
||||
export * from './transaction-type.enum';
|
||||
|
4
src/card/enums/transaction-type.enum.ts
Normal file
4
src/card/enums/transaction-type.enum.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum TransactionType {
|
||||
INTERNAL = 'INTERNAL',
|
||||
EXTERNAL = 'EXTERNAL',
|
||||
}
|
19
src/card/repositories/account.repository.ts
Normal file
19
src/card/repositories/account.repository.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Account } from '../entities/account.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AccountRepository {
|
||||
constructor(@InjectRepository(Account) private readonly accountRepository: Repository<Account>) {}
|
||||
|
||||
createAccount(accountId: string): Promise<Account> {
|
||||
return this.accountRepository.save(
|
||||
this.accountRepository.create({
|
||||
accountReference: accountId,
|
||||
balance: 0,
|
||||
currency: '682',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
37
src/card/repositories/card.repository.ts
Normal file
37
src/card/repositories/card.repository.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||
import { Card } from '../entities';
|
||||
import { CardColors, CardIssuers, CardScheme, CardStatus, CustomerType } from '../enums';
|
||||
|
||||
@Injectable()
|
||||
export class CardRepository {
|
||||
constructor(@InjectRepository(Card) private readonly cardRepository: Repository<Card>) {}
|
||||
|
||||
createCard(customerId: string, accountId: string, card: CreateApplicationResponse): Promise<Card> {
|
||||
return this.cardRepository.save(
|
||||
this.cardRepository.create({
|
||||
customerId: customerId,
|
||||
expiry: card.expiryDate,
|
||||
cardReference: card.cardId,
|
||||
customerType: CustomerType.PARENT,
|
||||
firstSixDigits: card.firstSixDigits,
|
||||
lastFourDigits: card.lastFourDigits,
|
||||
color: CardColors.BLUE,
|
||||
status: CardStatus.ACTIVE,
|
||||
scheme: CardScheme.VISA,
|
||||
issuer: CardIssuers.NEOLEAP,
|
||||
accountId: accountId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getCardById(id: string): Promise<Card | null> {
|
||||
return this.cardRepository.findOne({ where: { id } });
|
||||
}
|
||||
|
||||
getCardByReferenceNumber(referenceNumber: string): Promise<Card | null> {
|
||||
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
||||
}
|
||||
}
|
1
src/card/repositories/index.ts
Normal file
1
src/card/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './card.repository';
|
34
src/card/repositories/transaction.repository.ts
Normal file
34
src/card/repositories/transaction.repository.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import moment from 'moment';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CardTransactionWebhookRequest } from '~/common/modules/neoleap/dtos/requests';
|
||||
import { Card } from '../entities';
|
||||
import { Transaction } from '../entities/transaction.entity';
|
||||
import { TransactionType } from '../enums';
|
||||
|
||||
@Injectable()
|
||||
export class TransactionRepository {
|
||||
constructor(@InjectRepository(Transaction) private transactionRepository: Repository<Transaction>) {}
|
||||
|
||||
createCardTransaction(card: Card, transactionData: CardTransactionWebhookRequest): Promise<Transaction> {
|
||||
return this.transactionRepository.save(
|
||||
this.transactionRepository.create({
|
||||
transactionId: transactionData.transactionId,
|
||||
cardReference: transactionData.cardId,
|
||||
transactionAmount: transactionData.transactionAmount,
|
||||
transactionCurrency: transactionData.transactionCurrency,
|
||||
billingAmount: transactionData.billingAmount,
|
||||
settlementAmount: transactionData.settlementAmount,
|
||||
transactionDate: moment(transactionData.date + transactionData.time, 'YYYYMMDDHHmmss').toDate(),
|
||||
rrn: transactionData.rrn,
|
||||
cardMaskedNumber: transactionData.cardMaskedNumber,
|
||||
fees: transactionData.fees,
|
||||
cardId: card.id,
|
||||
accountId: card.account!.id,
|
||||
transactionType: TransactionType.EXTERNAL,
|
||||
accountReference: card.account!.accountReference,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
12
src/card/services/account.service.ts
Normal file
12
src/card/services/account.service.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Account } from '../entities/account.entity';
|
||||
import { AccountRepository } from '../repositories/account.repository';
|
||||
|
||||
@Injectable()
|
||||
export class AccountService {
|
||||
constructor(private readonly accountRepository: AccountRepository) {}
|
||||
|
||||
createAccount(accountId: string): Promise<Account> {
|
||||
return this.accountRepository.createAccount(accountId);
|
||||
}
|
||||
}
|
37
src/card/services/card.service.ts
Normal file
37
src/card/services/card.service.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Transactional } from 'typeorm-transactional';
|
||||
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||
import { Card } from '../entities';
|
||||
import { CardRepository } from '../repositories';
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
@Injectable()
|
||||
export class CardService {
|
||||
constructor(private readonly cardRepository: CardRepository, private readonly accountService: AccountService) {}
|
||||
|
||||
@Transactional()
|
||||
async createCard(customerId: string, cardData: CreateApplicationResponse): Promise<Card> {
|
||||
const account = await this.accountService.createAccount(cardData.accountId);
|
||||
return this.cardRepository.createCard(customerId, account.id, cardData);
|
||||
}
|
||||
|
||||
async getCardById(id: string): Promise<Card> {
|
||||
const card = await this.cardRepository.getCardById(id);
|
||||
|
||||
if (!card) {
|
||||
throw new BadRequestException('CARD.NOT_FOUND');
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
async getCardByReferenceNumber(referenceNumber: string): Promise<Card> {
|
||||
const card = await this.cardRepository.getCardByReferenceNumber(referenceNumber);
|
||||
|
||||
if (!card) {
|
||||
throw new BadRequestException('CARD.NOT_FOUND');
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
1
src/card/services/index.ts
Normal file
1
src/card/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './card.service';
|
16
src/card/services/transaction.service.ts
Normal file
16
src/card/services/transaction.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CardTransactionWebhookRequest } from '~/common/modules/neoleap/dtos/requests';
|
||||
import { TransactionRepository } from '../repositories/transaction.repository';
|
||||
import { CardService } from './card.service';
|
||||
|
||||
@Injectable()
|
||||
export class TransactionService {
|
||||
constructor(
|
||||
private readonly transactionRepository: TransactionRepository,
|
||||
private readonly cardService: CardService,
|
||||
) {}
|
||||
async createCardTransaction(body: CardTransactionWebhookRequest) {
|
||||
const card = await this.cardService.getCardByReferenceNumber(body.cardId);
|
||||
return this.transactionRepository.createCardTransaction(card, body);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { TransactionService } from '~/card/services/transaction.service';
|
||||
import { CardTransactionWebhookRequest } from '../dtos/requests';
|
||||
|
||||
@Controller('neoleap-webhooks')
|
||||
@ApiTags('Neoleap Webhooks')
|
||||
export class NeoLeapWebhooksController {
|
||||
constructor(private readonly transactionService: TransactionService) {}
|
||||
@Post('account-transaction')
|
||||
async handleAccountTransactionWebhook(@Body() body: CardTransactionWebhookRequest) {
|
||||
await this.transactionService.createCardTransaction(body);
|
||||
}
|
||||
}
|
@ -1,22 +1,39 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { CardService } from '~/card/services';
|
||||
import { ApiDataResponse } from '~/core/decorators';
|
||||
import { ResponseFactory } from '~/core/utils';
|
||||
import { Customer } from '~/customer/entities';
|
||||
import { CustomerService } from '~/customer/services';
|
||||
import { CreateApplicationResponse, InquireApplicationResponse } from '../dtos/response';
|
||||
import { NeoLeapService } from '../services/neoleap.service';
|
||||
|
||||
@Controller('neotest')
|
||||
@ApiTags('Neoleap Test API , for testing purposes only, will be removed in production')
|
||||
export class NeoTestController {
|
||||
constructor(private readonly neoleapService: NeoLeapService, private readonly customerService: CustomerService) {}
|
||||
constructor(
|
||||
private readonly neoleapService: NeoLeapService,
|
||||
private readonly customerService: CustomerService,
|
||||
private readonly cardService: CardService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
@Get('inquire-application')
|
||||
@ApiDataResponse(InquireApplicationResponse)
|
||||
async inquireApplication() {
|
||||
return this.neoleapService.inquireApplication('1');
|
||||
const data = await this.neoleapService.inquireApplication('15');
|
||||
return ResponseFactory.data(data);
|
||||
}
|
||||
|
||||
@Get('create-application')
|
||||
@ApiDataResponse(CreateApplicationResponse)
|
||||
async createApplication() {
|
||||
const customer = await this.customerService.findAnyCustomer();
|
||||
return this.neoleapService.createApplication(customer as Customer);
|
||||
const customer = await this.customerService.findCustomerById(
|
||||
this.configService.get<string>('MOCK_CUSTOMER_ID', '0778c431-f604-4b91-af53-49c33849b5ff'),
|
||||
);
|
||||
const data = await this.neoleapService.createApplication(customer as Customer);
|
||||
await this.cardService.createCard(customer.id, data);
|
||||
return ResponseFactory.data(data);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Expose, Type } from 'class-transformer';
|
||||
import { IsNumber, IsString, ValidateNested } from 'class-validator';
|
||||
|
||||
export class CardAcceptorLocationDto {
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
merchantId!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
merchantName!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
merchantCountry!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
merchantCity!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
mcc!: string;
|
||||
}
|
||||
|
||||
export class CardTransactionWebhookRequest {
|
||||
@Expose({ name: 'InstId' })
|
||||
@IsString()
|
||||
@ApiProperty({ name: 'InstId', example: '1100' })
|
||||
instId!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '30829' })
|
||||
cardId!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '1234567890123456' })
|
||||
transactionId!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '277012*****3456' })
|
||||
cardMaskedNumber!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '1234567890123456' })
|
||||
accountNumber!: string;
|
||||
|
||||
@Expose({ name: 'Date' })
|
||||
@IsString()
|
||||
@ApiProperty({ name: 'Date', example: '20241112' })
|
||||
date!: string;
|
||||
|
||||
@Expose({ name: 'Time' })
|
||||
@IsString()
|
||||
@ApiProperty({ name: 'Time', example: '125250' })
|
||||
time!: string;
|
||||
|
||||
@Expose()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '132' })
|
||||
otb!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '0' })
|
||||
transactionCode!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '1' })
|
||||
messageClass!: string;
|
||||
|
||||
@Expose({ name: 'RRN' })
|
||||
@IsString()
|
||||
@ApiProperty({ name: 'RRN', example: '431712003306' })
|
||||
rrn!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '3306' })
|
||||
stan!: string;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => CardAcceptorLocationDto)
|
||||
@ApiProperty({ type: CardAcceptorLocationDto })
|
||||
cardAcceptorLocation!: CardAcceptorLocationDto;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '100.5' })
|
||||
transactionAmount!: number;
|
||||
|
||||
@IsString()
|
||||
@ApiProperty({ example: '682' })
|
||||
transactionCurrency!: string;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '100.5' })
|
||||
billingAmount!: number;
|
||||
|
||||
@IsString()
|
||||
@ApiProperty({ example: '682' })
|
||||
billingCurrency!: string;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '100.5' })
|
||||
settlementAmount!: number;
|
||||
|
||||
@IsString()
|
||||
@ApiProperty({ example: '682' })
|
||||
settlementCurrency!: string;
|
||||
|
||||
@Expose()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '20' })
|
||||
fees!: number;
|
||||
|
||||
@Expose()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@ApiProperty({ example: '4.5' })
|
||||
vatOnFees!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '9' })
|
||||
posEntryMode!: string;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '036657' })
|
||||
authIdResponse!: string;
|
||||
|
||||
@Expose({ name: 'POSCDIM' })
|
||||
@IsString()
|
||||
@ApiProperty({ name: 'POSCDIM', example: '9' })
|
||||
posCdim!: string;
|
||||
}
|
1
src/common/modules/neoleap/dtos/requests/index.ts
Normal file
1
src/common/modules/neoleap/dtos/requests/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './card-transaction-webhook.request.dto';
|
@ -1,12 +1,40 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
import { InquireApplicationResponse } from './inquire-application.response';
|
||||
|
||||
export class CreateApplicationResponse extends InquireApplicationResponse {
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ResponseCardIdentifier.Id)
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ResponseCardIdentifier.Id.toString())
|
||||
@Expose()
|
||||
cardId!: number;
|
||||
@ApiProperty()
|
||||
cardId!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ResponseCardIdentifier.VPan)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
vpan!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ExpiryDate)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
expiryDate!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.CardStatus)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
cardStatus!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ResponseCardIdentifier?.MaskedPan?.split('_')[0])
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
firstSixDigits!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.CardDetailsList?.[0]?.ResponseCardIdentifier?.MaskedPan?.split('_')[1])
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
lastFourDigits!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.AccountDetailsList?.[0]?.Id.toString())
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
accountId!: string;
|
||||
}
|
||||
|
@ -1,143 +1,179 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
|
||||
export class InquireApplicationResponse {
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ApplicationNumber)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
applicationNumber!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ExternalApplicationNumber)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
externalApplicationNumber!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ApplicationStatus)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
applicationStatus!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Organization)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
organization!: number;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Product)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
product!: string;
|
||||
|
||||
// this typo is from neoleap, so we keep it as is
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ApplicatonDate)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
applicationDate!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ApplicationSource)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
applicationSource!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.SalesSource)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
salesSource!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.DeliveryMethod)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
deliveryMethod!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ProgramCode)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
ProgramCode!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Plastic)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
plastic!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Design)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
design!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ProcessStage)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
processStage!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ProcessStageStatus)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
processStageStatus!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.EligibilityCheckResult)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
eligibilityCheckResult!: string;
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.EligibilityCheckDescription)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
eligibilityCheckDescription!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Title)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
title!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.FirstName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
firstName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.SecondName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
secondName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.ThirdName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
thirdName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.LastName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
lastName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.FullName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
fullName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.EmbossName)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
embossName!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.PlaceOfBirth)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
placeOfBirth!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.DateOfBirth)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
dateOfBirth!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.LocalizedDateOfBirth)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
localizedDateOfBirth!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Age)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
age!: number;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Gender)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
gender!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails?.Married)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
married!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails.Nationality)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
nationality!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails.IdType)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
idType!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails.IdNumber)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
idNumber!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationDetails.IdExpiryDate)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
idExpiryDate!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationStatusDetails?.Description)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
applicationStatusDescription!: string;
|
||||
|
||||
@Transform(({ obj }) => obj.ApplicationStatusDetails?.Canceled)
|
||||
@Expose()
|
||||
@ApiProperty()
|
||||
canceled!: boolean;
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CardModule } from '~/card/card.module';
|
||||
import { CustomerModule } from '~/customer/customer.module';
|
||||
import { NeoLeapWebhooksController } from './controllers/neoleap-webhooks.controller';
|
||||
import { NeoTestController } from './controllers/neotest.controller';
|
||||
import { NeoLeapService } from './services/neoleap.service';
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule, CustomerModule],
|
||||
controllers: [NeoTestController],
|
||||
imports: [HttpModule, CustomerModule, CardModule],
|
||||
controllers: [NeoTestController, NeoLeapWebhooksController],
|
||||
providers: [NeoLeapService],
|
||||
})
|
||||
export class NeoLeapModule {}
|
||||
|
@ -29,6 +29,9 @@ export class NeoLeapService {
|
||||
|
||||
async createApplication(customer: Customer) {
|
||||
const responseKey = 'CreateNewApplicationResponseDetails';
|
||||
if (customer.cards.length > 0) {
|
||||
throw new BadRequestException('CUSTOMER.ALREADY_HAS_CARD');
|
||||
}
|
||||
if (this.useMock) {
|
||||
return plainToInstance(CreateApplicationResponse, CREATE_APPLICATION_MOCK[responseKey], {
|
||||
excludeExtraneousValues: true,
|
||||
@ -39,7 +42,7 @@ export class NeoLeapService {
|
||||
CreateNewApplicationRequestDetails: {
|
||||
ApplicationRequestDetails: {
|
||||
InstitutionCode: this.institutionCode,
|
||||
ExternalApplicationNumber: customer.waitingNumber.toString(),
|
||||
ExternalApplicationNumber: (customer.waitingNumber * 64).toString(),
|
||||
ApplicationType: '01',
|
||||
Product: '1101',
|
||||
ApplicationDate: moment().format('YYYY-MM-DD'),
|
||||
@ -66,7 +69,7 @@ export class NeoLeapService {
|
||||
FullName: customer.fullName,
|
||||
DateOfBirth: moment(customer.dateOfBirth).format('YYYY-MM-DD'),
|
||||
EmbossName: customer.fullName.toUpperCase(), // TODO Enter Emboss Name
|
||||
IdType: '001',
|
||||
IdType: '01',
|
||||
IdNumber: customer.nationalId,
|
||||
IdExpiryDate: moment(customer.nationalIdExpiry).format('YYYY-MM-DD'),
|
||||
Title: customer.gender === Gender.MALE ? 'Mr' : 'Ms',
|
||||
@ -76,7 +79,7 @@ export class NeoLeapService {
|
||||
},
|
||||
ApplicationAddress: {
|
||||
City: customer.city,
|
||||
Country: CountriesNumericISO[customer.countryOfResidence],
|
||||
Country: CountriesNumericISO[customer.country],
|
||||
Region: customer.region,
|
||||
AddressLine1: `${customer.street} ${customer.building}`,
|
||||
AddressLine2: customer.neighborhood,
|
||||
@ -174,7 +177,11 @@ export class NeoLeapService {
|
||||
return plainToInstance(responseClass, response.data[responseKey], {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error.status === 400) {
|
||||
console.error('Error sending request to NeoLeap:', error);
|
||||
throw new BadRequestException(error.response?.data?.ResponseHeader?.ResponseDescription || error.message);
|
||||
}
|
||||
console.error('Error sending request to NeoLeap:', error);
|
||||
throw new InternalServerErrorException('Error communicating with NeoLeap service');
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export function buildValidationPipe(config: ConfigService): ValidationPipe {
|
||||
transform: true,
|
||||
validateCustomDecorators: true,
|
||||
stopAtFirstError: true,
|
||||
forbidNonWhitelisted: true,
|
||||
forbidNonWhitelisted: false,
|
||||
dismissDefaultMessages: true,
|
||||
enableDebugMessages: config.getOrThrow('NODE_ENV') === Environment.DEV,
|
||||
exceptionFactory: i18nValidationErrorFactory,
|
||||
|
@ -79,7 +79,7 @@ export class Customer extends BaseEntity {
|
||||
userId!: string;
|
||||
|
||||
@Column('varchar', { name: 'country', length: 255, nullable: true })
|
||||
country!: string;
|
||||
country!: CountryIso;
|
||||
|
||||
@Column('varchar', { name: 'region', length: 255, nullable: true })
|
||||
region!: string;
|
||||
|
@ -15,7 +15,7 @@ export class CustomerRepository {
|
||||
findOne(where: FindOptionsWhere<Customer>) {
|
||||
return this.customerRepository.findOne({
|
||||
where,
|
||||
relations: ['profilePicture', 'user', 'civilIdFront', 'civilIdBack'],
|
||||
relations: ['profilePicture', 'user', 'civilIdFront', 'civilIdBack', 'cards'],
|
||||
});
|
||||
}
|
||||
|
||||
|
40
src/db/migrations/1749633935436-create-card-entity.ts
Normal file
40
src/db/migrations/1749633935436-create-card-entity.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateCardEntity1749633935436 implements MigrationInterface {
|
||||
name = 'CreateCardEntity1749633935436';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "cards"
|
||||
("id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"card_reference" character varying NOT NULL,
|
||||
"first_six_digits" character varying(6) NOT NULL,
|
||||
"last_four_digits" character varying(4) NOT NULL,
|
||||
"expiry" character varying NOT NULL,
|
||||
"customer_type" character varying NOT NULL,
|
||||
"color" character varying NOT NULL DEFAULT 'BLUE',
|
||||
"status" character varying NOT NULL DEFAULT 'PENDING',
|
||||
"scheme" character varying NOT NULL DEFAULT 'VISA',
|
||||
"issuer" character varying NOT NULL,
|
||||
"customer_id" uuid NOT NULL,
|
||||
"parent_id" uuid,
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_9451069b6f1199730791a7f4ae4" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_6fb82820c0b1b7ec32a8dbf911" ON "card" ("card_reference") `);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "card" ADD CONSTRAINT "FK_d46837f6ab27271d8125517d0b6" FOREIGN KEY ("parent_id") REFERENCES "customers"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "card" ADD CONSTRAINT "FK_f5aa0baf4ff1b397b3f946a443e" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "card" DROP CONSTRAINT "FK_f5aa0baf4ff1b397b3f946a443e"`);
|
||||
await queryRunner.query(`ALTER TABLE "card" DROP CONSTRAINT "FK_d46837f6ab27271d8125517d0b6"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_6fb82820c0b1b7ec32a8dbf911"`);
|
||||
await queryRunner.query(`DROP TABLE "card"`);
|
||||
}
|
||||
}
|
45
src/db/migrations/1751456987627-create-account-entity.ts
Normal file
45
src/db/migrations/1751456987627-create-account-entity.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateAccountEntity1751456987627 implements MigrationInterface {
|
||||
name = 'CreateAccountEntity1751456987627';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP CONSTRAINT "FK_f5aa0baf4ff1b397b3f946a443e"`);
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP CONSTRAINT "FK_d46837f6ab27271d8125517d0b6"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_6fb82820c0b1b7ec32a8dbf911"`);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "accounts" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "account_reference" character varying(255) NOT NULL, "currency" character varying(255) NOT NULL, "balance" numeric(10,2) NOT NULL DEFAULT '0', "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "UQ_829183fe026a0ce5fc8026b2417" UNIQUE ("account_reference"), CONSTRAINT "PK_5a7a02c20412299d198e097a8fe" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_829183fe026a0ce5fc8026b241" ON "accounts" ("account_reference") `,
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "cards" ADD "account_id" uuid NOT NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4f7df5c5dc950295dc417a11d8" ON "cards" ("card_reference") `);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "cards" ADD CONSTRAINT "FK_8ba18e7060c38ddae12e5bdf907" FOREIGN KEY ("parent_id") REFERENCES "customers"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "cards" ADD CONSTRAINT "FK_2fd0ee722ec57594d2e448c73d7" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "cards" ADD CONSTRAINT "FK_b2874ef49ff7da2dee49e4bc6d3" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP CONSTRAINT "FK_b2874ef49ff7da2dee49e4bc6d3"`);
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP CONSTRAINT "FK_2fd0ee722ec57594d2e448c73d7"`);
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP CONSTRAINT "FK_8ba18e7060c38ddae12e5bdf907"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_4f7df5c5dc950295dc417a11d8"`);
|
||||
await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "account_id"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_829183fe026a0ce5fc8026b241"`);
|
||||
await queryRunner.query(`DROP TABLE "accounts"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_6fb82820c0b1b7ec32a8dbf911" ON "cards" ("card_reference") `);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "cards" ADD CONSTRAINT "FK_d46837f6ab27271d8125517d0b6" FOREIGN KEY ("parent_id") REFERENCES "customers"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "cards" ADD CONSTRAINT "FK_f5aa0baf4ff1b397b3f946a443e" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
18
src/db/migrations/1751466314709-create-transaction-table.ts
Normal file
18
src/db/migrations/1751466314709-create-transaction-table.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreateTransactionTable1751466314709 implements MigrationInterface {
|
||||
name = 'CreateTransactionTable1751466314709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "transactions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "card_reference" character varying, "transaction_type" character varying NOT NULL DEFAULT 'EXTERNAL', "account_reference" character varying, "transaction_id" character varying, "card_masked_number" character varying, "transaction_date" TIMESTAMP WITH TIME ZONE, "rrn" character varying, "transaction_amount" numeric(12,2) NOT NULL, "transaction_currency" character varying NOT NULL, "billing_amount" numeric(12,2) NOT NULL, "settlement_amount" numeric(12,2) NOT NULL, "fees" numeric(12,2) NOT NULL, "card_id" uuid, "account_id" uuid, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "UQ_9162bf9ab4e31961a8f7932974c" UNIQUE ("transaction_id"), CONSTRAINT "PK_a219afd8dd77ed80f5a862f1db9" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "transactions" ADD CONSTRAINT "FK_80ad48141be648db2d84ff32f79" FOREIGN KEY ("card_id") REFERENCES "cards"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "transactions" ADD CONSTRAINT "FK_49c0d6e8ba4bfb5582000d851f0" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "transactions" DROP CONSTRAINT "FK_49c0d6e8ba4bfb5582000d851f0"`);
|
||||
await queryRunner.query(`ALTER TABLE "transactions" DROP CONSTRAINT "FK_80ad48141be648db2d84ff32f79"`);
|
||||
await queryRunner.query(`DROP TABLE "transactions"`);
|
||||
}
|
||||
|
||||
}
|
@ -25,3 +25,6 @@ export * from './1740045960580-create-user-registration-table';
|
||||
export * from './1741087742821-add-used-flag-to-otp-and-remove-constraints-from-customers';
|
||||
export * from './1742112997024-update-customer-table';
|
||||
export * from './1747569536067-add-address-fields-to-customers';
|
||||
export * from './1749633935436-create-card-entity';
|
||||
export * from './1751456987627-create-account-entity';
|
||||
export * from './1751466314709-create-transaction-table';
|
||||
|
Reference in New Issue
Block a user