diff --git a/src/card/entities/account.entity.ts b/src/card/entities/account.entity.ts index cdd7847..4a1ffcb 100644 --- a/src/card/entities/account.entity.ts +++ b/src/card/entities/account.entity.ts @@ -11,6 +11,14 @@ export class Account { @Index({ unique: true }) accountReference!: string; + @Index({ unique: true }) + @Column('varchar', { length: 255, nullable: false, name: 'account_number' }) + accountNumber!: string; + + @Index({ unique: true }) + @Column('varchar', { length: 255, nullable: false, name: 'iban' }) + iban!: string; + @Column('varchar', { length: 255, nullable: false, name: 'currency' }) currency!: string; diff --git a/src/card/repositories/account.repository.ts b/src/card/repositories/account.repository.ts index da15be6..428b109 100644 --- a/src/card/repositories/account.repository.ts +++ b/src/card/repositories/account.repository.ts @@ -1,16 +1,19 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response'; import { Account } from '../entities/account.entity'; @Injectable() export class AccountRepository { constructor(@InjectRepository(Account) private readonly accountRepository: Repository) {} - createAccount(accountId: string): Promise { + createAccount(data: CreateApplicationResponse): Promise { return this.accountRepository.save( this.accountRepository.create({ - accountReference: accountId, + accountReference: data.accountId, + accountNumber: data.accountNumber, + iban: data.iBan, balance: 0, currency: '682', }), @@ -24,6 +27,13 @@ export class AccountRepository { }); } + getAccountByAccountNumber(accountNumber: string): Promise { + return this.accountRepository.findOne({ + where: { accountNumber }, + relations: ['cards'], + }); + } + topUpAccountBalance(accountReference: string, amount: number) { return this.accountRepository.increment({ accountReference }, 'balance', amount); } diff --git a/src/card/repositories/card.repository.ts b/src/card/repositories/card.repository.ts index d747feb..d5bc71d 100644 --- a/src/card/repositories/card.repository.ts +++ b/src/card/repositories/card.repository.ts @@ -34,6 +34,13 @@ export class CardRepository { return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] }); } + getCardByAccountNumber(accountNumber: string): Promise { + return this.cardRepository.findOne({ + where: { account: { accountNumber } }, + relations: ['account'], + }); + } + getActiveCardForCustomer(customerId: string): Promise { return this.cardRepository.findOne({ where: { customerId, status: CardStatus.ACTIVE }, diff --git a/src/card/services/account.service.ts b/src/card/services/account.service.ts index cce8e68..ac9e0dd 100644 --- a/src/card/services/account.service.ts +++ b/src/card/services/account.service.ts @@ -1,4 +1,5 @@ import { Injectable, UnprocessableEntityException } from '@nestjs/common'; +import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response'; import { Account } from '../entities/account.entity'; import { AccountRepository } from '../repositories/account.repository'; @@ -6,8 +7,8 @@ import { AccountRepository } from '../repositories/account.repository'; export class AccountService { constructor(private readonly accountRepository: AccountRepository) {} - createAccount(accountId: string): Promise { - return this.accountRepository.createAccount(accountId); + createAccount(data: CreateApplicationResponse): Promise { + return this.accountRepository.createAccount(data); } async getAccountByReferenceNumber(accountReference: string): Promise { @@ -18,6 +19,14 @@ export class AccountService { return account; } + async getAccountByAccountNumber(accountNumber: string): Promise { + const account = await this.accountRepository.getAccountByAccountNumber(accountNumber); + if (!account) { + throw new UnprocessableEntityException('ACCOUNT.NOT_FOUND'); + } + return account; + } + async creditAccountBalance(accountReference: string, amount: number) { return this.accountRepository.topUpAccountBalance(accountReference, amount); } diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index 61f785e..0241852 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -13,7 +13,7 @@ export class CardService { @Transactional() async createCard(customerId: string, cardData: CreateApplicationResponse): Promise { - const account = await this.accountService.createAccount(cardData.accountId); + const account = await this.accountService.createAccount(cardData); return this.cardRepository.createCard(customerId, account.id, cardData); } @@ -37,6 +37,15 @@ export class CardService { return card; } + async getCardByAccountNumber(accountNumber: string): Promise { + const card = await this.cardRepository.getCardByAccountNumber(accountNumber); + + if (!card) { + throw new BadRequestException('CARD.NOT_FOUND'); + } + return card; + } + async getActiveCardForCustomer(customerId: string): Promise { const card = await this.cardRepository.getActiveCardForCustomer(customerId); if (!card) { diff --git a/src/card/services/transaction.service.ts b/src/card/services/transaction.service.ts index d512299..4db3a80 100644 --- a/src/card/services/transaction.service.ts +++ b/src/card/services/transaction.service.ts @@ -20,7 +20,7 @@ export class TransactionService { @Transactional() async createCardTransaction(body: CardTransactionWebhookRequest) { - const card = await this.cardService.getCardByReferenceNumber(body.cardId); + const card = await this.cardService.getCardByAccountNumber(body.cardId); const existingTransaction = await this.findExistingTransaction(body.transactionId, card.account.accountReference); if (existingTransaction) { diff --git a/src/common/modules/neoleap/dtos/response/create-application.response.dto.ts b/src/common/modules/neoleap/dtos/response/create-application.response.dto.ts index f135b4f..8da0c0a 100644 --- a/src/common/modules/neoleap/dtos/response/create-application.response.dto.ts +++ b/src/common/modules/neoleap/dtos/response/create-application.response.dto.ts @@ -37,4 +37,14 @@ export class CreateApplicationResponse extends InquireApplicationResponse { @Expose() @ApiProperty() accountId!: string; + + @Transform(({ obj }) => obj.AccountDetailsList?.[0]?.AccountNumber) + @Expose() + @ApiProperty() + accountNumber!: string; + + @Transform(({ obj }) => obj.AccountDetailsList?.[0]?.UserData5) + @Expose() + @ApiProperty() + iBan!: string; } diff --git a/src/db/migrations/1753948642040-add-account-number-and-iban-to-account-entity.ts b/src/db/migrations/1753948642040-add-account-number-and-iban-to-account-entity.ts new file mode 100644 index 0000000..165be69 --- /dev/null +++ b/src/db/migrations/1753948642040-add-account-number-and-iban-to-account-entity.ts @@ -0,0 +1,32 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +export class AddAccountNumberAndIbanToAccountEntity1753948642040 implements MigrationInterface { + name = 'AddAccountNumberAndIbanToAccountEntity1753948642040'; + + public async up(queryRunner: QueryRunner): Promise { + // Step 1: Add columns as nullable + await queryRunner.query(`ALTER TABLE "accounts" ADD "account_number" character varying(255)`); + await queryRunner.query(`ALTER TABLE "accounts" ADD "iban" character varying(255)`); + + // Step 2: Populate dummy values or correct ones + await queryRunner.query(` + UPDATE "accounts" + SET "account_number" = 'TEMP_ACC_' || id, + "iban" = 'TEMP_IBAN_' || id + `); + + // Step 3: Alter columns to be NOT NULL + await queryRunner.query(`ALTER TABLE "accounts" ALTER COLUMN "account_number" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "accounts" ALTER COLUMN "iban" SET NOT NULL`); + + // Step 4: Add unique indexes + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ffd1ae96513bfb2c6eada0f7d3" ON "accounts" ("account_number")`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a4b004902294416b096e7556e" ON "accounts" ("iban")`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_9a4b004902294416b096e7556e"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ffd1ae96513bfb2c6eada0f7d3"`); + await queryRunner.query(`ALTER TABLE "accounts" DROP COLUMN "iban"`); + await queryRunner.query(`ALTER TABLE "accounts" DROP COLUMN "account_number"`); + } +} diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index 9996c42..8f22bfe 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -1,5 +1,6 @@ -export * from './1753869637732-seed-default-avatar'; +export * from './1733750228289-initial-migration'; export * from './1733990253208-seeds-default-tasks-logo'; export * from './1734247702310-seeds-goals-categories'; -export * from './1733750228289-initial-migration'; +export * from './1753869637732-seed-default-avatar'; export * from './1753874205042-add-neoleap-related-entities'; +export * from './1753948642040-add-account-number-and-iban-to-account-entity';