mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
271 lines
9.0 KiB
TypeScript
271 lines
9.0 KiB
TypeScript
import { HttpService } from '@nestjs/axios';
|
|
import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { ClassConstructor, plainToInstance } from 'class-transformer';
|
|
import moment from 'moment';
|
|
import { v4 as uuid } from 'uuid';
|
|
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 { getKycCallbackMock } from '../__mocks__/kyc-callback.mock';
|
|
import {
|
|
CreateApplicationResponse,
|
|
InitiateKycResponseDto,
|
|
InquireApplicationResponse,
|
|
UpdateCardControlsResponseDto,
|
|
} from '../dtos/response';
|
|
import {
|
|
ICreateApplicationRequest,
|
|
IInquireApplicationRequest,
|
|
INeoleapHeaderRequest,
|
|
IUpdateCardControlRequest,
|
|
} from '../interfaces';
|
|
@Injectable()
|
|
export class NeoLeapService {
|
|
private readonly logger = new Logger(NeoLeapService.name);
|
|
private readonly gatewayBaseUrl: string;
|
|
private readonly zodApiUrl: string;
|
|
private readonly apiKey: string;
|
|
private readonly useGateway: boolean;
|
|
private readonly institutionCode = '1100';
|
|
useLocalCert: boolean;
|
|
constructor(private readonly httpService: HttpService, private readonly configService: ConfigService) {
|
|
this.gatewayBaseUrl = this.configService.getOrThrow<string>('GATEWAY_URL');
|
|
this.apiKey = this.configService.getOrThrow<string>('GATEWAY_API_KEY');
|
|
this.useGateway = [true, 'true'].includes(this.configService.get<boolean>('USE_GATEWAY', false));
|
|
this.useLocalCert = this.configService.get<boolean>('USE_LOCAL_CERT', false);
|
|
this.zodApiUrl = this.configService.getOrThrow<string>('ZOD_API_URL');
|
|
}
|
|
|
|
async initiateKyc(customerId: string, body: InitiateKycRequestDto) {
|
|
const responseKey = 'InitiateKycResponseDetails';
|
|
|
|
if (!this.useGateway) {
|
|
const responseDto = plainToInstance(InitiateKycResponseDto, INITIATE_KYC_MOCK[responseKey], {
|
|
excludeExtraneousValues: true,
|
|
});
|
|
|
|
setTimeout(() => {
|
|
this.httpService
|
|
.post(`${this.zodApiUrl}/neoleap-webhooks/kyc`, getKycCallbackMock(body.nationalId), {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
.subscribe({
|
|
next: () => this.logger.log('Mock KYC webhook sent'),
|
|
error: (err) => console.error(err),
|
|
});
|
|
}, 7000);
|
|
|
|
return responseDto;
|
|
}
|
|
|
|
const payload = {
|
|
InitiateKycRequestDetails: {
|
|
CustomerIdentifier: {
|
|
InstitutionCode: this.institutionCode,
|
|
Id: customerId,
|
|
NationalId: body.nationalId,
|
|
},
|
|
},
|
|
RequestHeader: this.prepareHeaders('InitiateKyc'),
|
|
};
|
|
|
|
return this.sendRequestToNeoLeap<typeof payload, InitiateKycResponseDto>(
|
|
'kyc/InitiateKyc',
|
|
payload,
|
|
responseKey,
|
|
InitiateKycResponseDto,
|
|
);
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
const payload: ICreateApplicationRequest = {
|
|
CreateNewApplicationRequestDetails: {
|
|
ApplicationRequestDetails: {
|
|
InstitutionCode: this.institutionCode,
|
|
ExternalApplicationNumber: customer.applicationNumber.toString(),
|
|
ApplicationType: '01',
|
|
Product: '1101',
|
|
ApplicationDate: moment().format('YYYY-MM-DD'),
|
|
BranchCode: '000',
|
|
ApplicationSource: 'O',
|
|
DeliveryMethod: 'V',
|
|
},
|
|
ApplicationProcessingDetails: {
|
|
SuggestedLimit: 0,
|
|
RequestedLimit: 0,
|
|
AssignedLimit: 0,
|
|
ProcessControl: 'STND',
|
|
},
|
|
ApplicationFinancialInformation: {
|
|
Currency: {
|
|
AlphaCode: 'SAR',
|
|
},
|
|
BillingCycle: 'C1',
|
|
},
|
|
ApplicationOtherInfo: {},
|
|
ApplicationCustomerDetails: {
|
|
FirstName: customer.firstName,
|
|
LastName: customer.lastName,
|
|
FullName: customer.fullName,
|
|
DateOfBirth: moment(customer.dateOfBirth).format('YYYY-MM-DD'),
|
|
EmbossName: customer.fullName.toUpperCase(), // TODO Enter Emboss Name
|
|
IdType: '01',
|
|
IdNumber: customer.nationalId,
|
|
IdExpiryDate: moment(customer.nationalIdExpiry).format('YYYY-MM-DD'),
|
|
Title: customer.gender === Gender.MALE ? 'Mr' : 'Ms',
|
|
Gender: customer.gender === Gender.MALE ? 'M' : 'F',
|
|
LocalizedDateOfBirth: moment(customer.dateOfBirth).format('YYYY-MM-DD'),
|
|
Nationality: CountriesNumericISO[customer.countryOfResidence],
|
|
},
|
|
ApplicationAddress: {
|
|
City: customer.city,
|
|
Country: CountriesNumericISO[customer.country],
|
|
Region: customer.region,
|
|
AddressLine1: `${customer.street} ${customer.building}`,
|
|
AddressLine2: customer.neighborhood,
|
|
AddressRole: 0,
|
|
Email: customer.user.email,
|
|
Phone1: customer.user.phoneNumber,
|
|
CountryDetails: {
|
|
DefaultCurrency: {},
|
|
Description: [],
|
|
},
|
|
},
|
|
},
|
|
RequestHeader: this.prepareHeaders('CreateNewApplication'),
|
|
};
|
|
|
|
return this.sendRequestToNeoLeap<ICreateApplicationRequest, CreateApplicationResponse>(
|
|
'application/CreateNewApplication',
|
|
payload,
|
|
responseKey,
|
|
CreateApplicationResponse,
|
|
);
|
|
}
|
|
|
|
async inquireApplication(externalApplicationNumber: string) {
|
|
const responseKey = 'InquireApplicationResponseDetails';
|
|
if (!this.useGateway) {
|
|
return plainToInstance(InquireApplicationResponse, INQUIRE_APPLICATION_MOCK[responseKey], {
|
|
excludeExtraneousValues: true,
|
|
});
|
|
}
|
|
|
|
const payload = {
|
|
InquireApplicationRequestDetails: {
|
|
ApplicationIdentifier: {
|
|
InstitutionCode: this.institutionCode,
|
|
ExternalApplicationNumber: externalApplicationNumber,
|
|
},
|
|
AdditionalData: {
|
|
ReturnApplicationType: true,
|
|
ReturnApplicationStatus: true,
|
|
ReturnCustomer: true,
|
|
ReturnAddresses: true,
|
|
},
|
|
},
|
|
RequestHeader: this.prepareHeaders('InquireApplication'),
|
|
};
|
|
|
|
return this.sendRequestToNeoLeap<IInquireApplicationRequest, InquireApplicationResponse>(
|
|
'application/InquireApplication',
|
|
payload,
|
|
responseKey,
|
|
InquireApplicationResponse,
|
|
);
|
|
}
|
|
|
|
async updateCardControl(cardId: string, amount: number, count?: number) {
|
|
const responseKey = 'UpdateCardControlResponseDetails';
|
|
if (!this.useGateway) {
|
|
return;
|
|
}
|
|
|
|
const payload: IUpdateCardControlRequest = {
|
|
UpdateCardControlsRequestDetails: {
|
|
InstitutionCode: this.institutionCode,
|
|
CardIdentifier: {
|
|
InstitutionCode: this.institutionCode,
|
|
Id: cardId,
|
|
},
|
|
UsageTransactionLimit: {
|
|
AmountLimit: amount,
|
|
CountLimit: count || 10,
|
|
},
|
|
},
|
|
RequestHeader: this.prepareHeaders('UpdateCardControl'),
|
|
};
|
|
|
|
return this.sendRequestToNeoLeap<IUpdateCardControlRequest, UpdateCardControlsResponseDto>(
|
|
'cardcontrol/UpdateCardControl',
|
|
payload,
|
|
responseKey,
|
|
UpdateCardControlsResponseDto,
|
|
);
|
|
}
|
|
|
|
private prepareHeaders(serviceName: string): INeoleapHeaderRequest['RequestHeader'] {
|
|
return {
|
|
Version: '1.0.0',
|
|
MsgUid: uuid(),
|
|
Source: 'ZOD',
|
|
ServiceId: serviceName,
|
|
ReqDateTime: new Date(),
|
|
};
|
|
}
|
|
|
|
private async sendRequestToNeoLeap<T, R>(
|
|
endpoint: string,
|
|
payload: T,
|
|
responseKey: string,
|
|
responseClass: ClassConstructor<R>,
|
|
): Promise<R> {
|
|
try {
|
|
const { data } = await this.httpService.axiosRef.post(`${this.gatewayBaseUrl}/${endpoint}`, payload, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `${this.apiKey}`,
|
|
},
|
|
});
|
|
|
|
if (data.data?.ResponseHeader?.ResponseCode !== '000') {
|
|
throw new BadRequestException(
|
|
data.data.ResponseHeader.ResponseDescription || 'Unexpected Error from Service Provider',
|
|
);
|
|
}
|
|
|
|
return plainToInstance(responseClass, data.data[responseKey], {
|
|
excludeExtraneousValues: true,
|
|
});
|
|
} catch (error: any) {
|
|
if (error.status === 400) {
|
|
console.error('Error sending request to NeoLeap:', error);
|
|
throw new BadRequestException(error.response?.data?.ResponseHeader?.ResponseDescription || error.message);
|
|
}
|
|
console.error('Error sending request to NeoLeap:', error);
|
|
throw new InternalServerErrorException('Error communicating with NeoLeap service');
|
|
}
|
|
}
|
|
}
|