forked from Shared/esg
moallem all endpoints working
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { AxiosError } from 'axios';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { NormalizedErrorDto } from '../../common/dto/normalized-error.dto';
|
||||
@@ -131,19 +132,41 @@ export abstract class BaseProvider<TRequest = unknown, TResponse = unknown>
|
||||
protected formatProviderError(
|
||||
providerMessage?: string,
|
||||
providerCode?: string,
|
||||
fallbackMessage = 'Provider returned an error',
|
||||
fallbackMessage = 'Provider request failed',
|
||||
): Error {
|
||||
const message = providerMessage?.trim() || fallbackMessage;
|
||||
const code = providerCode?.trim() || 'PROVIDER_ERROR';
|
||||
const normalized = this.normalizeError({
|
||||
code: 'PROVIDER_ERROR',
|
||||
message: fallbackMessage,
|
||||
providerMessage,
|
||||
providerCode,
|
||||
code,
|
||||
message,
|
||||
providerMessage: message,
|
||||
providerCode: code,
|
||||
});
|
||||
const err = new Error(normalized.message);
|
||||
const err = new Error(message);
|
||||
(err as Error & { normalizedError: NormalizedErrorDto }).normalizedError = normalized;
|
||||
return err;
|
||||
}
|
||||
|
||||
protected formatAxiosError(error: AxiosError): Error {
|
||||
const data = error.response?.data;
|
||||
if (typeof data === 'string') {
|
||||
const faultMatch = data.match(
|
||||
/<(?:\w+:)?faultstring[^>]*>([\s\S]*?)<\/(?:\w+:)?faultstring>/i,
|
||||
);
|
||||
if (faultMatch?.[1]) {
|
||||
return this.formatProviderError(
|
||||
faultMatch[1].trim().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'),
|
||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.formatProviderError(
|
||||
error.message,
|
||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract callProvider(
|
||||
inquiryType: InquiryType,
|
||||
payload: TRequest,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError, AxiosInstance } from 'axios';
|
||||
import { AxiosError, AxiosInstance } from 'axios';
|
||||
import {
|
||||
createOutboundAxiosInstance,
|
||||
isOutboundHttpDebugEnabled,
|
||||
} from '../../common/helpers/http-client.helper';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { AmitisAuthServiceConfig } from '../../config/configuration';
|
||||
@@ -22,6 +26,8 @@ interface CentInsurTokenResponse {
|
||||
ExpiresIn?: number | string;
|
||||
expiresIn?: number | string;
|
||||
expires_in?: number | string;
|
||||
IsSucceed?: boolean;
|
||||
LoginStatus?: number;
|
||||
data?: CentInsurTokenResponse;
|
||||
}
|
||||
|
||||
@@ -49,7 +55,7 @@ export class AmitisProvider {
|
||||
private readonly generalTokenService: GeneralTokenService,
|
||||
) {
|
||||
this.config = configService.get<AmitisAuthServiceConfig>('amitis')!;
|
||||
this.httpClient = axios.create({
|
||||
this.httpClient = createOutboundAxiosInstance({
|
||||
baseURL: this.config.baseUrl,
|
||||
timeout: this.config.timeout,
|
||||
});
|
||||
@@ -102,25 +108,36 @@ export class AmitisProvider {
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<string> {
|
||||
// Use FormData for multipart/form-data format
|
||||
const FormData = require('form-data');
|
||||
const formData = new FormData();
|
||||
formData.append('username', username);
|
||||
formData.append('password', password);
|
||||
const loginBody = new URLSearchParams({
|
||||
username: username.toLowerCase(),
|
||||
password,
|
||||
});
|
||||
|
||||
try {
|
||||
const loginUrl = `${this.config.baseUrl}${this.config.loginPath}`;
|
||||
this.logger.log(
|
||||
`AMITIS login → POST ${loginUrl} | provider=${providerName} | inquiry=${inquiryType} | username=${username.toLowerCase()}`,
|
||||
);
|
||||
|
||||
const response = await this.httpClient.post<CentInsurTokenResponse>(
|
||||
this.config.loginPath,
|
||||
formData,
|
||||
loginBody.toString(),
|
||||
{
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (isOutboundHttpDebugEnabled()) {
|
||||
this.logger.log(
|
||||
`AMITIS login ← ${response.status} | provider=${providerName} | inquiry=${inquiryType} | bodyKeys=${Object.keys(response.data ?? {}).join(',') || 'none'}`,
|
||||
);
|
||||
}
|
||||
|
||||
const token = this.extractToken(response.data);
|
||||
|
||||
await this.saveToken(token, providerName, inquiryType, username, 'login');
|
||||
await this.saveToken(token, providerName, inquiryType, username, password, 'login');
|
||||
return token.accessToken;
|
||||
} catch (error) {
|
||||
throw this.toAuthError(`AMITIS login failed for ${providerName}/${inquiryType}`, error);
|
||||
@@ -144,6 +161,7 @@ export class AmitisProvider {
|
||||
providerName,
|
||||
inquiryType,
|
||||
latestToken.username,
|
||||
latestToken.clientSecret,
|
||||
'refresh',
|
||||
);
|
||||
return token.accessToken;
|
||||
@@ -160,15 +178,16 @@ export class AmitisProvider {
|
||||
providerName: ProviderName,
|
||||
inquiryType: InquiryType,
|
||||
username: string,
|
||||
password: string,
|
||||
scope: string,
|
||||
): Promise<void> {
|
||||
await this.generalTokenService.create({
|
||||
serviceProvider: this.getTokenKey(providerName, inquiryType),
|
||||
tokenType: token.tokenType,
|
||||
url: this.config.baseUrl,
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
username,
|
||||
clientId: username.toLowerCase(),
|
||||
clientSecret: password,
|
||||
username: username.toLowerCase(),
|
||||
scope,
|
||||
accessToken: token.accessToken,
|
||||
refreshToken: token.refreshToken,
|
||||
@@ -182,11 +201,21 @@ export class AmitisProvider {
|
||||
fallbackRefreshToken?: string,
|
||||
): AmitisAuthToken {
|
||||
const body = responseBody.data ?? responseBody;
|
||||
|
||||
if (body.IsSucceed === false) {
|
||||
throw new Error(
|
||||
`AMITIS login rejected (LoginStatus=${body.LoginStatus ?? 'unknown'})`,
|
||||
);
|
||||
}
|
||||
|
||||
const accessToken =
|
||||
body.Token ?? body.token ?? body.AccessToken ?? body.accessToken ?? body.access_token;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('AMITIS auth response did not include an access token');
|
||||
if (!accessToken || !String(accessToken).trim()) {
|
||||
const bodyPreview = JSON.stringify(responseBody).slice(0, 500);
|
||||
throw new Error(
|
||||
`AMITIS auth response did not include an access token | body=${bodyPreview}`,
|
||||
);
|
||||
}
|
||||
|
||||
const expiresIn = Number(body.ExpiresIn ?? body.expiresIn ?? body.expires_in ?? 20 * 60);
|
||||
@@ -229,7 +258,13 @@ export class AmitisProvider {
|
||||
private toAuthError(message: string, error: unknown): Error {
|
||||
if (error instanceof AxiosError) {
|
||||
const status = error.response?.status ?? 'NETWORK_ERROR';
|
||||
return new Error(`${message}: ${status} ${error.message}`);
|
||||
const body =
|
||||
error.response?.data !== undefined
|
||||
? JSON.stringify(error.response.data).slice(0, 500)
|
||||
: undefined;
|
||||
return new Error(
|
||||
body ? `${message}: ${status} ${error.message} | body=${body}` : `${message}: ${status} ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||
import {
|
||||
getSayahProviderError,
|
||||
SayahApiResponse,
|
||||
} from '../../common/helpers/sayah-response.helper';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { ProviderEnvConfig } from '../../config/configuration';
|
||||
@@ -107,11 +112,11 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
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 };
|
||||
@@ -146,14 +151,14 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
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 result = this.extractSoapValue(response.data, 'SubmitInqDteStsWithPstCodResult');
|
||||
@@ -195,14 +200,14 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
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 result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
|
||||
@@ -241,11 +246,7 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await axios.post<{
|
||||
IsSucceed?: boolean;
|
||||
Errors?: Array<{ Code?: string; Message?: string }>;
|
||||
Result?: unknown;
|
||||
}>(
|
||||
const response = await axios.post<SayahApiResponse>(
|
||||
inquiryConfig.url,
|
||||
{
|
||||
AccountOwnerType: accountOwnerType,
|
||||
@@ -253,20 +254,18 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
LegalId: legalId,
|
||||
ShebaId: shebaId,
|
||||
},
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
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',
|
||||
);
|
||||
const providerError = getSayahProviderError(response.data);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code);
|
||||
}
|
||||
|
||||
return { raw: response.data };
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
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 { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { ProviderEnvConfig } from '../../config/configuration';
|
||||
@@ -10,6 +20,7 @@ import {
|
||||
LegacyApiProvider,
|
||||
LegacyInquiryPayload,
|
||||
LegacyInquiryResult,
|
||||
PersonInquiryResult,
|
||||
} from '../shared/legacy-api.provider.abstract';
|
||||
|
||||
interface CivilRegistrationPayload extends LegacyInquiryPayload {
|
||||
@@ -107,11 +118,11 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
try {
|
||||
const response = await axios.get<Record<string, unknown>>(
|
||||
`${inquiryConfig.url}/AddressByPostcode`,
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
params: { PostalCode: postalCode },
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
timeout: this.moallemConfig.timeout,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return { raw: response.data };
|
||||
@@ -128,7 +139,7 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
|
||||
private async inquireCivilRegistration(
|
||||
payload: CivilRegistrationPayload,
|
||||
): Promise<{ raw: unknown }> {
|
||||
): Promise<PersonInquiryResult> {
|
||||
const nationalCode = this.getRequiredString(
|
||||
payload.nationalCode ?? payload.NIN,
|
||||
'nationalCode',
|
||||
@@ -141,37 +152,39 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
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/ISabtV3/SabtInquery"',
|
||||
SOAPAction: '"http://tempuri.org/ISabtInq/SubmitInqDteStsWithPstCod"',
|
||||
},
|
||||
timeout: this.moallemConfig.timeout,
|
||||
responseType: 'text',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = this.extractSoapValue(response.data, 'SabtInqueryResult');
|
||||
const civilRegistration = parseCiiEstelamResult(response.data);
|
||||
const providerError = getCivilRegistrationProviderError(civilRegistration);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code);
|
||||
}
|
||||
|
||||
const fullName = buildCivilRegistrationFullName(civilRegistration);
|
||||
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
birthDate,
|
||||
result,
|
||||
soap: response.data,
|
||||
},
|
||||
nationalCode,
|
||||
birthDate,
|
||||
fullName: fullName || undefined,
|
||||
raw: civilRegistration,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw this.formatProviderError(
|
||||
error.message,
|
||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
||||
);
|
||||
throw this.formatAxiosError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -195,14 +208,14 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
inquiryConfig.username,
|
||||
inquiryConfig.password,
|
||||
),
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
SOAPAction: '"http://tempuri.org/IShahkarInq/ShahkarInquery"',
|
||||
},
|
||||
timeout: this.moallemConfig.timeout,
|
||||
responseType: 'text',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
|
||||
@@ -241,11 +254,7 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await axios.post<{
|
||||
IsSucceed?: boolean;
|
||||
Errors?: Array<{ Code?: string; Message?: string }>;
|
||||
Result?: unknown;
|
||||
}>(
|
||||
const response = await axios.post<SayahApiResponse>(
|
||||
inquiryConfig.url,
|
||||
{
|
||||
AccountOwnerType: accountOwnerType,
|
||||
@@ -253,20 +262,18 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
LegalId: legalId,
|
||||
ShebaId: shebaId,
|
||||
},
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: this.moallemConfig.timeout,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (!response.data.IsSucceed && response.data.Errors) {
|
||||
throw this.formatProviderError(
|
||||
this.formatErrors(response.data.Errors),
|
||||
'SAYAH_HAS_ERROR',
|
||||
);
|
||||
const providerError = getSayahProviderError(response.data);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code);
|
||||
}
|
||||
|
||||
return { raw: response.data };
|
||||
@@ -282,22 +289,38 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
}
|
||||
|
||||
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>
|
||||
<SabtInquery xmlns="http://tempuri.org/">
|
||||
<NIN>${this.escapeXml(nationalCode)}</NIN>
|
||||
<BirthDate>${this.escapeXml(birthDate)}</BirthDate>
|
||||
<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>
|
||||
</SabtInquery>
|
||||
</SubmitInqDteStsWithPstCod>
|
||||
</soap:Body>
|
||||
</soap:Envelope>`;
|
||||
}
|
||||
@@ -341,10 +364,10 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
|
||||
private escapeXml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||
import {
|
||||
getSayahProviderError,
|
||||
SayahApiResponse,
|
||||
} 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';
|
||||
@@ -172,16 +177,19 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get<ParsianShahkarResponse>(inquiryConfig.url, {
|
||||
params: {
|
||||
nationalCode,
|
||||
mobileNumber,
|
||||
},
|
||||
headers: {
|
||||
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
});
|
||||
const response = await axios.get<ParsianShahkarResponse>(
|
||||
inquiryConfig.url,
|
||||
mergeOutboundAxiosConfig({
|
||||
params: {
|
||||
nationalCode,
|
||||
mobileNumber,
|
||||
},
|
||||
headers: {
|
||||
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
}),
|
||||
);
|
||||
|
||||
const body = response.data;
|
||||
return {
|
||||
@@ -230,14 +238,14 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
inquiryConfig.username,
|
||||
inquiryConfig.password,
|
||||
),
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
SOAPAction: '"http://tempuri.org/ISabtInq/SubmitInqDteStsWithPstCod"',
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
responseType: 'text',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -348,14 +356,14 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
inquiryConfig.password,
|
||||
passwordFieldName,
|
||||
),
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
SOAPAction: `"http://tempuri.org/ICarAllPlcysV4/${methodName}"`,
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
responseType: 'text',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -396,7 +404,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await axios.post<ParsianSayahResponse>(
|
||||
const response = await axios.post<SayahApiResponse>(
|
||||
inquiryConfig.url,
|
||||
{
|
||||
AccountOwnerType: accountOwnerType,
|
||||
@@ -404,23 +412,18 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
LegalId: legalId,
|
||||
ShebaId: shebaId,
|
||||
},
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
(response.data.IsSucceed === false || response.data.isSucceed === false) &&
|
||||
this.hasErrors(response.data.Errors ?? response.data.errors)
|
||||
) {
|
||||
throw this.formatProviderError(
|
||||
this.formatErrors(response.data.Errors ?? response.data.errors),
|
||||
'SAYAH_HAS_ERROR',
|
||||
);
|
||||
const providerError = getSayahProviderError(response.data);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code);
|
||||
}
|
||||
|
||||
return { raw: response.data };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError, AxiosInstance } from 'axios';
|
||||
import { createOutboundAxiosInstance, mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { jalaliDateToGregorianDate } from '../../common/helpers/jalali-date.helper';
|
||||
@@ -51,7 +52,7 @@ export class TejaratNouProvider implements InquiryProvider<PersonInquiryPayload,
|
||||
private readonly cachedInquiryResultService: CachedInquiryResultService,
|
||||
) {
|
||||
this.config = this.configService.get<TejaratNouConfig>('tejaratnou')!;
|
||||
this.httpClient = axios.create({
|
||||
this.httpClient = createOutboundAxiosInstance({
|
||||
baseURL: this.config.inquiryBaseUrl,
|
||||
timeout: this.config.timeout,
|
||||
headers: {
|
||||
@@ -227,13 +228,13 @@ export class TejaratNouProvider implements InquiryProvider<PersonInquiryPayload,
|
||||
password: this.config.password,
|
||||
scope: 'api-gateway access-management',
|
||||
}),
|
||||
{
|
||||
mergeOutboundAxiosConfig({
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Cookie: 'cookiesession1=678A8C5D2222753B93723D81AA53FF8C',
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const jsonData = response.data;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import axios, { AxiosError, AxiosInstance } from 'axios';
|
||||
import { AxiosError, AxiosInstance } from 'axios';
|
||||
import { createOutboundAxiosInstance } from '../../common/helpers/http-client.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';
|
||||
@@ -51,7 +53,7 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
) {
|
||||
super(config);
|
||||
this.providerConfig = config;
|
||||
this.httpClient = axios.create({
|
||||
this.httpClient = createOutboundAxiosInstance({
|
||||
timeout: config.timeout,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -131,15 +133,9 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
|
||||
// Handle Sayah/Sheba API format (ReturnValue/HasError)
|
||||
if ('ReturnValue' in body || 'HasError' in body) {
|
||||
if (body.HasError) {
|
||||
// Format error message from Errors object
|
||||
const errorMessages = body.Errors
|
||||
? Object.entries(body.Errors).map(([code, field]) => `${field} (code: ${code})`).join(', ')
|
||||
: 'Request failed';
|
||||
throw this.formatProviderError(
|
||||
errorMessages,
|
||||
'SAYAH_ERROR',
|
||||
);
|
||||
const providerError = getSayahProviderError(body);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code);
|
||||
}
|
||||
return { raw: body };
|
||||
}
|
||||
@@ -168,14 +164,11 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
const data = error.response?.data as LegacyProviderApiResponse | undefined;
|
||||
|
||||
// Check for Sayah/Sheba format error
|
||||
if (data && ('ReturnValue' in data || 'HasError' in data) && data.HasError) {
|
||||
const errorMessages = data.Errors
|
||||
? Object.entries(data.Errors).map(([code, field]) => `${field} (code: ${code})`).join(', ')
|
||||
: error.message;
|
||||
throw this.formatProviderError(
|
||||
errorMessages,
|
||||
String(error.response?.status ?? 'SAYAH_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
|
||||
|
||||
@@ -55,27 +55,34 @@ export class ProviderOrchestratorService {
|
||||
} catch (error) {
|
||||
const normalized = this.extractError(error);
|
||||
errors.push(normalized);
|
||||
const errorSummary = normalized.providerMessage ?? normalized.message;
|
||||
const hasNextProvider = providers.indexOf(provider) < providers.length - 1;
|
||||
this.logger.warn(
|
||||
hasNextProvider
|
||||
? `Provider ${provider.name} failed for ${inquiryType}, trying next fallback`
|
||||
: `Provider ${provider.name} failed for ${inquiryType}, no fallback available`,
|
||||
? `Provider ${provider.name} failed for ${inquiryType}, trying next fallback | ${errorSummary}`
|
||||
: `Provider ${provider.name} failed for ${inquiryType}, no fallback available | ${errorSummary}`,
|
||||
);
|
||||
this.logger.debug(
|
||||
`Attempt duration: ${Date.now() - attemptStart}ms | error: ${normalized.message}`,
|
||||
this.logger.warn(
|
||||
`Attempt duration: ${Date.now() - attemptStart}ms | providerCode=${normalized.providerCode ?? normalized.code} | providerMessage=${normalized.providerMessage ?? normalized.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InquiryException('All providers failed', {
|
||||
code: providers.length === 1 ? 'PROVIDER_SERVICE_NOT_AVAILABLE' : 'ALL_PROVIDERS_FAILED',
|
||||
message:
|
||||
providers.length === 1
|
||||
? `${providers[0].name} service is not available`
|
||||
: errors.map((e) => e.message).join('; '),
|
||||
providerMessage: errors[0]?.providerMessage,
|
||||
providerCode: errors[0]?.providerCode,
|
||||
});
|
||||
const lastError = errors[errors.length - 1]!;
|
||||
|
||||
if (providers.length === 1) {
|
||||
throw new InquiryException(lastError.message, lastError);
|
||||
}
|
||||
|
||||
throw new InquiryException(
|
||||
errors.map((error) => error.message).join('; '),
|
||||
{
|
||||
code: 'ALL_PROVIDERS_FAILED',
|
||||
message: errors.map((error) => error.message).join('; '),
|
||||
providerMessage: lastError.providerMessage ?? lastError.message,
|
||||
providerCode: lastError.providerCode ?? lastError.code,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private extractError(error: unknown): NormalizedErrorDto {
|
||||
|
||||
Reference in New Issue
Block a user