From bf43e62b17bf651ec35fa2d0905df731fe47f805 Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Mon, 21 Jul 2025 15:30:55 +0300 Subject: [PATCH] feat: handle card status changed webhook --- src/card/entities/card.entity.ts | 8 +- .../enums/card-status-description.enum.ts | 68 +++++++++++ src/card/enums/index.ts | 1 + .../mappers/card-status-description.mapper.ts | 109 ++++++++++++++++++ src/card/mappers/card-status.mapper.ts | 37 ++++++ src/card/repositories/card.repository.ts | 10 +- src/card/services/card.service.ts | 9 ++ .../neoleap-webhooks.controller.ts | 19 ++- .../neoleap/controllers/neotest.controller.ts | 2 +- ...card-status-changed-webhook.request.dto.ts | 20 ++++ .../modules/neoleap/dtos/requests/index.ts | 1 + src/common/modules/neoleap/neoleap.module.ts | 3 +- src/common/modules/neoleap/services/index.ts | 2 + .../services/neoleap-webook.service.ts | 25 ++++ .../neoleap/services/neoleap.service.ts | 2 +- src/core/enums/user-locale.enum.ts | 12 +- .../dtos/response/customer.response.dto.ts | 2 +- src/customer/entities/customer.entity.ts | 4 +- .../1753098116701-update-card-table.ts | 16 +++ .../1753098326876-edit-customer-table.ts | 16 +++ src/db/migrations/index.ts | 2 + 21 files changed, 350 insertions(+), 18 deletions(-) create mode 100644 src/card/enums/card-status-description.enum.ts create mode 100644 src/card/mappers/card-status-description.mapper.ts create mode 100644 src/card/mappers/card-status.mapper.ts create mode 100644 src/common/modules/neoleap/dtos/requests/account-card-status-changed-webhook.request.dto.ts create mode 100644 src/common/modules/neoleap/services/neoleap-webook.service.ts create mode 100644 src/db/migrations/1753098116701-update-card-table.ts create mode 100644 src/db/migrations/1753098326876-edit-customer-table.ts diff --git a/src/card/entities/card.entity.ts b/src/card/entities/card.entity.ts index 4984a61..53ac864 100644 --- a/src/card/entities/card.entity.ts +++ b/src/card/entities/card.entity.ts @@ -10,7 +10,7 @@ import { UpdateDateColumn, } from 'typeorm'; import { Customer } from '~/customer/entities'; -import { CardColors, CardIssuers, CardScheme, CardStatus, CustomerType } from '../enums'; +import { CardColors, CardIssuers, CardScheme, CardStatus, CardStatusDescription, CustomerType } from '../enums'; import { Account } from './account.entity'; import { Transaction } from './transaction.entity'; @@ -41,6 +41,12 @@ export class Card { @Column({ type: 'varchar', nullable: false, default: CardStatus.PENDING }) status!: CardStatus; + @Column({ type: 'varchar', nullable: false, default: CardStatusDescription.PENDING_ACTIVATION }) + statusDescription!: CardStatusDescription; + + @Column({ type: 'decimal', precision: 10, scale: 2, default: 0.0, name: 'limit' }) + limit!: number; + @Column({ type: 'varchar', nullable: false, default: CardScheme.VISA }) scheme!: CardScheme; diff --git a/src/card/enums/card-status-description.enum.ts b/src/card/enums/card-status-description.enum.ts new file mode 100644 index 0000000..1c277a8 --- /dev/null +++ b/src/card/enums/card-status-description.enum.ts @@ -0,0 +1,68 @@ +/** + * import { CardStatus, CardStatusDescription } from '../enums'; + + export const CardStatusMapper: Record = { + //ACTIVE + '00': { description: 'NORMAL', status: CardStatus.ACTIVE }, + + //PENDING + '02': { description: 'NOT_YET_ISSUED', status: CardStatus.PENDING }, + '20': { description: 'PENDING_ISSUANCE', status: CardStatus.PENDING }, + '21': { description: 'CARD_EXTRACTED', status: CardStatus.PENDING }, + '22': { description: 'EXTRACTION_FAILED', status: CardStatus.PENDING }, + '23': { description: 'FAILED_PRINTING_BULK', status: CardStatus.PENDING }, + '24': { description: 'FAILED_PRINTING_INST', status: CardStatus.PENDING }, + '30': { description: 'PENDING_ACTIVATION', status: CardStatus.PENDING }, + '27': { description: 'PENDING_PIN', status: CardStatus.PENDING }, + '16': { description: 'PREPARE_TO_CLOSE', status: CardStatus.PENDING }, + + //BLOCKED + '01': { description: 'PIN_TRIES_EXCEEDED', status: CardStatus.BLOCKED }, + '03': { description: 'CARD_EXPIRED', status: CardStatus.BLOCKED }, + '04': { description: 'LOST', status: CardStatus.BLOCKED }, + '05': { description: 'STOLEN', status: CardStatus.BLOCKED }, + '06': { description: 'CUSTOMER_CLOSE', status: CardStatus.BLOCKED }, + '07': { description: 'BANK_CANCELLED', status: CardStatus.BLOCKED }, + '08': { description: 'FRAUD', status: CardStatus.BLOCKED }, + '09': { description: 'DAMAGED', status: CardStatus.BLOCKED }, + '50': { description: 'SAFE_BLOCK', status: CardStatus.BLOCKED }, + '51': { description: 'TEMPORARY_BLOCK', status: CardStatus.BLOCKED }, + '52': { description: 'RISK_BLOCK', status: CardStatus.BLOCKED }, + '53': { description: 'OVERDRAFT', status: CardStatus.BLOCKED }, + '54': { description: 'BLOCKED_FOR_FEES', status: CardStatus.BLOCKED }, + '67': { description: 'CLOSED_CUSTOMER_DEAD', status: CardStatus.BLOCKED }, + '75': { description: 'RETURN_CARD', status: CardStatus.BLOCKED }, + + //Fallback + '99': { description: 'UNKNOWN', status: CardStatus.PENDING }, + }; + + */ +export enum CardStatusDescription { + NORMAL = 'NORMAL', + NOT_YET_ISSUED = 'NOT_YET_ISSUED', + PENDING_ISSUANCE = 'PENDING_ISSUANCE', + CARD_EXTRACTED = 'CARD_EXTRACTED', + EXTRACTION_FAILED = 'EXTRACTION_FAILED', + FAILED_PRINTING_BULK = 'FAILED_PRINTING_BULK', + FAILED_PRINTING_INST = 'FAILED_PRINTING_INST', + PENDING_ACTIVATION = 'PENDING_ACTIVATION', + PENDING_PIN = 'PENDING_PIN', + PREPARE_TO_CLOSE = 'PREPARE_TO_CLOSE', + PIN_TRIES_EXCEEDED = 'PIN_TRIES_EXCEEDED', + CARD_EXPIRED = 'CARD_EXPIRED', + LOST = 'LOST', + STOLEN = 'STOLEN', + CUSTOMER_CLOSE = 'CUSTOMER_CLOSE', + BANK_CANCELLED = 'BANK_CANCELLED', + FRAUD = 'FRAUD', + DAMAGED = 'DAMAGED', + SAFE_BLOCK = 'SAFE_BLOCK', + TEMPORARY_BLOCK = 'TEMPORARY_BLOCK', + RISK_BLOCK = 'RISK_BLOCK', + OVERDRAFT = 'OVERDRAFT', + BLOCKED_FOR_FEES = 'BLOCKED_FOR_FEES', + CLOSED_CUSTOMER_DEAD = 'CLOSED_CUSTOMER_DEAD', + RETURN_CARD = 'RETURN_CARD', + UNKNOWN = 'UNKNOWN', +} diff --git a/src/card/enums/index.ts b/src/card/enums/index.ts index cac8b22..16b52c2 100644 --- a/src/card/enums/index.ts +++ b/src/card/enums/index.ts @@ -1,6 +1,7 @@ export * from './card-colors.enum'; export * from './card-issuers.enum'; export * from './card-scheme.enum'; +export * from './card-status-description.enum'; export * from './card-status.enum'; export * from './customer-type.enum'; export * from './transaction-scope.enum'; diff --git a/src/card/mappers/card-status-description.mapper.ts b/src/card/mappers/card-status-description.mapper.ts new file mode 100644 index 0000000..85fe6d9 --- /dev/null +++ b/src/card/mappers/card-status-description.mapper.ts @@ -0,0 +1,109 @@ +import { UserLocale } from '~/core/enums'; +import { CardStatusDescription } from '../enums'; + +export const CardStatusMapper: Record = { + [CardStatusDescription.NORMAL]: { + [UserLocale.ENGLISH]: { description: 'The card is active' }, + [UserLocale.ARABIC]: { description: 'البطاقة نشطة' }, + }, + [CardStatusDescription.NOT_YET_ISSUED]: { + [UserLocale.ENGLISH]: { description: 'The card is not yet issued' }, + [UserLocale.ARABIC]: { description: 'البطاقة لم تصدر بعد' }, + }, + [CardStatusDescription.PENDING_ISSUANCE]: { + [UserLocale.ENGLISH]: { description: 'The card is pending issuance' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد الإصدار' }, + }, + [CardStatusDescription.CARD_EXTRACTED]: { + [UserLocale.ENGLISH]: { description: 'The card has been extracted' }, + [UserLocale.ARABIC]: { description: 'تم استخراج البطاقة' }, + }, + [CardStatusDescription.EXTRACTION_FAILED]: { + [UserLocale.ENGLISH]: { description: 'The card extraction has failed' }, + [UserLocale.ARABIC]: { description: 'فشل استخراج البطاقة' }, + }, + [CardStatusDescription.FAILED_PRINTING_BULK]: { + [UserLocale.ENGLISH]: { description: 'The card printing in bulk has failed' }, + [UserLocale.ARABIC]: { description: 'فشل الطباعة بالجملة للبطاقة' }, + }, + [CardStatusDescription.FAILED_PRINTING_INST]: { + [UserLocale.ENGLISH]: { description: 'The card printing in institution has failed' }, + [UserLocale.ARABIC]: { description: 'فشل الطباعة في المؤسسة للبطاقة' }, + }, + [CardStatusDescription.PENDING_ACTIVATION]: { + [UserLocale.ENGLISH]: { description: 'The card is pending activation' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد التفعيل' }, + }, + [CardStatusDescription.PENDING_PIN]: { + [UserLocale.ENGLISH]: { description: 'The card is pending PIN' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد الانتظار لرقم التعريف الشخصي' }, + }, + [CardStatusDescription.PREPARE_TO_CLOSE]: { + [UserLocale.ENGLISH]: { description: 'The card is being prepared for closure' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد التحضير للإغلاق' }, + }, + [CardStatusDescription.PIN_TRIES_EXCEEDED]: { + [UserLocale.ENGLISH]: { description: 'The card PIN tries have been exceeded' }, + [UserLocale.ARABIC]: { description: 'تم تجاوز محاولات رقم التعريف الشخصي للبطاقة' }, + }, + [CardStatusDescription.CARD_EXPIRED]: { + [UserLocale.ENGLISH]: { description: 'The card has expired' }, + [UserLocale.ARABIC]: { description: 'انتهت صلاحية البطاقة' }, + }, + [CardStatusDescription.LOST]: { + [UserLocale.ENGLISH]: { description: 'The card is lost' }, + [UserLocale.ARABIC]: { description: 'البطاقة ضائعة' }, + }, + [CardStatusDescription.STOLEN]: { + [UserLocale.ENGLISH]: { description: 'The card is stolen' }, + [UserLocale.ARABIC]: { description: 'البطاقة مسروقة' }, + }, + [CardStatusDescription.CUSTOMER_CLOSE]: { + [UserLocale.ENGLISH]: { description: 'The card is being closed by the customer' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد الإغلاق من قبل العميل' }, + }, + [CardStatusDescription.BANK_CANCELLED]: { + [UserLocale.ENGLISH]: { description: 'The card has been cancelled by the bank' }, + [UserLocale.ARABIC]: { description: 'البطاقة ألغيت من قبل البنك' }, + }, + [CardStatusDescription.FRAUD]: { + [UserLocale.ENGLISH]: { description: 'Fraud' }, + [UserLocale.ARABIC]: { description: 'احتيال' }, + }, + [CardStatusDescription.DAMAGED]: { + [UserLocale.ENGLISH]: { description: 'The card is damaged' }, + [UserLocale.ARABIC]: { description: 'البطاقة تالفة' }, + }, + [CardStatusDescription.SAFE_BLOCK]: { + [UserLocale.ENGLISH]: { description: 'The card is in a safe block' }, + [UserLocale.ARABIC]: { description: 'البطاقة في حظر آمن' }, + }, + [CardStatusDescription.TEMPORARY_BLOCK]: { + [UserLocale.ENGLISH]: { description: 'The card is in a temporary block' }, + [UserLocale.ARABIC]: { description: 'البطاقة في حظر مؤقت' }, + }, + [CardStatusDescription.RISK_BLOCK]: { + [UserLocale.ENGLISH]: { description: 'The card is in a risk block' }, + [UserLocale.ARABIC]: { description: 'البطاقة في حظر المخاطر' }, + }, + [CardStatusDescription.OVERDRAFT]: { + [UserLocale.ENGLISH]: { description: 'The card is in overdraft' }, + [UserLocale.ARABIC]: { description: 'البطاقة في السحب على المكشوف' }, + }, + [CardStatusDescription.BLOCKED_FOR_FEES]: { + [UserLocale.ENGLISH]: { description: 'The card is blocked for fees' }, + [UserLocale.ARABIC]: { description: 'البطاقة محظورة للرسوم' }, + }, + [CardStatusDescription.CLOSED_CUSTOMER_DEAD]: { + [UserLocale.ENGLISH]: { description: 'The card is closed because the customer is dead' }, + [UserLocale.ARABIC]: { description: 'البطاقة مغلقة لأن العميل متوفى' }, + }, + [CardStatusDescription.RETURN_CARD]: { + [UserLocale.ENGLISH]: { description: 'The card is being returned' }, + [UserLocale.ARABIC]: { description: 'البطاقة قيد الإرجاع' }, + }, + [CardStatusDescription.UNKNOWN]: { + [UserLocale.ENGLISH]: { description: 'The card status is unknown' }, + [UserLocale.ARABIC]: { description: 'حالة البطاقة غير معروفة' }, + }, +}; diff --git a/src/card/mappers/card-status.mapper.ts b/src/card/mappers/card-status.mapper.ts new file mode 100644 index 0000000..3c6da90 --- /dev/null +++ b/src/card/mappers/card-status.mapper.ts @@ -0,0 +1,37 @@ +import { CardStatus, CardStatusDescription } from '../enums'; + +export const CardStatusMapper: Record = { + //ACTIVE + '00': { description: CardStatusDescription.NORMAL, status: CardStatus.ACTIVE }, + + //PENDING + '02': { description: CardStatusDescription.NOT_YET_ISSUED, status: CardStatus.PENDING }, + '20': { description: CardStatusDescription.PENDING_ISSUANCE, status: CardStatus.PENDING }, + '21': { description: CardStatusDescription.CARD_EXTRACTED, status: CardStatus.PENDING }, + '22': { description: CardStatusDescription.EXTRACTION_FAILED, status: CardStatus.PENDING }, + '23': { description: CardStatusDescription.FAILED_PRINTING_BULK, status: CardStatus.PENDING }, + '24': { description: CardStatusDescription.FAILED_PRINTING_INST, status: CardStatus.PENDING }, + '30': { description: CardStatusDescription.PENDING_ACTIVATION, status: CardStatus.PENDING }, + '27': { description: CardStatusDescription.PENDING_PIN, status: CardStatus.PENDING }, + '16': { description: CardStatusDescription.PREPARE_TO_CLOSE, status: CardStatus.PENDING }, + + //BLOCKED + '01': { description: CardStatusDescription.PIN_TRIES_EXCEEDED, status: CardStatus.BLOCKED }, + '03': { description: CardStatusDescription.CARD_EXPIRED, status: CardStatus.BLOCKED }, + '04': { description: CardStatusDescription.LOST, status: CardStatus.BLOCKED }, + '05': { description: CardStatusDescription.STOLEN, status: CardStatus.BLOCKED }, + '06': { description: CardStatusDescription.CUSTOMER_CLOSE, status: CardStatus.BLOCKED }, + '07': { description: CardStatusDescription.BANK_CANCELLED, status: CardStatus.BLOCKED }, + '08': { description: CardStatusDescription.FRAUD, status: CardStatus.BLOCKED }, + '09': { description: CardStatusDescription.DAMAGED, status: CardStatus.BLOCKED }, + '50': { description: CardStatusDescription.SAFE_BLOCK, status: CardStatus.BLOCKED }, + '51': { description: CardStatusDescription.TEMPORARY_BLOCK, status: CardStatus.BLOCKED }, + '52': { description: CardStatusDescription.RISK_BLOCK, status: CardStatus.BLOCKED }, + '53': { description: CardStatusDescription.OVERDRAFT, status: CardStatus.BLOCKED }, + '54': { description: CardStatusDescription.BLOCKED_FOR_FEES, status: CardStatus.BLOCKED }, + '67': { description: CardStatusDescription.CLOSED_CUSTOMER_DEAD, status: CardStatus.BLOCKED }, + '75': { description: CardStatusDescription.RETURN_CARD, status: CardStatus.BLOCKED }, + + //Fallback + '99': { description: CardStatusDescription.UNKNOWN, status: CardStatus.PENDING }, +}; diff --git a/src/card/repositories/card.repository.ts b/src/card/repositories/card.repository.ts index ebab1f7..d747feb 100644 --- a/src/card/repositories/card.repository.ts +++ b/src/card/repositories/card.repository.ts @@ -3,7 +3,7 @@ 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'; +import { CardColors, CardIssuers, CardScheme, CardStatus, CardStatusDescription, CustomerType } from '../enums'; @Injectable() export class CardRepository { @@ -19,7 +19,6 @@ export class CardRepository { firstSixDigits: card.firstSixDigits, lastFourDigits: card.lastFourDigits, color: CardColors.BLUE, - status: CardStatus.ACTIVE, scheme: CardScheme.VISA, issuer: CardIssuers.NEOLEAP, accountId: accountId, @@ -40,4 +39,11 @@ export class CardRepository { where: { customerId, status: CardStatus.ACTIVE }, }); } + + updateCardStatus(id: string, status: CardStatus, statusDescription: CardStatusDescription) { + return this.cardRepository.update(id, { + status: status, + statusDescription: statusDescription, + }); + } } diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index bc71cfd..61f785e 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -1,7 +1,9 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Transactional } from 'typeorm-transactional'; +import { AccountCardStatusChangedWebhookRequest } from '~/common/modules/neoleap/dtos/requests'; import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response'; import { Card } from '../entities'; +import { CardStatusMapper } from '../mappers/card-status.mapper'; import { CardRepository } from '../repositories'; import { AccountService } from './account.service'; @@ -42,4 +44,11 @@ export class CardService { } return card; } + + async updateCardStatus(body: AccountCardStatusChangedWebhookRequest) { + const card = await this.getCardByReferenceNumber(body.cardId); + const { description, status } = CardStatusMapper[body.newStatus] || CardStatusMapper['99']; + + return this.cardRepository.updateCardStatus(card.id, status, description); + } } diff --git a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts b/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts index 05d3bf9..1fb8d60 100644 --- a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts +++ b/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts @@ -1,20 +1,29 @@ import { Body, Controller, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { TransactionService } from '~/card/services/transaction.service'; -import { AccountTransactionWebhookRequest, CardTransactionWebhookRequest } from '../dtos/requests'; +import { + AccountCardStatusChangedWebhookRequest, + AccountTransactionWebhookRequest, + CardTransactionWebhookRequest, +} from '../dtos/requests'; +import { NeoLeapWebhookService } from '../services'; @Controller('neoleap-webhooks') @ApiTags('Neoleap Webhooks') export class NeoLeapWebhooksController { - constructor(private readonly transactionService: TransactionService) {} + constructor(private readonly neoleapWebhookService: NeoLeapWebhookService) {} @Post('card-transaction') async handleCardTransactionWebhook(@Body() body: CardTransactionWebhookRequest) { - return this.transactionService.createCardTransaction(body); + return this.neoleapWebhookService.handleCardTransactionWebhook(body); } @Post('account-transaction') async handleAccountTransactionWebhook(@Body() body: AccountTransactionWebhookRequest) { - return this.transactionService.createAccountTransaction(body); + return this.neoleapWebhookService.handleAccountTransactionWebhook(body); + } + + @Post('account-card-status-changed') + async handleAccountCardStatusChangedWebhook(@Body() body: AccountCardStatusChangedWebhookRequest) { + return this.neoleapWebhookService.handleAccountCardStatusChangedWebhook(body); } } diff --git a/src/common/modules/neoleap/controllers/neotest.controller.ts b/src/common/modules/neoleap/controllers/neotest.controller.ts index 47ed777..5ea191d 100644 --- a/src/common/modules/neoleap/controllers/neotest.controller.ts +++ b/src/common/modules/neoleap/controllers/neotest.controller.ts @@ -37,7 +37,7 @@ export class NeoTestController { @ApiDataResponse(InquireApplicationResponse) async inquireApplication(@AuthenticatedUser() user: IJwtPayload) { const customer = await this.customerService.findCustomerById(user.sub); - const data = await this.neoleapService.inquireApplication(customer.waitingNumber.toString()); + const data = await this.neoleapService.inquireApplication(customer.applicationNumber.toString()); return ResponseFactory.data(data); } diff --git a/src/common/modules/neoleap/dtos/requests/account-card-status-changed-webhook.request.dto.ts b/src/common/modules/neoleap/dtos/requests/account-card-status-changed-webhook.request.dto.ts new file mode 100644 index 0000000..412effc --- /dev/null +++ b/src/common/modules/neoleap/dtos/requests/account-card-status-changed-webhook.request.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; +import { IsString } from 'class-validator'; + +export class AccountCardStatusChangedWebhookRequest { + @ApiProperty() + @Expose({ name: 'InstId' }) + @IsString() + instId!: string; + + @ApiProperty() + @Expose({ name: 'cardId' }) + @IsString() + cardId!: string; + + @ApiProperty() + @Expose({ name: 'newStatus' }) + @IsString() + newStatus!: string; +} diff --git a/src/common/modules/neoleap/dtos/requests/index.ts b/src/common/modules/neoleap/dtos/requests/index.ts index d4cad6f..9360a77 100644 --- a/src/common/modules/neoleap/dtos/requests/index.ts +++ b/src/common/modules/neoleap/dtos/requests/index.ts @@ -1,3 +1,4 @@ +export * from './account-card-status-changed-webhook.request.dto'; export * from './account-transaction-webhook.request.dto'; export * from './card-transaction-webhook.request.dto'; export * from './update-card-controls.request.dto'; diff --git a/src/common/modules/neoleap/neoleap.module.ts b/src/common/modules/neoleap/neoleap.module.ts index 78c0224..39951f0 100644 --- a/src/common/modules/neoleap/neoleap.module.ts +++ b/src/common/modules/neoleap/neoleap.module.ts @@ -4,11 +4,12 @@ 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 { NeoLeapWebhookService } from './services'; import { NeoLeapService } from './services/neoleap.service'; @Module({ imports: [HttpModule, CustomerModule, CardModule], controllers: [NeoTestController, NeoLeapWebhooksController], - providers: [NeoLeapService], + providers: [NeoLeapService, NeoLeapWebhookService], }) export class NeoLeapModule {} diff --git a/src/common/modules/neoleap/services/index.ts b/src/common/modules/neoleap/services/index.ts index e69de29..4fac8d7 100644 --- a/src/common/modules/neoleap/services/index.ts +++ b/src/common/modules/neoleap/services/index.ts @@ -0,0 +1,2 @@ +export * from './neoleap-webook.service'; +export * from './neoleap.service'; diff --git a/src/common/modules/neoleap/services/neoleap-webook.service.ts b/src/common/modules/neoleap/services/neoleap-webook.service.ts new file mode 100644 index 0000000..83b68c4 --- /dev/null +++ b/src/common/modules/neoleap/services/neoleap-webook.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { CardService } from '~/card/services'; +import { TransactionService } from '~/card/services/transaction.service'; +import { + AccountCardStatusChangedWebhookRequest, + AccountTransactionWebhookRequest, + CardTransactionWebhookRequest, +} from '../dtos/requests'; + +@Injectable() +export class NeoLeapWebhookService { + constructor(private readonly transactionService: TransactionService, private readonly cardService: CardService) {} + + handleCardTransactionWebhook(body: CardTransactionWebhookRequest) { + return this.transactionService.createCardTransaction(body); + } + + handleAccountTransactionWebhook(body: AccountTransactionWebhookRequest) { + return this.transactionService.createAccountTransaction(body); + } + + handleAccountCardStatusChangedWebhook(body: AccountCardStatusChangedWebhookRequest) { + return this.cardService.updateCardStatus(body); + } +} diff --git a/src/common/modules/neoleap/services/neoleap.service.ts b/src/common/modules/neoleap/services/neoleap.service.ts index 6f31ac4..3abd065 100644 --- a/src/common/modules/neoleap/services/neoleap.service.ts +++ b/src/common/modules/neoleap/services/neoleap.service.ts @@ -50,7 +50,7 @@ export class NeoLeapService { CreateNewApplicationRequestDetails: { ApplicationRequestDetails: { InstitutionCode: this.institutionCode, - ExternalApplicationNumber: customer.waitingNumber.toString(), + ExternalApplicationNumber: customer.applicationNumber.toString(), ApplicationType: '01', Product: '1101', ApplicationDate: moment().format('YYYY-MM-DD'), diff --git a/src/core/enums/user-locale.enum.ts b/src/core/enums/user-locale.enum.ts index b9aa1f4..66339fe 100644 --- a/src/core/enums/user-locale.enum.ts +++ b/src/core/enums/user-locale.enum.ts @@ -1,4 +1,8 @@ -export enum UserLocale { - ARABIC = 'ar', - ENGLISH = 'en', -} +import { ObjectValues } from '../types'; + +export const UserLocale = { + ARABIC: 'ar', + ENGLISH: 'en', +} as const; + +export type UserLocale = ObjectValues; diff --git a/src/customer/dtos/response/customer.response.dto.ts b/src/customer/dtos/response/customer.response.dto.ts index f49d12c..6475221 100644 --- a/src/customer/dtos/response/customer.response.dto.ts +++ b/src/customer/dtos/response/customer.response.dto.ts @@ -97,7 +97,7 @@ export class CustomerResponseDto { this.gender = customer.gender; this.isJunior = customer.isJunior; this.isGuardian = customer.isGuardian; - this.waitingNumber = customer.waitingNumber; + this.waitingNumber = customer.applicationNumber; this.country = customer.country; this.region = customer.region; this.city = customer.city; diff --git a/src/customer/entities/customer.entity.ts b/src/customer/entities/customer.entity.ts index 96786c0..aa029ef 100644 --- a/src/customer/entities/customer.entity.ts +++ b/src/customer/entities/customer.entity.ts @@ -71,9 +71,9 @@ export class Customer extends BaseEntity { @Column('boolean', { default: false, name: 'is_guardian' }) isGuardian!: boolean; - @Column('int', { name: 'waiting_number' }) + @Column('int', { name: 'application_number' }) @Generated('increment') - waitingNumber!: number; + applicationNumber!: number; @Column('varchar', { name: 'user_id' }) userId!: string; diff --git a/src/db/migrations/1753098116701-update-card-table.ts b/src/db/migrations/1753098116701-update-card-table.ts new file mode 100644 index 0000000..56cb15c --- /dev/null +++ b/src/db/migrations/1753098116701-update-card-table.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateCardTable1753098116701 implements MigrationInterface { + name = 'UpdateCardTable1753098116701' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cards" ADD "statusDescription" character varying NOT NULL DEFAULT 'PENDING_ACTIVATION'`); + await queryRunner.query(`ALTER TABLE "cards" ADD "limit" numeric(10,2) NOT NULL DEFAULT '0'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "limit"`); + await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "statusDescription"`); + } + +} diff --git a/src/db/migrations/1753098326876-edit-customer-table.ts b/src/db/migrations/1753098326876-edit-customer-table.ts new file mode 100644 index 0000000..2595e2b --- /dev/null +++ b/src/db/migrations/1753098326876-edit-customer-table.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class EditCustomerTable1753098326876 implements MigrationInterface { + name = 'EditCustomerTable1753098326876' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "customers" RENAME COLUMN "waiting_number" TO "application_number"`); + await queryRunner.query(`ALTER SEQUENCE "customers_waiting_number_seq" RENAME TO "customers_application_number_seq"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER SEQUENCE "customers_application_number_seq" RENAME TO "customers_waiting_number_seq"`); + await queryRunner.query(`ALTER TABLE "customers" RENAME COLUMN "application_number" TO "waiting_number"`); + } + +} diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index 5efe087..a0238a5 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -29,3 +29,5 @@ export * from './1749633935436-create-card-entity'; export * from './1751456987627-create-account-entity'; export * from './1751466314709-create-transaction-table'; export * from './1752056898465-edit-transaction-table'; +export * from './1753098116701-update-card-table'; +export * from './1753098326876-edit-customer-table';