mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
feat: handle card status changed webhook
This commit is contained in:
@ -10,7 +10,7 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Customer } from '~/customer/entities';
|
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 { Account } from './account.entity';
|
||||||
import { Transaction } from './transaction.entity';
|
import { Transaction } from './transaction.entity';
|
||||||
|
|
||||||
@ -41,6 +41,12 @@ export class Card {
|
|||||||
@Column({ type: 'varchar', nullable: false, default: CardStatus.PENDING })
|
@Column({ type: 'varchar', nullable: false, default: CardStatus.PENDING })
|
||||||
status!: CardStatus;
|
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 })
|
@Column({ type: 'varchar', nullable: false, default: CardScheme.VISA })
|
||||||
scheme!: CardScheme;
|
scheme!: CardScheme;
|
||||||
|
|
||||||
|
68
src/card/enums/card-status-description.enum.ts
Normal file
68
src/card/enums/card-status-description.enum.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* import { CardStatus, CardStatusDescription } from '../enums';
|
||||||
|
|
||||||
|
export const CardStatusMapper: Record<string, { description: CardStatusDescription; status: CardStatus }> = {
|
||||||
|
//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',
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
export * from './card-colors.enum';
|
export * from './card-colors.enum';
|
||||||
export * from './card-issuers.enum';
|
export * from './card-issuers.enum';
|
||||||
export * from './card-scheme.enum';
|
export * from './card-scheme.enum';
|
||||||
|
export * from './card-status-description.enum';
|
||||||
export * from './card-status.enum';
|
export * from './card-status.enum';
|
||||||
export * from './customer-type.enum';
|
export * from './customer-type.enum';
|
||||||
export * from './transaction-scope.enum';
|
export * from './transaction-scope.enum';
|
||||||
|
109
src/card/mappers/card-status-description.mapper.ts
Normal file
109
src/card/mappers/card-status-description.mapper.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { UserLocale } from '~/core/enums';
|
||||||
|
import { CardStatusDescription } from '../enums';
|
||||||
|
|
||||||
|
export const CardStatusMapper: Record<CardStatusDescription, { [key in UserLocale]: { description: string } }> = {
|
||||||
|
[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: 'حالة البطاقة غير معروفة' },
|
||||||
|
},
|
||||||
|
};
|
37
src/card/mappers/card-status.mapper.ts
Normal file
37
src/card/mappers/card-status.mapper.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { CardStatus, CardStatusDescription } from '../enums';
|
||||||
|
|
||||||
|
export const CardStatusMapper: Record<string, { description: CardStatusDescription; status: CardStatus }> = {
|
||||||
|
//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 },
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||||
import { Card } from '../entities';
|
import { Card } from '../entities';
|
||||||
import { CardColors, CardIssuers, CardScheme, CardStatus, CustomerType } from '../enums';
|
import { CardColors, CardIssuers, CardScheme, CardStatus, CardStatusDescription, CustomerType } from '../enums';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CardRepository {
|
export class CardRepository {
|
||||||
@ -19,7 +19,6 @@ export class CardRepository {
|
|||||||
firstSixDigits: card.firstSixDigits,
|
firstSixDigits: card.firstSixDigits,
|
||||||
lastFourDigits: card.lastFourDigits,
|
lastFourDigits: card.lastFourDigits,
|
||||||
color: CardColors.BLUE,
|
color: CardColors.BLUE,
|
||||||
status: CardStatus.ACTIVE,
|
|
||||||
scheme: CardScheme.VISA,
|
scheme: CardScheme.VISA,
|
||||||
issuer: CardIssuers.NEOLEAP,
|
issuer: CardIssuers.NEOLEAP,
|
||||||
accountId: accountId,
|
accountId: accountId,
|
||||||
@ -40,4 +39,11 @@ export class CardRepository {
|
|||||||
where: { customerId, status: CardStatus.ACTIVE },
|
where: { customerId, status: CardStatus.ACTIVE },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCardStatus(id: string, status: CardStatus, statusDescription: CardStatusDescription) {
|
||||||
|
return this.cardRepository.update(id, {
|
||||||
|
status: status,
|
||||||
|
statusDescription: statusDescription,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { Transactional } from 'typeorm-transactional';
|
import { Transactional } from 'typeorm-transactional';
|
||||||
|
import { AccountCardStatusChangedWebhookRequest } from '~/common/modules/neoleap/dtos/requests';
|
||||||
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||||
import { Card } from '../entities';
|
import { Card } from '../entities';
|
||||||
|
import { CardStatusMapper } from '../mappers/card-status.mapper';
|
||||||
import { CardRepository } from '../repositories';
|
import { CardRepository } from '../repositories';
|
||||||
import { AccountService } from './account.service';
|
import { AccountService } from './account.service';
|
||||||
|
|
||||||
@ -42,4 +44,11 @@ export class CardService {
|
|||||||
}
|
}
|
||||||
return card;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
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 { TransactionService } from '~/card/services/transaction.service';
|
import {
|
||||||
import { AccountTransactionWebhookRequest, CardTransactionWebhookRequest } from '../dtos/requests';
|
AccountCardStatusChangedWebhookRequest,
|
||||||
|
AccountTransactionWebhookRequest,
|
||||||
|
CardTransactionWebhookRequest,
|
||||||
|
} from '../dtos/requests';
|
||||||
|
import { NeoLeapWebhookService } from '../services';
|
||||||
|
|
||||||
@Controller('neoleap-webhooks')
|
@Controller('neoleap-webhooks')
|
||||||
@ApiTags('Neoleap Webhooks')
|
@ApiTags('Neoleap Webhooks')
|
||||||
export class NeoLeapWebhooksController {
|
export class NeoLeapWebhooksController {
|
||||||
constructor(private readonly transactionService: TransactionService) {}
|
constructor(private readonly neoleapWebhookService: NeoLeapWebhookService) {}
|
||||||
|
|
||||||
@Post('card-transaction')
|
@Post('card-transaction')
|
||||||
async handleCardTransactionWebhook(@Body() body: CardTransactionWebhookRequest) {
|
async handleCardTransactionWebhook(@Body() body: CardTransactionWebhookRequest) {
|
||||||
return this.transactionService.createCardTransaction(body);
|
return this.neoleapWebhookService.handleCardTransactionWebhook(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('account-transaction')
|
@Post('account-transaction')
|
||||||
async handleAccountTransactionWebhook(@Body() body: AccountTransactionWebhookRequest) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export class NeoTestController {
|
|||||||
@ApiDataResponse(InquireApplicationResponse)
|
@ApiDataResponse(InquireApplicationResponse)
|
||||||
async inquireApplication(@AuthenticatedUser() user: IJwtPayload) {
|
async inquireApplication(@AuthenticatedUser() user: IJwtPayload) {
|
||||||
const customer = await this.customerService.findCustomerById(user.sub);
|
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);
|
return ResponseFactory.data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export * from './account-card-status-changed-webhook.request.dto';
|
||||||
export * from './account-transaction-webhook.request.dto';
|
export * from './account-transaction-webhook.request.dto';
|
||||||
export * from './card-transaction-webhook.request.dto';
|
export * from './card-transaction-webhook.request.dto';
|
||||||
export * from './update-card-controls.request.dto';
|
export * from './update-card-controls.request.dto';
|
||||||
|
@ -4,11 +4,12 @@ import { CardModule } from '~/card/card.module';
|
|||||||
import { CustomerModule } from '~/customer/customer.module';
|
import { CustomerModule } from '~/customer/customer.module';
|
||||||
import { NeoLeapWebhooksController } from './controllers/neoleap-webhooks.controller';
|
import { NeoLeapWebhooksController } from './controllers/neoleap-webhooks.controller';
|
||||||
import { NeoTestController } from './controllers/neotest.controller';
|
import { NeoTestController } from './controllers/neotest.controller';
|
||||||
|
import { NeoLeapWebhookService } from './services';
|
||||||
import { NeoLeapService } from './services/neoleap.service';
|
import { NeoLeapService } from './services/neoleap.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HttpModule, CustomerModule, CardModule],
|
imports: [HttpModule, CustomerModule, CardModule],
|
||||||
controllers: [NeoTestController, NeoLeapWebhooksController],
|
controllers: [NeoTestController, NeoLeapWebhooksController],
|
||||||
providers: [NeoLeapService],
|
providers: [NeoLeapService, NeoLeapWebhookService],
|
||||||
})
|
})
|
||||||
export class NeoLeapModule {}
|
export class NeoLeapModule {}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './neoleap-webook.service';
|
||||||
|
export * from './neoleap.service';
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,7 @@ export class NeoLeapService {
|
|||||||
CreateNewApplicationRequestDetails: {
|
CreateNewApplicationRequestDetails: {
|
||||||
ApplicationRequestDetails: {
|
ApplicationRequestDetails: {
|
||||||
InstitutionCode: this.institutionCode,
|
InstitutionCode: this.institutionCode,
|
||||||
ExternalApplicationNumber: customer.waitingNumber.toString(),
|
ExternalApplicationNumber: customer.applicationNumber.toString(),
|
||||||
ApplicationType: '01',
|
ApplicationType: '01',
|
||||||
Product: '1101',
|
Product: '1101',
|
||||||
ApplicationDate: moment().format('YYYY-MM-DD'),
|
ApplicationDate: moment().format('YYYY-MM-DD'),
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
export enum UserLocale {
|
import { ObjectValues } from '../types';
|
||||||
ARABIC = 'ar',
|
|
||||||
ENGLISH = 'en',
|
export const UserLocale = {
|
||||||
}
|
ARABIC: 'ar',
|
||||||
|
ENGLISH: 'en',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UserLocale = ObjectValues<typeof UserLocale>;
|
||||||
|
@ -97,7 +97,7 @@ export class CustomerResponseDto {
|
|||||||
this.gender = customer.gender;
|
this.gender = customer.gender;
|
||||||
this.isJunior = customer.isJunior;
|
this.isJunior = customer.isJunior;
|
||||||
this.isGuardian = customer.isGuardian;
|
this.isGuardian = customer.isGuardian;
|
||||||
this.waitingNumber = customer.waitingNumber;
|
this.waitingNumber = customer.applicationNumber;
|
||||||
this.country = customer.country;
|
this.country = customer.country;
|
||||||
this.region = customer.region;
|
this.region = customer.region;
|
||||||
this.city = customer.city;
|
this.city = customer.city;
|
||||||
|
@ -71,9 +71,9 @@ export class Customer extends BaseEntity {
|
|||||||
@Column('boolean', { default: false, name: 'is_guardian' })
|
@Column('boolean', { default: false, name: 'is_guardian' })
|
||||||
isGuardian!: boolean;
|
isGuardian!: boolean;
|
||||||
|
|
||||||
@Column('int', { name: 'waiting_number' })
|
@Column('int', { name: 'application_number' })
|
||||||
@Generated('increment')
|
@Generated('increment')
|
||||||
waitingNumber!: number;
|
applicationNumber!: number;
|
||||||
|
|
||||||
@Column('varchar', { name: 'user_id' })
|
@Column('varchar', { name: 'user_id' })
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
16
src/db/migrations/1753098116701-update-card-table.ts
Normal file
16
src/db/migrations/1753098116701-update-card-table.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class UpdateCardTable1753098116701 implements MigrationInterface {
|
||||||
|
name = 'UpdateCardTable1753098116701'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "limit"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "cards" DROP COLUMN "statusDescription"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/db/migrations/1753098326876-edit-customer-table.ts
Normal file
16
src/db/migrations/1753098326876-edit-customer-table.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class EditCustomerTable1753098326876 implements MigrationInterface {
|
||||||
|
name = 'EditCustomerTable1753098326876'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -29,3 +29,5 @@ export * from './1749633935436-create-card-entity';
|
|||||||
export * from './1751456987627-create-account-entity';
|
export * from './1751456987627-create-account-entity';
|
||||||
export * from './1751466314709-create-transaction-table';
|
export * from './1751466314709-create-transaction-table';
|
||||||
export * from './1752056898465-edit-transaction-table';
|
export * from './1752056898465-edit-transaction-table';
|
||||||
|
export * from './1753098116701-update-card-table';
|
||||||
|
export * from './1753098326876-edit-customer-table';
|
||||||
|
Reference in New Issue
Block a user