forked from Shared/esg
296 lines
8.9 KiB
TypeScript
296 lines
8.9 KiB
TypeScript
import { AxiosError, AxiosInstance } from 'axios';
|
|
import { createOutboundAxiosInstance } from '../../common/helpers/http-client.helper';
|
|
import {
|
|
CentInsurApiResponse,
|
|
describeCentInsurResponse,
|
|
getCentInsurProviderError,
|
|
} from '../../common/helpers/centinsur-response.helper';
|
|
import { getSayahProviderError } from '../../common/helpers/sayah-response.helper';
|
|
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
|
import { ProviderName } from '../../common/enums/provider-name.enum';
|
|
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
|
import { BaseProvider } from '../base/base-provider.abstract';
|
|
import {
|
|
LegacyProviderApiResponse,
|
|
} from '../interfaces/provider-config.interface';
|
|
import { ProviderEnvConfig, InquiryConfig } from '../../config/configuration';
|
|
|
|
export interface PersonInquiryPayload {
|
|
nationalCode: string;
|
|
birthDate: string;
|
|
dateHasPostfix?: number;
|
|
}
|
|
|
|
export interface PersonInquiryResult {
|
|
fullName?: string;
|
|
nationalCode: string;
|
|
birthDate: string;
|
|
raw: unknown;
|
|
}
|
|
|
|
export type LegacyInquiryPayload = Record<string, unknown>;
|
|
export type LegacyInquiryResult = PersonInquiryResult | { raw: unknown };
|
|
export type LegacyAuthTokenResolver = (
|
|
providerName: ProviderName,
|
|
inquiryType: InquiryType,
|
|
username: string,
|
|
password: string,
|
|
) => Promise<string>;
|
|
|
|
/**
|
|
* Shared implementation for providers with REST API contracts (Hamta, Moallem).
|
|
*
|
|
* Supports multiple authentication methods:
|
|
* - AMITIS: Token-based authentication via AMITIS service
|
|
* - SOAP: Direct SOAP authentication (handled by subclasses)
|
|
* - NONE: No authentication required
|
|
*/
|
|
export abstract class LegacyApiProvider extends BaseProvider<
|
|
LegacyInquiryPayload,
|
|
LegacyInquiryResult
|
|
> {
|
|
protected readonly httpClient: AxiosInstance;
|
|
protected readonly providerConfig: ProviderEnvConfig;
|
|
|
|
constructor(
|
|
config: ProviderEnvConfig,
|
|
private readonly authTokenResolver?: LegacyAuthTokenResolver,
|
|
) {
|
|
super(config);
|
|
this.providerConfig = config;
|
|
this.httpClient = createOutboundAxiosInstance({
|
|
timeout: config.timeout,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
}
|
|
|
|
protected async callProvider(
|
|
inquiryType: InquiryType,
|
|
payload: LegacyInquiryPayload,
|
|
_context: ProviderExecutionContext,
|
|
): Promise<LegacyInquiryResult> {
|
|
if (inquiryType === InquiryType.PERSON) {
|
|
return this.inquirePerson(payload as unknown as PersonInquiryPayload, inquiryType);
|
|
}
|
|
|
|
return this.inquireGeneric(inquiryType, payload);
|
|
}
|
|
|
|
protected async inquirePerson(
|
|
payload: PersonInquiryPayload,
|
|
inquiryType: InquiryType,
|
|
): Promise<PersonInquiryResult> {
|
|
const inquiryConfig = this.getInquiryConfig(inquiryType);
|
|
|
|
try {
|
|
const response = await this.httpClient.post<LegacyProviderApiResponse>(
|
|
inquiryConfig.url,
|
|
{
|
|
nationalCode: payload.nationalCode,
|
|
birthDate: payload.birthDate,
|
|
},
|
|
await this.buildRequestConfig(inquiryType, inquiryConfig),
|
|
);
|
|
|
|
const body = response.data;
|
|
|
|
if (!body.success) {
|
|
throw this.formatProviderError(body.message, body.code);
|
|
}
|
|
|
|
return {
|
|
nationalCode: payload.nationalCode,
|
|
birthDate: payload.birthDate,
|
|
fullName: body.data?.fullName as string | undefined,
|
|
raw: body,
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
const data = error.response?.data as LegacyProviderApiResponse | undefined;
|
|
throw this.formatProviderError(
|
|
data?.message ?? error.message,
|
|
data?.code ?? String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
protected async inquireGeneric(
|
|
inquiryType: InquiryType,
|
|
payload: LegacyInquiryPayload,
|
|
): Promise<{ raw: unknown }> {
|
|
const inquiryConfig = this.getInquiryConfig(inquiryType);
|
|
|
|
// Transform payload for Real Estate inquiry to match CentInsur API format
|
|
const requestPayload = this.transformPayloadForInquiryType(inquiryType, payload);
|
|
|
|
try {
|
|
const response = await this.httpClient.post<LegacyProviderApiResponse>(
|
|
inquiryConfig.url,
|
|
requestPayload,
|
|
await this.buildRequestConfig(inquiryType, inquiryConfig),
|
|
);
|
|
|
|
const body = response.data;
|
|
|
|
if ('IsSucceed' in body) {
|
|
this.nestLogger.log(
|
|
`${inquiryType} provider response | http=${response.status} | ${describeCentInsurResponse(body)}`,
|
|
);
|
|
}
|
|
|
|
// Handle Sayah/Sheba API format (ReturnValue/HasError)
|
|
if ('ReturnValue' in body || 'HasError' in body) {
|
|
const providerError = getSayahProviderError(body);
|
|
if (providerError) {
|
|
throw this.formatProviderError(providerError.message, providerError.code);
|
|
}
|
|
return { raw: body };
|
|
}
|
|
|
|
// Handle CentInsur API format (IsSucceed/Result)
|
|
if ('IsSucceed' in body) {
|
|
const providerError = getCentInsurProviderError(body, inquiryType);
|
|
if (providerError) {
|
|
this.nestLogger.warn(
|
|
`${inquiryType} provider business failure | ${describeCentInsurResponse(body)}`,
|
|
);
|
|
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
|
providerTrackingCode: providerError.providerTrackingCode,
|
|
});
|
|
}
|
|
return { raw: body };
|
|
}
|
|
|
|
// Handle legacy format (success)
|
|
if (!body.success) {
|
|
throw this.formatProviderError(body.message, body.code);
|
|
}
|
|
|
|
return {
|
|
raw: body,
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof AxiosError) {
|
|
const data = error.response?.data as LegacyProviderApiResponse | undefined;
|
|
|
|
// Check for Sayah/Sheba format error
|
|
if (data && ('ReturnValue' in data || 'HasError' in data)) {
|
|
const providerError = getSayahProviderError(data);
|
|
if (providerError) {
|
|
throw this.formatProviderError(providerError.message, providerError.code);
|
|
}
|
|
}
|
|
|
|
// Check for CentInsur format error
|
|
if (data && 'IsSucceed' in data) {
|
|
const providerError = getCentInsurProviderError(data, inquiryType);
|
|
if (providerError) {
|
|
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
|
providerTrackingCode: providerError.providerTrackingCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
throw this.formatProviderError(
|
|
data?.message ?? error.message,
|
|
data?.code ?? String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform payload to match provider-specific API format
|
|
* Real Estate inquiry uses NationalId/PostalCode instead of nationalCode/postalCode
|
|
*/
|
|
protected transformPayloadForInquiryType(
|
|
inquiryType: InquiryType,
|
|
payload: LegacyInquiryPayload,
|
|
): LegacyInquiryPayload {
|
|
if (inquiryType === InquiryType.REAL_ESTATE) {
|
|
return {
|
|
NationalId: payload.nationalCode ?? payload.nationalId ?? payload.NationalId,
|
|
PostalCode: payload.postalCode ?? payload.PostalCode,
|
|
};
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
protected getInquiryConfig(inquiryType: InquiryType): InquiryConfig {
|
|
const inquiryConfig = this.providerConfig.inquiries[inquiryType];
|
|
|
|
if (!inquiryConfig || !inquiryConfig.url) {
|
|
throw this.formatProviderError(
|
|
undefined,
|
|
undefined,
|
|
`Inquiry type ${inquiryType} is not configured for ${this.name}`,
|
|
);
|
|
}
|
|
|
|
return inquiryConfig;
|
|
}
|
|
|
|
private async buildRequestConfig(
|
|
inquiryType: InquiryType,
|
|
inquiryConfig: InquiryConfig,
|
|
): Promise<{
|
|
headers?: Record<string, string>;
|
|
}> {
|
|
// No authentication required
|
|
if (inquiryConfig.authMethod === 'NONE') {
|
|
return {};
|
|
}
|
|
|
|
// SOAP authentication is handled by subclasses (e.g., Shahkar)
|
|
if (inquiryConfig.authMethod === 'SOAP') {
|
|
return {};
|
|
}
|
|
|
|
// AMITIS token-based authentication
|
|
if (inquiryConfig.authMethod === 'AMITIS') {
|
|
if (!this.authTokenResolver) {
|
|
throw this.formatProviderError(
|
|
undefined,
|
|
undefined,
|
|
`No auth token resolver configured for ${this.name}`,
|
|
);
|
|
}
|
|
|
|
if (!inquiryConfig.username || !inquiryConfig.password) {
|
|
throw this.formatProviderError(
|
|
undefined,
|
|
undefined,
|
|
`Credentials not configured for ${this.name}/${inquiryType}`,
|
|
);
|
|
}
|
|
|
|
const token = await this.authTokenResolver(
|
|
this.name,
|
|
inquiryType,
|
|
inquiryConfig.username,
|
|
inquiryConfig.password,
|
|
);
|
|
|
|
return {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
throw this.formatProviderError(
|
|
undefined,
|
|
undefined,
|
|
`Unsupported auth method ${inquiryConfig.authMethod} for ${this.name}/${inquiryType}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Made with Bob
|