Files
esg/src/providers/implementations/hamta.provider.ts
2026-06-15 17:13:21 +03:30

399 lines
12 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, { AxiosError } from 'axios';
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
import {
buildCivilRegistrationFullName,
getCivilRegistrationProviderError,
parseCiiEstelamResult,
} from '../../common/helpers/soap-civil-registration.helper';
import {
getSayahProviderError,
SayahApiResponse,
} from '../../common/helpers/sayah-response.helper';
import {
describeShahkarResponse,
getShahkarProviderError,
parseShahkarInqueryResult,
} from '../../common/helpers/shahkar-response.helper';
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,
PersonInquiryResult,
} 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,
InquiryType.REAL_ESTATE,
];
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);
this.assertAmitisCredentials(InquiryType.POSTAL_CODE, inquiryConfig);
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`,
mergeOutboundAxiosConfig({
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<PersonInquiryResult> {
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(
payload,
nationalCode,
birthDate,
inquiryConfig.username,
inquiryConfig.password,
),
mergeOutboundAxiosConfig({
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"http://tempuri.org/ISabtInq/SubmitInqDteStsWithPstCod"',
},
timeout: this.hamtaConfig.timeout,
responseType: 'text',
}),
);
const civilRegistration = parseCiiEstelamResult(response.data);
const providerError = getCivilRegistrationProviderError(civilRegistration);
if (providerError) {
throw this.formatProviderError(providerError.message, providerError.code);
}
const fullName = buildCivilRegistrationFullName(civilRegistration);
return {
nationalCode,
birthDate,
fullName: fullName || undefined,
raw: civilRegistration,
};
} catch (error) {
if (error instanceof AxiosError) {
throw this.formatAxiosError(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,
),
mergeOutboundAxiosConfig({
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"http://tempuri.org/IShahkarInq/ShahkarInquery"',
},
timeout: this.hamtaConfig.timeout,
responseType: 'text',
}),
);
const shahkarFields = parseShahkarInqueryResult(response.data);
const providerError = getShahkarProviderError(shahkarFields);
if (providerError) {
this.nestLogger.warn(
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
);
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
providerTrackingCode: providerError.providerTrackingCode,
});
}
return {
raw: {
nationalCode,
mobileNo,
...shahkarFields,
},
};
} 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);
this.assertAmitisCredentials(InquiryType.SHEBA, inquiryConfig);
const token = await this.amitisProvider.getAccessToken(
ProviderName.HAMTA,
InquiryType.SHEBA,
inquiryConfig.username,
inquiryConfig.password,
);
try {
const response = await axios.post<SayahApiResponse>(
inquiryConfig.url,
{
AccountOwnerType: accountOwnerType,
NationalId: nationalId,
LegalId: legalId,
ShebaId: shebaId,
},
mergeOutboundAxiosConfig({
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
timeout: this.hamtaConfig.timeout,
}),
);
const providerError = getSayahProviderError(response.data);
if (providerError) {
throw this.formatProviderError(providerError.message, providerError.code);
}
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(
payload: CivilRegistrationPayload,
nationalCode: string,
birthDate: string,
username: string,
password: string,
): string {
const birthDateCompact = birthDate.replace(/-/g, '');
const dateHasPostfix = payload.dateHasPostfix ?? 0;
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/">
<req>
<Nin>${this.escapeXml(nationalCode)}</Nin>
<Shenasnameserial>0</Shenasnameserial>
<ShenasnameNo>0</ShenasnameNo>
<BirthDate>${this.escapeXml(birthDateCompact)}</BirthDate>
<DateHasPostfix>${dateHasPostfix}</DateHasPostfix>
<Gender>0</Gender>
<OfficeCode>0</OfficeCode>
<BookNo>0</BookNo>
<NameHasPrefix>0</NameHasPrefix>
<NameHasPostFix>0</NameHasPostFix>
<FamilyHasPrefix>0</FamilyHasPrefix>
<FamilyHasPostFix>0</FamilyHasPostFix>
</req>
<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 assertAmitisCredentials(
inquiryType: InquiryType,
inquiryConfig: { username: string; password: string },
): void {
const envPrefix = inquiryType.replace(/_INQUIRY$/, '');
if (!inquiryConfig.username?.trim() || !inquiryConfig.password?.trim()) {
throw this.formatProviderError(
`${envPrefix} AMITIS credentials are missing. Set HAMTA_${envPrefix}_USERNAME and HAMTA_${envPrefix}_PASSWORD in .env`,
'PROVIDER_NOT_CONFIGURED',
);
}
}
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
}
// Made with Bob