feat: add vpan to card entity

This commit is contained in:
Abdalhamid Alhamad
2025-08-03 11:53:16 +03:00
parent 5e0a4e6bd1
commit fce720237f
7 changed files with 41 additions and 9 deletions

View File

@ -23,6 +23,10 @@ export class Card {
@Column({ name: 'card_reference', nullable: false, type: 'varchar' }) @Column({ name: 'card_reference', nullable: false, type: 'varchar' })
cardReference!: string; 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' }) @Column({ length: 6, name: 'first_six_digits', nullable: false, type: 'varchar' })
firstSixDigits!: string; firstSixDigits!: string;

View File

@ -22,6 +22,7 @@ export class CardRepository {
scheme: CardScheme.VISA, scheme: CardScheme.VISA,
issuer: CardIssuers.NEOLEAP, issuer: CardIssuers.NEOLEAP,
accountId: accountId, accountId: accountId,
vpan: card.vpan,
}), }),
); );
} }
@ -34,9 +35,9 @@ export class CardRepository {
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] }); return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
} }
getCardByAccountNumber(accountNumber: string): Promise<Card | null> { getCardByVpan(vpan: string): Promise<Card | null> {
return this.cardRepository.findOne({ return this.cardRepository.findOne({
where: { account: { accountNumber } }, where: { vpan },
relations: ['account'], relations: ['account'],
}); });
} }

View File

@ -37,8 +37,8 @@ export class CardService {
return card; return card;
} }
async getCardByAccountNumber(accountNumber: string): Promise<Card> { async getCardByVpan(vpan: string): Promise<Card> {
const card = await this.cardRepository.getCardByAccountNumber(accountNumber); const card = await this.cardRepository.getCardByVpan(vpan);
if (!card) { if (!card) {
throw new BadRequestException('CARD.NOT_FOUND'); throw new BadRequestException('CARD.NOT_FOUND');
@ -55,7 +55,7 @@ export class CardService {
} }
async updateCardStatus(body: AccountCardStatusChangedWebhookRequest) { 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']; const { description, status } = CardStatusMapper[body.newStatus] || CardStatusMapper['99'];
return this.cardRepository.updateCardStatus(card.id, status, description); return this.cardRepository.updateCardStatus(card.id, status, description);

View File

@ -20,7 +20,7 @@ export class TransactionService {
@Transactional() @Transactional()
async createCardTransaction(body: CardTransactionWebhookRequest) { 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); const existingTransaction = await this.findExistingTransaction(body.transactionId, card.account.accountReference);
if (existingTransaction) { if (existingTransaction) {

View File

@ -1,5 +1,6 @@
import { Body, Controller, Post } from '@nestjs/common'; import { Body, Controller, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { ResponseFactory } from '~/core/utils';
import { import {
AccountCardStatusChangedWebhookRequest, AccountCardStatusChangedWebhookRequest,
AccountTransactionWebhookRequest, AccountTransactionWebhookRequest,
@ -14,16 +15,19 @@ export class NeoLeapWebhooksController {
@Post('card-transaction') @Post('card-transaction')
async handleCardTransactionWebhook(@Body() body: CardTransactionWebhookRequest) { 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') @Post('account-transaction')
async handleAccountTransactionWebhook(@Body() body: AccountTransactionWebhookRequest) { 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') @Post('account-card-status-changed')
async handleAccountCardStatusChangedWebhook(@Body() body: AccountCardStatusChangedWebhookRequest) { 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' });
} }
} }

View File

@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddVpanToCard1754210729273 implements MigrationInterface {
name = 'AddVpanToCard1754210729273';
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
await queryRunner.query(`DROP INDEX "public"."IDX_1ec2ef68b0370f26639261e87b"`);
await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "vpan"`);
}
}

View File

@ -4,3 +4,4 @@ export * from './1734247702310-seeds-goals-categories';
export * from './1753869637732-seed-default-avatar'; export * from './1753869637732-seed-default-avatar';
export * from './1753874205042-add-neoleap-related-entities'; export * from './1753874205042-add-neoleap-related-entities';
export * from './1753948642040-add-account-number-and-iban-to-account-entity'; export * from './1753948642040-add-account-number-and-iban-to-account-entity';
export * from './1754210729273-add-vpan-to-card';