forked from Shared/esg
361 lines
10 KiB
TypeScript
361 lines
10 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import axios, { AxiosError } from 'axios';
|
|
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
|
import { ProviderName } from '../../common/enums/provider-name.enum';
|
|
import { ProviderEnvConfig } from '../../config/configuration';
|
|
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
|
import { AmitisProvider } from './amitis.provider';
|
|
import {
|
|
LegacyApiProvider,
|
|
LegacyInquiryPayload,
|
|
LegacyInquiryResult,
|
|
} from '../shared/legacy-api.provider.abstract';
|
|
|
|
interface CivilRegistrationPayload extends LegacyInquiryPayload {
|
|
nationalCode?: string;
|
|
NIN?: string;
|
|
birthDate?: string;
|
|
BirthDate?: string;
|
|
dateHasPostfix?: number;
|
|
}
|
|
|
|
interface PostalCodePayload extends LegacyInquiryPayload {
|
|
postalCode?: string;
|
|
PostalCode?: string;
|
|
}
|
|
|
|
interface ShahkarPayload extends LegacyInquiryPayload {
|
|
nationalCode?: string;
|
|
nationalCod?: string;
|
|
NationalCod?: string;
|
|
mobileNo?: string;
|
|
MobileNo?: string;
|
|
}
|
|
|
|
interface SayahPayload extends LegacyInquiryPayload {
|
|
accountOwnerType?: string;
|
|
nationalCode?: string;
|
|
legalId?: string | null;
|
|
sheba?: string;
|
|
}
|
|
|
|
/**
|
|
* Hamta provider — implements Hamta/CentInsur REST and SOAP inquiry services.
|
|
*/
|
|
@Injectable()
|
|
export class HamtaProvider extends LegacyApiProvider {
|
|
readonly name = ProviderName.HAMTA;
|
|
readonly supportedInquiryTypes = [
|
|
InquiryType.PERSON,
|
|
InquiryType.SHEBA,
|
|
InquiryType.SHAHKAR,
|
|
InquiryType.POSTAL_CODE,
|
|
];
|
|
|
|
private readonly hamtaConfig: ProviderEnvConfig;
|
|
|
|
constructor(configService: ConfigService, private readonly amitisProvider: AmitisProvider) {
|
|
const config = configService.get<ProviderEnvConfig>('hamta')!;
|
|
super(
|
|
config,
|
|
(providerName, inquiryType, username, password) =>
|
|
amitisProvider.getAccessToken(providerName, inquiryType, username, password),
|
|
);
|
|
this.hamtaConfig = config;
|
|
}
|
|
|
|
protected async callProvider(
|
|
inquiryType: InquiryType,
|
|
payload: LegacyInquiryPayload,
|
|
context: ProviderExecutionContext,
|
|
): Promise<LegacyInquiryResult> {
|
|
if (inquiryType === InquiryType.PERSON) {
|
|
return this.inquireCivilRegistration(payload as CivilRegistrationPayload);
|
|
}
|
|
|
|
if (inquiryType === InquiryType.POSTAL_CODE) {
|
|
return this.inquirePostalCode(payload as PostalCodePayload);
|
|
}
|
|
|
|
if (inquiryType === InquiryType.SHAHKAR) {
|
|
return this.inquireShahkar(payload as ShahkarPayload);
|
|
}
|
|
|
|
if (inquiryType === InquiryType.SHEBA) {
|
|
return this.inquireSayah(payload as SayahPayload);
|
|
}
|
|
|
|
return super.callProvider(inquiryType, payload, context);
|
|
}
|
|
|
|
private async inquirePostalCode(payload: PostalCodePayload): Promise<{ raw: unknown }> {
|
|
const postalCode = this.getRequiredString(
|
|
payload.postalCode ?? payload.PostalCode,
|
|
'postalCode',
|
|
);
|
|
|
|
const inquiryConfig = this.getInquiryConfig(InquiryType.POSTAL_CODE);
|
|
const token = await this.amitisProvider.getAccessToken(
|
|
ProviderName.HAMTA,
|
|
InquiryType.POSTAL_CODE,
|
|
inquiryConfig.username,
|
|
inquiryConfig.password,
|
|
);
|
|
|
|
try {
|
|
const response = await axios.get<Record<string, unknown>>(
|
|
`${inquiryConfig.url}/AddressByPostcode`,
|
|
{
|
|
params: { PostalCode: postalCode },
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
timeout: this.hamtaConfig.timeout,
|
|
},
|
|
);
|
|
|
|
return { raw: response.data };
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
throw this.formatProviderError(
|
|
error.message,
|
|
String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async inquireCivilRegistration(
|
|
payload: CivilRegistrationPayload,
|
|
): Promise<{ raw: unknown }> {
|
|
const nationalCode = this.getRequiredString(
|
|
payload.nationalCode ?? payload.NIN,
|
|
'nationalCode',
|
|
);
|
|
const birthDate = this.getRequiredString(payload.birthDate ?? payload.BirthDate, 'birthDate');
|
|
|
|
const inquiryConfig = this.getInquiryConfig(InquiryType.PERSON);
|
|
|
|
try {
|
|
const response = await axios.post<string>(
|
|
inquiryConfig.url,
|
|
this.buildCivilRegistrationEnvelope(
|
|
nationalCode,
|
|
birthDate,
|
|
inquiryConfig.username,
|
|
inquiryConfig.password,
|
|
),
|
|
{
|
|
headers: {
|
|
'Content-Type': 'text/xml; charset=utf-8',
|
|
SOAPAction: '"http://tempuri.org/ISabtInq/SubmitInqDteStsWithPstCod"',
|
|
},
|
|
timeout: this.hamtaConfig.timeout,
|
|
responseType: 'text',
|
|
},
|
|
);
|
|
|
|
const result = this.extractSoapValue(response.data, 'SubmitInqDteStsWithPstCodResult');
|
|
|
|
return {
|
|
raw: {
|
|
nationalCode,
|
|
birthDate,
|
|
result,
|
|
soap: response.data,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
throw this.formatProviderError(
|
|
error.message,
|
|
String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async inquireShahkar(payload: ShahkarPayload): Promise<{ raw: unknown }> {
|
|
const nationalCode = this.getRequiredString(
|
|
payload.nationalCode ?? payload.nationalCod ?? payload.NationalCod,
|
|
'nationalCode',
|
|
);
|
|
const mobileNo = this.getRequiredString(payload.mobileNo ?? payload.MobileNo, 'mobileNo');
|
|
|
|
const inquiryConfig = this.getInquiryConfig(InquiryType.SHAHKAR);
|
|
|
|
try {
|
|
const response = await axios.post<string>(
|
|
inquiryConfig.url,
|
|
this.buildShahkarEnvelope(
|
|
nationalCode,
|
|
mobileNo,
|
|
inquiryConfig.username,
|
|
inquiryConfig.password,
|
|
),
|
|
{
|
|
headers: {
|
|
'Content-Type': 'text/xml; charset=utf-8',
|
|
SOAPAction: '"http://tempuri.org/IShahkarInq/ShahkarInquery"',
|
|
},
|
|
timeout: this.hamtaConfig.timeout,
|
|
responseType: 'text',
|
|
},
|
|
);
|
|
|
|
const result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
|
|
|
|
return {
|
|
raw: {
|
|
nationalCode,
|
|
mobileNo,
|
|
result,
|
|
soap: response.data,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
throw this.formatProviderError(
|
|
error.message,
|
|
String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
|
const accountOwnerType = payload.accountOwnerType ?? '1';
|
|
const nationalId = payload.nationalCode ?? '';
|
|
const legalId = payload.legalId ?? null;
|
|
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
|
|
|
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
|
const token = await this.amitisProvider.getAccessToken(
|
|
ProviderName.HAMTA,
|
|
InquiryType.SHEBA,
|
|
inquiryConfig.username,
|
|
inquiryConfig.password,
|
|
);
|
|
|
|
try {
|
|
const response = await axios.post<{
|
|
IsSucceed?: boolean;
|
|
Errors?: Array<{ Code?: string; Message?: string }>;
|
|
Result?: unknown;
|
|
}>(
|
|
inquiryConfig.url,
|
|
{
|
|
AccountOwnerType: accountOwnerType,
|
|
NationalId: nationalId,
|
|
LegalId: legalId,
|
|
ShebaId: shebaId,
|
|
},
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
timeout: this.hamtaConfig.timeout,
|
|
},
|
|
);
|
|
|
|
if (!response.data.IsSucceed && response.data.Errors) {
|
|
throw this.formatProviderError(
|
|
this.formatErrors(response.data.Errors),
|
|
'SAYAH_HAS_ERROR',
|
|
);
|
|
}
|
|
|
|
return { raw: response.data };
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
throw this.formatProviderError(
|
|
error.message,
|
|
String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private buildCivilRegistrationEnvelope(
|
|
nationalCode: string,
|
|
birthDate: string,
|
|
username: string,
|
|
password: string,
|
|
): string {
|
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<soap:Body>
|
|
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
|
|
<NIN>${this.escapeXml(nationalCode)}</NIN>
|
|
<BirthDate>${this.escapeXml(birthDate)}</BirthDate>
|
|
<Username>${this.escapeXml(username)}</Username>
|
|
<Password>${this.escapeXml(password)}</Password>
|
|
</SubmitInqDteStsWithPstCod>
|
|
</soap:Body>
|
|
</soap:Envelope>`;
|
|
}
|
|
|
|
private buildShahkarEnvelope(
|
|
nationalCode: string,
|
|
mobileNo: string,
|
|
username: string,
|
|
password: string,
|
|
): string {
|
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<soap:Body>
|
|
<ShahkarInquery xmlns="http://tempuri.org/">
|
|
<NationalCod>${this.escapeXml(nationalCode)}</NationalCod>
|
|
<MobileNo>${this.escapeXml(mobileNo)}</MobileNo>
|
|
<Username>${this.escapeXml(username)}</Username>
|
|
<Password>${this.escapeXml(password)}</Password>
|
|
</ShahkarInquery>
|
|
</soap:Body>
|
|
</soap:Envelope>`;
|
|
}
|
|
|
|
private extractSoapValue(xml: string, tagName: string): string | null {
|
|
const match = xml.match(new RegExp(`<(?:\\w+:)?${tagName}[^>]*>([\\s\\S]*?)</(?:\\w+:)?${tagName}>`));
|
|
return match ? this.decodeXml(match[1].trim()) : null;
|
|
}
|
|
|
|
private getRequiredString(value: unknown, fieldName: string): string {
|
|
if (typeof value !== 'string' || !value.trim()) {
|
|
throw this.formatProviderError(undefined, undefined, `${fieldName} is required`);
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
private formatErrors(errors: Array<{ Code?: string; Message?: string }>): string {
|
|
return errors.map((e) => `${e.Code}: ${e.Message}`).join(', ');
|
|
}
|
|
|
|
private escapeXml(value: string): string {
|
|
return value
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
private decodeXml(value: string): string {
|
|
return value
|
|
.replace(/'/g, "'")
|
|
.replace(/"/g, '"')
|
|
.replace(/>/g, '>')
|
|
.replace(/</g, '<')
|
|
.replace(/&/g, '&');
|
|
}
|
|
}
|
|
|
|
// Made with Bob
|