From fce720237f5df89577a7a0f0ca63761b710cf3dc Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Sun, 3 Aug 2025 11:53:16 +0300 Subject: [PATCH] feat: add vpan to card entity --- src/card/entities/card.entity.ts | 4 ++++ src/card/repositories/card.repository.ts | 5 +++-- src/card/services/card.service.ts | 6 ++--- src/card/services/transaction.service.ts | 2 +- .../neoleap-webhooks.controller.ts | 10 ++++++--- .../1754210729273-add-vpan-to-card.ts | 22 +++++++++++++++++++ src/db/migrations/index.ts | 1 + 7 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/db/migrations/1754210729273-add-vpan-to-card.ts diff --git a/src/card/entities/card.entity.ts b/src/card/entities/card.entity.ts index 53ac864..72ce7a0 100644 --- a/src/card/entities/card.entity.ts +++ b/src/card/entities/card.entity.ts @@ -23,6 +23,10 @@ export class Card { @Column({ name: 'card_reference', nullable: false, type: 'varchar' }) cardReference!: string; + @Index({ unique: true }) + @Column({ name: 'vpan', nullable: false, type: 'varchar' }) + vpan!: string; + @Column({ length: 6, name: 'first_six_digits', nullable: false, type: 'varchar' }) firstSixDigits!: string; diff --git a/src/card/repositories/card.repository.ts b/src/card/repositories/card.repository.ts index d5bc71d..a6e8cb6 100644 --- a/src/card/repositories/card.repository.ts +++ b/src/card/repositories/card.repository.ts @@ -22,6 +22,7 @@ export class CardRepository { scheme: CardScheme.VISA, issuer: CardIssuers.NEOLEAP, accountId: accountId, + vpan: card.vpan, }), ); } @@ -34,9 +35,9 @@ export class CardRepository { return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] }); } - getCardByAccountNumber(accountNumber: string): Promise { + getCardByVpan(vpan: string): Promise { return this.cardRepository.findOne({ - where: { account: { accountNumber } }, + where: { vpan }, relations: ['account'], }); } diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index e2e862b..6d48d0f 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -37,8 +37,8 @@ export class CardService { return card; } - async getCardByAccountNumber(accountNumber: string): Promise { - const card = await this.cardRepository.getCardByAccountNumber(accountNumber); + async getCardByVpan(vpan: string): Promise { + const card = await this.cardRepository.getCardByVpan(vpan); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); @@ -55,7 +55,7 @@ export class CardService { } async updateCardStatus(body: AccountCardStatusChangedWebhookRequest) { - const card = await this.getCardByAccountNumber(body.cardId); + const card = await this.getCardByVpan(body.cardId); const { description, status } = CardStatusMapper[body.newStatus] || CardStatusMapper['99']; return this.cardRepository.updateCardStatus(card.id, status, description); diff --git a/src/card/services/transaction.service.ts b/src/card/services/transaction.service.ts index 17973a3..ff22aef 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.getCardByAccountNumber(body.cardId); + const card = await this.cardService.getCardByVpan(body.cardId); const existingTransaction = await this.findExistingTransaction(body.transactionId, card.account.accountReference); if (existingTransaction) { diff --git a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts b/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts index 1fb8d60..03aaf8a 100644 --- a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts +++ b/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { ResponseFactory } from '~/core/utils'; import { AccountCardStatusChangedWebhookRequest, AccountTransactionWebhookRequest, @@ -14,16 +15,19 @@ export class NeoLeapWebhooksController { @Post('card-transaction') async handleCardTransactionWebhook(@Body() body: CardTransactionWebhookRequest) { - return this.neoleapWebhookService.handleCardTransactionWebhook(body); + await this.neoleapWebhookService.handleCardTransactionWebhook(body); + return ResponseFactory.data({ message: 'Card transaction processed successfully', status: 'success' }); } @Post('account-transaction') async handleAccountTransactionWebhook(@Body() body: AccountTransactionWebhookRequest) { - return this.neoleapWebhookService.handleAccountTransactionWebhook(body); + await this.neoleapWebhookService.handleAccountTransactionWebhook(body); + return ResponseFactory.data({ message: 'Account transaction processed successfully', status: 'success' }); } @Post('account-card-status-changed') async handleAccountCardStatusChangedWebhook(@Body() body: AccountCardStatusChangedWebhookRequest) { - return this.neoleapWebhookService.handleAccountCardStatusChangedWebhook(body); + await this.neoleapWebhookService.handleAccountCardStatusChangedWebhook(body); + return ResponseFactory.data({ message: 'Card status updated successfully', status: 'success' }); } } diff --git a/src/db/migrations/1754210729273-add-vpan-to-card.ts b/src/db/migrations/1754210729273-add-vpan-to-card.ts new file mode 100644 index 0000000..03d7beb --- /dev/null +++ b/src/db/migrations/1754210729273-add-vpan-to-card.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddVpanToCard1754210729273 implements MigrationInterface { + name = 'AddVpanToCard1754210729273'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cards" ADD "vpan" character varying`); + await queryRunner.query(` + UPDATE "cards" + SET "vpan" = 'TEMP_VPAN_' || id + `); + + await queryRunner.query(`ALTER TABLE "cards" ALTER COLUMN "vpan" SET NOT NULL`); + + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_1ec2ef68b0370f26639261e87b" ON "cards" ("vpan") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_1ec2ef68b0370f26639261e87b"`); + await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "vpan"`); + } +} diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index 8f22bfe..63f3865 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -4,3 +4,4 @@ export * from './1734247702310-seeds-goals-categories'; export * from './1753869637732-seed-default-avatar'; export * from './1753874205042-add-neoleap-related-entities'; export * from './1753948642040-add-account-number-and-iban-to-account-entity'; +export * from './1754210729273-add-vpan-to-card';