From e06642225ae5713416783981d8be303affc9723d Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Thu, 14 Aug 2025 14:40:08 +0300 Subject: [PATCH] feat: working on creating parent card --- src/app.module.ts | 6 +- src/card/card.module.ts | 12 +++- src/card/controllers/cards.controller.ts | 39 ++++++++++++ src/card/controllers/index.ts | 1 + src/card/dtos/responses/card.response.dto.ts | 56 +++++++++++++++++ src/card/dtos/responses/index.ts | 1 + .../mappers/card-status-description.mapper.ts | 5 +- src/card/repositories/card.repository.ts | 7 ++- src/card/services/card.service.ts | 42 ++++++++++--- src/card/services/transaction.service.ts | 2 +- .../__mocks__/card-embossing-details.mock.ts | 38 ++++++++++++ src/common/modules/neoleap/__mocks__/index.ts | 1 + .../neoleap/__mocks__/kyc-callback.mock.ts | 6 ++ .../neoleap/controllers/neotest.controller.ts | 62 ------------------- .../dtos/requests/kyc-webhook.request.dto.ts | 30 +++++++++ .../card-embossing-details.response.dto.ts | 26 ++++++++ .../modules/neoleap/dtos/response/index.ts | 1 + src/common/modules/neoleap/neoleap.module.ts | 13 ++-- src/common/modules/neoleap/services/index.ts | 2 +- .../neoleap/services/neoleap.service.ts | 46 +++++++++++--- src/customer/customer.module.ts | 7 +-- src/customer/services/customer.service.ts | 9 ++- src/i18n/ar/app.json | 3 +- src/i18n/en/app.json | 3 +- src/webhook/controllers/index.ts | 0 .../neoleap-webhooks.controller.ts | 4 +- src/webhook/services/index.ts | 1 + .../services/neoleap-webook.service.ts | 7 ++- src/webhook/webhook.module.ts | 12 ++++ 29 files changed, 328 insertions(+), 114 deletions(-) create mode 100644 src/card/controllers/cards.controller.ts create mode 100644 src/card/controllers/index.ts create mode 100644 src/card/dtos/responses/card.response.dto.ts create mode 100644 src/card/dtos/responses/index.ts create mode 100644 src/common/modules/neoleap/__mocks__/card-embossing-details.mock.ts delete mode 100644 src/common/modules/neoleap/controllers/neotest.controller.ts create mode 100644 src/common/modules/neoleap/dtos/response/card-embossing-details.response.dto.ts create mode 100644 src/webhook/controllers/index.ts rename src/{common/modules/neoleap => webhook}/controllers/neoleap-webhooks.controller.ts (92%) create mode 100644 src/webhook/services/index.ts rename src/{common/modules/neoleap => webhook}/services/neoleap-webook.service.ts (82%) create mode 100644 src/webhook/webhook.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index b47eeb7..2e3db91 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,6 +9,7 @@ import { LoggerModule } from 'nestjs-pino'; import { DataSource } from 'typeorm'; import { addTransactionalDataSource } from 'typeorm-transactional'; import { AuthModule } from './auth/auth.module'; +import { CardModule } from './card/card.module'; import { CacheModule } from './common/modules/cache/cache.module'; import { LookupModule } from './common/modules/lookup/lookup.module'; import { NeoLeapModule } from './common/modules/neoleap/neoleap.module'; @@ -26,7 +27,7 @@ import { GuardianModule } from './guardian/guardian.module'; import { HealthModule } from './health/health.module'; import { JuniorModule } from './junior/junior.module'; import { UserModule } from './user/user.module'; -import { CardModule } from './card/card.module'; +import { WebhookModule } from './webhook/webhook.module'; @Module({ controllers: [], @@ -61,6 +62,7 @@ import { CardModule } from './card/card.module'; CustomerModule, JuniorModule, GuardianModule, + CardModule, NotificationModule, OtpModule, @@ -71,7 +73,7 @@ import { CardModule } from './card/card.module'; CronModule, NeoLeapModule, - CardModule, + WebhookModule, ], providers: [ // Global Pipes diff --git a/src/card/card.module.ts b/src/card/card.module.ts index 92500ad..896a435 100644 --- a/src/card/card.module.ts +++ b/src/card/card.module.ts @@ -1,5 +1,8 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { NeoLeapModule } from '~/common/modules/neoleap/neoleap.module'; +import { CustomerModule } from '~/customer/customer.module'; +import { CardsController } from './controllers'; import { Card } from './entities'; import { Account } from './entities/account.entity'; import { Transaction } from './entities/transaction.entity'; @@ -11,7 +14,11 @@ import { AccountService } from './services/account.service'; import { TransactionService } from './services/transaction.service'; @Module({ - imports: [TypeOrmModule.forFeature([Card, Account, Transaction])], + imports: [ + TypeOrmModule.forFeature([Card, Account, Transaction]), + forwardRef(() => NeoLeapModule), + forwardRef(() => CustomerModule), // <-- add forwardRef here + ], providers: [ CardService, CardRepository, @@ -21,5 +28,6 @@ import { TransactionService } from './services/transaction.service'; AccountRepository, ], exports: [CardService, TransactionService], + controllers: [CardsController], }) export class CardModule {} diff --git a/src/card/controllers/cards.controller.ts b/src/card/controllers/cards.controller.ts new file mode 100644 index 0000000..385958a --- /dev/null +++ b/src/card/controllers/cards.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { IJwtPayload } from '~/auth/interfaces'; +import { AuthenticatedUser } from '~/common/decorators'; +import { AccessTokenGuard } from '~/common/guards'; +import { CardEmbossingDetailsResponseDto } from '~/common/modules/neoleap/dtos/response'; +import { ApiDataResponse } from '~/core/decorators'; +import { ResponseFactory } from '~/core/utils'; +import { CardResponseDto } from '../dtos/responses'; +import { CardService } from '../services'; + +@Controller('cards') +@ApiBearerAuth() +@ApiTags('Cards') +@UseGuards(AccessTokenGuard) +export class CardsController { + constructor(private readonly cardService: CardService) {} + + @Post() + @ApiDataResponse(CardResponseDto) + async createCard(@AuthenticatedUser() { sub }: IJwtPayload) { + const card = await this.cardService.createCard(sub); + return ResponseFactory.data(new CardResponseDto(card)); + } + + @Get('current') + @ApiDataResponse(CardResponseDto) + async getCurrentCard(@AuthenticatedUser() { sub }: IJwtPayload) { + const card = await this.cardService.getCardByCustomerId(sub); + return ResponseFactory.data(new CardResponseDto(card)); + } + + @Get('embossing-details') + @ApiDataResponse(CardEmbossingDetailsResponseDto) + async getCardById(@AuthenticatedUser() { sub }: IJwtPayload) { + const res = await this.cardService.getEmbossingInformation(sub); + return ResponseFactory.data(res); + } +} diff --git a/src/card/controllers/index.ts b/src/card/controllers/index.ts new file mode 100644 index 0000000..ae6254e --- /dev/null +++ b/src/card/controllers/index.ts @@ -0,0 +1 @@ +export * from './cards.controller'; diff --git a/src/card/dtos/responses/card.response.dto.ts b/src/card/dtos/responses/card.response.dto.ts new file mode 100644 index 0000000..53c13f1 --- /dev/null +++ b/src/card/dtos/responses/card.response.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Card } from '~/card/entities'; +import { CardScheme, CardStatus, CustomerType } from '~/card/enums'; +import { CardStatusDescriptionMapper } from '~/card/mappers/card-status-description.mapper'; +import { UserLocale } from '~/core/enums'; + +export class CardResponseDto { + @ApiProperty({ + example: 'b34df8c2-5d3e-4b1a-9c2f-7e3b1a2d3f4e', + }) + id!: string; + + @ApiProperty({ + example: '123456', + description: 'The first six digits of the card number.', + }) + firstSixDigits!: string; + + @ApiProperty({ example: '7890', description: 'The last four digits of the card number.' }) + lastFourDigits!: string; + + @ApiProperty({ + enum: CardScheme, + description: 'The card scheme (e.g., VISA, MASTERCARD).', + }) + scheme!: CardScheme; + + @ApiProperty({ + enum: CardStatus, + description: 'The current status of the card (e.g., ACTIVE, PENDING).', + }) + status!: CardStatus; + + @ApiProperty({ + example: 'The card is active', + description: 'A description of the card status.', + }) + statusDescription!: string; + + @ApiProperty({ + example: 2000.0, + description: 'The credit limit of the card.', + }) + balance!: number; + + constructor(card: Card) { + this.id = card.id; + this.firstSixDigits = card.firstSixDigits; + this.lastFourDigits = card.lastFourDigits; + this.scheme = card.scheme; + this.status = card.status; + this.statusDescription = CardStatusDescriptionMapper[card.statusDescription][UserLocale.ENGLISH].description; + this.balance = + card.customerType === CustomerType.CHILD ? Math.min(card.limit, card.account.balance) : card.account.balance; + } +} diff --git a/src/card/dtos/responses/index.ts b/src/card/dtos/responses/index.ts new file mode 100644 index 0000000..5f2d4bb --- /dev/null +++ b/src/card/dtos/responses/index.ts @@ -0,0 +1 @@ +export * from './card.response.dto'; diff --git a/src/card/mappers/card-status-description.mapper.ts b/src/card/mappers/card-status-description.mapper.ts index 85fe6d9..202c0e0 100644 --- a/src/card/mappers/card-status-description.mapper.ts +++ b/src/card/mappers/card-status-description.mapper.ts @@ -1,7 +1,10 @@ import { UserLocale } from '~/core/enums'; import { CardStatusDescription } from '../enums'; -export const CardStatusMapper: Record = { +export const CardStatusDescriptionMapper: Record< + CardStatusDescription, + { [key in UserLocale]: { description: string } } +> = { [CardStatusDescription.NORMAL]: { [UserLocale.ENGLISH]: { description: 'The card is active' }, [UserLocale.ARABIC]: { description: 'البطاقة نشطة' }, diff --git a/src/card/repositories/card.repository.ts b/src/card/repositories/card.repository.ts index a6e8cb6..3ee34ce 100644 --- a/src/card/repositories/card.repository.ts +++ b/src/card/repositories/card.repository.ts @@ -28,7 +28,7 @@ export class CardRepository { } getCardById(id: string): Promise { - return this.cardRepository.findOne({ where: { id } }); + return this.cardRepository.findOne({ where: { id }, relations: ['account'] }); } getCardByReferenceNumber(referenceNumber: string): Promise { @@ -42,9 +42,10 @@ export class CardRepository { }); } - getActiveCardForCustomer(customerId: string): Promise { + getCardByCustomerId(customerId: string): Promise { return this.cardRepository.findOne({ - where: { customerId, status: CardStatus.ACTIVE }, + where: { customerId }, + relations: ['account'], }); } diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index 6d48d0f..89a0c7a 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 { BadRequestException, forwardRef, Inject, 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 { NeoLeapService } from '~/common/modules/neoleap/services'; +import { KycStatus } from '~/customer/enums'; +import { CustomerService } from '~/customer/services'; import { Card } from '../entities'; import { CardStatusMapper } from '../mappers/card-status.mapper'; import { CardRepository } from '../repositories'; @@ -9,12 +11,30 @@ import { AccountService } from './account.service'; @Injectable() export class CardService { - constructor(private readonly cardRepository: CardRepository, private readonly accountService: AccountService) {} + constructor( + private readonly cardRepository: CardRepository, + private readonly accountService: AccountService, + @Inject(forwardRef(() => NeoLeapService)) private readonly neoleapService: NeoLeapService, + @Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService, + ) {} @Transactional() - async createCard(customerId: string, cardData: CreateApplicationResponse): Promise { - const account = await this.accountService.createAccount(cardData); - return this.cardRepository.createCard(customerId, account.id, cardData); + async createCard(customerId: string): Promise { + const customer = await this.customerService.findCustomerById(customerId); + + if (customer.kycStatus !== KycStatus.APPROVED) { + throw new BadRequestException('CUSTOMER.KYC_NOT_APPROVED'); + } + + if (customer.cards.length > 0) { + throw new BadRequestException('CUSTOMER.ALREADY_HAS_CARD'); + } + + const data = await this.neoleapService.createApplication(customer); + const account = await this.accountService.createAccount(data); + const createdCard = await this.cardRepository.createCard(customerId, account.id, data); + + return this.getCardById(createdCard.id); } async getCardById(id: string): Promise { @@ -46,8 +66,8 @@ export class CardService { return card; } - async getActiveCardForCustomer(customerId: string): Promise { - const card = await this.cardRepository.getActiveCardForCustomer(customerId); + async getCardByCustomerId(customerId: string): Promise { + const card = await this.cardRepository.getCardByCustomerId(customerId); if (!card) { throw new BadRequestException('CARD.NOT_FOUND'); } @@ -60,4 +80,10 @@ export class CardService { return this.cardRepository.updateCardStatus(card.id, status, description); } + + async getEmbossingInformation(customerId: string) { + const card = await this.getCardByCustomerId(customerId); + + return this.neoleapService.getEmbossingInformation(card); + } } diff --git a/src/card/services/transaction.service.ts b/src/card/services/transaction.service.ts index ff22aef..d195368 100644 --- a/src/card/services/transaction.service.ts +++ b/src/card/services/transaction.service.ts @@ -14,8 +14,8 @@ import { CardService } from './card.service'; export class TransactionService { constructor( private readonly transactionRepository: TransactionRepository, - private readonly cardService: CardService, private readonly accountService: AccountService, + private readonly cardService: CardService, ) {} @Transactional() diff --git a/src/common/modules/neoleap/__mocks__/card-embossing-details.mock.ts b/src/common/modules/neoleap/__mocks__/card-embossing-details.mock.ts new file mode 100644 index 0000000..00e8c8f --- /dev/null +++ b/src/common/modules/neoleap/__mocks__/card-embossing-details.mock.ts @@ -0,0 +1,38 @@ +export const CARD_EMBOSSING_DETAILS_MOCK = { + ResponseHeader: { + Version: '1.0.0', + MsgUid: 'adaa1893-9f95-48a8-b7a1-0422bcf629b5', + Source: 'ZOD', + ServiceId: 'GetEmbossingInformation', + ReqDateTime: '2025-06-11T07:32:16.304Z', + RspDateTime: '2025-08-14T10:01:14.205', + ResponseCode: '000', + ResponseType: 'Success', + ProcessingTime: 67, + EncryptionKey: null, + ResponseDescription: 'Operation Successful', + LocalizedResponseDescription: null, + CustomerSpecificResponseDescriptionList: null, + HeaderUserDataList: null, + }, + GetEmbossingInformationResponseDetails: { + icvv: '259', + Track1: '%B4017786818471184^AMMAR/QAFFAF^31102261029800997000000?', + Track2: ';4017786818471184=31102261029899700?', + Track3: null, + EmvTrack1: '1029800259000000', + EmvTrack2: '4017786818471184D31102261029825900', + ClearPan: '4017786818471184', + PinBlock: '663B1A71D8112D97', + Cvv: '997', + Cvv2: '834', + iCvv: '259', + Pvv: '0298', + EmbossingName: 'AMMAR QAFFAF', + ExpiryDate: '20311031', + OldPlasticExpiryDate: null, + CardStatus: '30', + OldPlasticCardStatus: ' ', + EmbossingRecord: null, + }, +}; diff --git a/src/common/modules/neoleap/__mocks__/index.ts b/src/common/modules/neoleap/__mocks__/index.ts index 98ef2c9..96a5d45 100644 --- a/src/common/modules/neoleap/__mocks__/index.ts +++ b/src/common/modules/neoleap/__mocks__/index.ts @@ -1,3 +1,4 @@ +export * from './card-embossing-details.mock'; export * from './create-application.mock'; export * from './initiate-kyc.mock'; export * from './inquire-application.mock'; diff --git a/src/common/modules/neoleap/__mocks__/kyc-callback.mock.ts b/src/common/modules/neoleap/__mocks__/kyc-callback.mock.ts index 5ce59e0..88d4e5b 100644 --- a/src/common/modules/neoleap/__mocks__/kyc-callback.mock.ts +++ b/src/common/modules/neoleap/__mocks__/kyc-callback.mock.ts @@ -19,5 +19,11 @@ export const getKycCallbackMock = (nationalId: string) => { professionTitle: 'Software Engineer', professionType: 'Full-Time', isPep: 'N', + country: '682', + region: 'Mecca', + city: 'At-Taif', + neighborhood: 'Al-Hamra', + street: 'Al-Masjid Al-Haram', + building: '123', }; }; diff --git a/src/common/modules/neoleap/controllers/neotest.controller.ts b/src/common/modules/neoleap/controllers/neotest.controller.ts deleted file mode 100644 index 5ea191d..0000000 --- a/src/common/modules/neoleap/controllers/neotest.controller.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { IJwtPayload } from '~/auth/interfaces'; -import { CardService } from '~/card/services'; -import { AuthenticatedUser } from '~/common/decorators'; -import { AccessTokenGuard } from '~/common/guards'; -import { ApiDataResponse } from '~/core/decorators'; -import { ResponseFactory } from '~/core/utils'; -import { CustomerResponseDto } from '~/customer/dtos/response'; -import { CustomerService } from '~/customer/services'; -import { UpdateCardControlsRequestDto } from '../dtos/requests'; -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') -@UseGuards(AccessTokenGuard) -@ApiBearerAuth() -export class NeoTestController { - constructor( - private readonly neoleapService: NeoLeapService, - private readonly customerService: CustomerService, - private readonly cardService: CardService, - private readonly configService: ConfigService, - ) {} - - @Post('update-kys') - @ApiDataResponse(CustomerResponseDto) - async updateKys(@AuthenticatedUser() user: IJwtPayload) { - const customer = await this.customerService.updateKyc(user.sub); - - return ResponseFactory.data(new CustomerResponseDto(customer)); - } - - @Post('inquire-application') - @ApiDataResponse(InquireApplicationResponse) - async inquireApplication(@AuthenticatedUser() user: IJwtPayload) { - const customer = await this.customerService.findCustomerById(user.sub); - const data = await this.neoleapService.inquireApplication(customer.applicationNumber.toString()); - return ResponseFactory.data(data); - } - - @Post('create-application') - @ApiDataResponse(CreateApplicationResponse) - async createApplication(@AuthenticatedUser() user: IJwtPayload) { - const customer = await this.customerService.findCustomerById(user.sub); - const data = await this.neoleapService.createApplication(customer); - await this.cardService.createCard(customer.id, data); - return ResponseFactory.data(data); - } - - @Post('update-card-controls') - async updateCardControls( - @AuthenticatedUser() user: IJwtPayload, - @Body() { amount, count }: UpdateCardControlsRequestDto, - ) { - const card = await this.cardService.getActiveCardForCustomer(user.sub); - await this.neoleapService.updateCardControl(card.cardReference, amount, count); - return ResponseFactory.data({ message: 'Card controls updated successfully' }); - } -} diff --git a/src/common/modules/neoleap/dtos/requests/kyc-webhook.request.dto.ts b/src/common/modules/neoleap/dtos/requests/kyc-webhook.request.dto.ts index 427af17..92d8504 100644 --- a/src/common/modules/neoleap/dtos/requests/kyc-webhook.request.dto.ts +++ b/src/common/modules/neoleap/dtos/requests/kyc-webhook.request.dto.ts @@ -96,4 +96,34 @@ export class KycWebhookRequest { @IsString() @ApiProperty({ example: 'N' }) isPep!: string; + + @Expose() + @IsString() + @ApiProperty({ example: '682' }) + country!: string; + + @Expose() + @IsString() + @ApiProperty({ example: 'Mecca' }) + region!: string; + + @Expose() + @IsString() + @ApiProperty({ example: 'At-Taif' }) + city!: string; + + @Expose() + @IsString() + @ApiProperty({ example: 'Al-Hamra' }) + neighborhood!: string; + + @Expose() + @IsString() + @ApiProperty({ example: 'Al-Masjid Al-Haram' }) + street!: string; + + @Expose() + @IsString() + @ApiProperty({ example: '123' }) + building!: string; } diff --git a/src/common/modules/neoleap/dtos/response/card-embossing-details.response.dto.ts b/src/common/modules/neoleap/dtos/response/card-embossing-details.response.dto.ts new file mode 100644 index 0000000..0561e64 --- /dev/null +++ b/src/common/modules/neoleap/dtos/response/card-embossing-details.response.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; + +export class CardEmbossingDetailsResponseDto { + @ApiProperty({ + example: '997', + }) + @Expose({ name: 'Cvv' }) + cvv!: string; + + @ApiProperty({ example: '4017786818471184' }) + @Expose({ name: 'ClearPan' }) + cardNumber!: string; + + @ApiProperty({ + example: '20311031', + }) + @Expose({ name: 'ExpiryDate' }) + expiryDate!: string; + + @ApiProperty({ + example: 'AMMAR QAFFAF', + }) + @Expose({ name: 'EmbossingName' }) + cardHolderName!: string; +} diff --git a/src/common/modules/neoleap/dtos/response/index.ts b/src/common/modules/neoleap/dtos/response/index.ts index 5b616e5..f6b5943 100644 --- a/src/common/modules/neoleap/dtos/response/index.ts +++ b/src/common/modules/neoleap/dtos/response/index.ts @@ -1,3 +1,4 @@ +export * from './card-embossing-details.response.dto'; export * from './create-application.response.dto'; export * from './initiate-kyc.response.dto'; export * from './inquire-application.response'; diff --git a/src/common/modules/neoleap/neoleap.module.ts b/src/common/modules/neoleap/neoleap.module.ts index 5b1043a..bc27f27 100644 --- a/src/common/modules/neoleap/neoleap.module.ts +++ b/src/common/modules/neoleap/neoleap.module.ts @@ -1,16 +1,11 @@ import { HttpModule } from '@nestjs/axios'; -import { forwardRef, 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 { NeoLeapWebhookService } from './services'; +import { Module } from '@nestjs/common'; import { NeoLeapService } from './services/neoleap.service'; @Module({ - imports: [HttpModule, CardModule, forwardRef(() => CustomerModule)], - controllers: [NeoTestController, NeoLeapWebhooksController], - providers: [NeoLeapService, NeoLeapWebhookService], + imports: [HttpModule], + controllers: [], + providers: [NeoLeapService], exports: [NeoLeapService], }) export class NeoLeapModule {} diff --git a/src/common/modules/neoleap/services/index.ts b/src/common/modules/neoleap/services/index.ts index 4fac8d7..bedb316 100644 --- a/src/common/modules/neoleap/services/index.ts +++ b/src/common/modules/neoleap/services/index.ts @@ -1,2 +1,2 @@ -export * from './neoleap-webook.service'; +export * from '../../../../webhook/services/neoleap-webook.service'; export * from './neoleap.service'; diff --git a/src/common/modules/neoleap/services/neoleap.service.ts b/src/common/modules/neoleap/services/neoleap.service.ts index 2d2eee7..ad89036 100644 --- a/src/common/modules/neoleap/services/neoleap.service.ts +++ b/src/common/modules/neoleap/services/neoleap.service.ts @@ -4,13 +4,20 @@ import { ConfigService } from '@nestjs/config'; import { ClassConstructor, plainToInstance } from 'class-transformer'; import moment from 'moment'; import { v4 as uuid } from 'uuid'; +import { Card } from '~/card/entities'; import { CountriesNumericISO } from '~/common/constants'; import { InitiateKycRequestDto } from '~/customer/dtos/request'; import { Customer } from '~/customer/entities'; -import { Gender, KycStatus } from '~/customer/enums'; -import { CREATE_APPLICATION_MOCK, INITIATE_KYC_MOCK, INQUIRE_APPLICATION_MOCK } from '../__mocks__/'; +import { Gender } from '~/customer/enums'; +import { + CARD_EMBOSSING_DETAILS_MOCK, + CREATE_APPLICATION_MOCK, + INITIATE_KYC_MOCK, + INQUIRE_APPLICATION_MOCK, +} from '../__mocks__/'; import { getKycCallbackMock } from '../__mocks__/kyc-callback.mock'; import { + CardEmbossingDetailsResponseDto, CreateApplicationResponse, InitiateKycResponseDto, InquireApplicationResponse, @@ -85,14 +92,6 @@ export class NeoLeapService { async createApplication(customer: Customer) { const responseKey = 'CreateNewApplicationResponseDetails'; - if (customer.kycStatus !== KycStatus.APPROVED) { - throw new BadRequestException('CUSTOMER.KYC_NOT_APPROVED'); - } - - if (customer.cards.length > 0) { - throw new BadRequestException('CUSTOMER.ALREADY_HAS_CARD'); - } - if (!this.useGateway) { return plainToInstance(CreateApplicationResponse, CREATE_APPLICATION_MOCK[responseKey], { excludeExtraneousValues: true, @@ -225,6 +224,33 @@ export class NeoLeapService { ); } + async getEmbossingInformation(card: Card) { + const responseKey = 'GetEmbossingInformationResponseDetails'; + if (!this.useGateway) { + return plainToInstance(CardEmbossingDetailsResponseDto, CARD_EMBOSSING_DETAILS_MOCK[responseKey], { + excludeExtraneousValues: true, + }); + } + + const payload = { + GetEmbossingInformationRequestDetails: { + InstitutionCode: this.institutionCode, + CardIdentifier: { + InstitutionCode: this.institutionCode, + Id: card.cardReference, + }, + }, + RequestHeader: this.prepareHeaders('GetEmbossingInformation'), + }; + + return this.sendRequestToNeoLeap( + 'cardembossing/CardEmbossingDetails', + payload, + responseKey, + CardEmbossingDetailsResponseDto, + ); + } + private prepareHeaders(serviceName: string): INeoleapHeaderRequest['RequestHeader'] { return { Version: '1.0.0', diff --git a/src/customer/customer.module.ts b/src/customer/customer.module.ts index 4f11d0a..e2b6b88 100644 --- a/src/customer/customer.module.ts +++ b/src/customer/customer.module.ts @@ -9,12 +9,7 @@ import { CustomerRepository } from './repositories/customer.repository'; import { CustomerService } from './services'; @Module({ - imports: [ - TypeOrmModule.forFeature([Customer]), - forwardRef(() => UserModule), - GuardianModule, - forwardRef(() => NeoLeapModule), - ], + imports: [TypeOrmModule.forFeature([Customer]), GuardianModule, forwardRef(() => UserModule), NeoLeapModule], controllers: [CustomerController], providers: [CustomerService, CustomerRepository], exports: [CustomerService], diff --git a/src/customer/services/customer.service.ts b/src/customer/services/customer.service.ts index b1aef8d..6d77eda 100644 --- a/src/customer/services/customer.service.ts +++ b/src/customer/services/customer.service.ts @@ -104,13 +104,18 @@ export class CustomerService { dateOfBirth: moment(body.dob, 'YYYYMMDD').toDate(), nationalId: body.nationalId, nationalIdExpiry: moment(body.nationalIdExpiry, 'YYYYMMDD').toDate(), - countryOfResidence: NumericToCountryIso[body.nationality], - country: NumericToCountryIso[body.nationality], + countryOfResidence: NumericToCountryIso[body.country], + country: NumericToCountryIso[body.country], gender: body.gender === 'M' ? Gender.MALE : Gender.FEMALE, sourceOfIncome: body.incomeSource, profession: body.professionTitle, professionType: body.professionType, isPep: body.isPep === 'Y', + city: body.city, + region: body.region, + neighborhood: body.neighborhood, + street: body.street, + building: body.building, }); } diff --git a/src/i18n/ar/app.json b/src/i18n/ar/app.json index 2b8c4cf..7f63e13 100644 --- a/src/i18n/ar/app.json +++ b/src/i18n/ar/app.json @@ -50,7 +50,8 @@ "CUSTOMER": { "NOT_FOUND": "لم يتم العثور على العميل.", "ALREADY_EXISTS": "العميل موجود بالفعل.", - "KYC_NOT_APPROVED": "لم يتم الموافقة على هوية العميل بعد." + "KYC_NOT_APPROVED": "لم يتم الموافقة على هوية العميل بعد.", + "ALREADY_HAS_CARD": "العميل لديه بطاقة بالفعل." }, "GIFT": { diff --git a/src/i18n/en/app.json b/src/i18n/en/app.json index 1bee066..c9d6a8d 100644 --- a/src/i18n/en/app.json +++ b/src/i18n/en/app.json @@ -49,7 +49,8 @@ "CUSTOMER": { "NOT_FOUND": "The customer was not found.", "ALREADY_EXISTS": "The customer already exists.", - "KYC_NOT_APPROVED": "The customer's KYC has not been approved yet." + "KYC_NOT_APPROVED": "The customer's KYC has not been approved yet.", + "ALREADY_HAS_CARD": "The customer already has a card." }, "GIFT": { diff --git a/src/webhook/controllers/index.ts b/src/webhook/controllers/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts b/src/webhook/controllers/neoleap-webhooks.controller.ts similarity index 92% rename from src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts rename to src/webhook/controllers/neoleap-webhooks.controller.ts index 1fda1ac..b54e93e 100644 --- a/src/common/modules/neoleap/controllers/neoleap-webhooks.controller.ts +++ b/src/webhook/controllers/neoleap-webhooks.controller.ts @@ -6,8 +6,8 @@ import { AccountTransactionWebhookRequest, CardTransactionWebhookRequest, KycWebhookRequest, -} from '../dtos/requests'; -import { NeoLeapWebhookService } from '../services'; +} from '../../common/modules/neoleap/dtos/requests'; +import { NeoLeapWebhookService } from '../../common/modules/neoleap/services'; @Controller('neoleap-webhooks') @ApiTags('Neoleap Webhooks') diff --git a/src/webhook/services/index.ts b/src/webhook/services/index.ts new file mode 100644 index 0000000..01468fb --- /dev/null +++ b/src/webhook/services/index.ts @@ -0,0 +1 @@ +export * from './neoleap-webook.service'; diff --git a/src/common/modules/neoleap/services/neoleap-webook.service.ts b/src/webhook/services/neoleap-webook.service.ts similarity index 82% rename from src/common/modules/neoleap/services/neoleap-webook.service.ts rename to src/webhook/services/neoleap-webook.service.ts index 5c605b8..0beb630 100644 --- a/src/common/modules/neoleap/services/neoleap-webook.service.ts +++ b/src/webhook/services/neoleap-webook.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { CardService } from '~/card/services'; import { TransactionService } from '~/card/services/transaction.service'; import { CustomerService } from '~/customer/services'; @@ -7,14 +7,15 @@ import { AccountTransactionWebhookRequest, CardTransactionWebhookRequest, KycWebhookRequest, -} from '../dtos/requests'; +} from '../../common/modules/neoleap/dtos/requests'; @Injectable() export class NeoLeapWebhookService { constructor( + @Inject(forwardRef(() => CustomerService)) + private readonly customerService: CustomerService, private readonly transactionService: TransactionService, private readonly cardService: CardService, - private customerService: CustomerService, ) {} handleCardTransactionWebhook(body: CardTransactionWebhookRequest) { diff --git a/src/webhook/webhook.module.ts b/src/webhook/webhook.module.ts new file mode 100644 index 0000000..0e42c87 --- /dev/null +++ b/src/webhook/webhook.module.ts @@ -0,0 +1,12 @@ +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 { NeoLeapWebhookService } from './services'; + +@Module({ + providers: [NeoLeapWebhookService], + controllers: [NeoLeapWebhooksController], + imports: [CustomerModule, CardModule], +}) +export class WebhookModule {}