forked from Shared/esg
requests fix
This commit is contained in:
@@ -2,9 +2,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|||||||
import { IsIn, IsNotEmpty, IsOptional, IsString, Length, Matches } from 'class-validator';
|
import { IsIn, IsNotEmpty, IsOptional, IsString, Length, Matches } from 'class-validator';
|
||||||
|
|
||||||
export class SayahRequestDto {
|
export class SayahRequestDto {
|
||||||
@ApiProperty({ example: '0', description: '0 for natural person' })
|
@ApiProperty({ example: '1', description: '1 for natural person (always use "1")' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsIn(['0'])
|
@IsIn(['1'])
|
||||||
accountOwnerType!: string;
|
accountOwnerType!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '4311402422', description: 'Iranian national code (10 digits)' })
|
@ApiProperty({ example: '4311402422', description: 'Iranian national code (10 digits)' })
|
||||||
@@ -19,7 +19,7 @@ export class SayahRequestDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
legalId?: string | null;
|
legalId?: string | null;
|
||||||
|
|
||||||
@ApiProperty({ example: 'IR000000000000000000000000', description: 'Iranian Sheba ID' })
|
@ApiProperty({ example: 'IR800560611828005105117001', description: 'Iranian Sheba ID (26 characters)' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@Length(26, 26)
|
@Length(26, 26)
|
||||||
@Matches(/^IR\d{24}$/, { message: 'shebaId must start with IR and contain 24 digits' })
|
@Matches(/^IR\d{24}$/, { message: 'shebaId must start with IR and contain 24 digits' })
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ export class AmitisProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get access token for a specific provider and inquiry type
|
* Get access token for a specific provider and inquiry type
|
||||||
* Handles token caching, refresh, and daily expiration
|
* Handles token caching, refresh, and 24-hour expiration
|
||||||
|
* - Access token expires in 20 minutes
|
||||||
|
* - Refresh token expires in 24 hours from creation
|
||||||
*/
|
*/
|
||||||
async getAccessToken(
|
async getAccessToken(
|
||||||
providerName: ProviderName,
|
providerName: ProviderName,
|
||||||
@@ -72,13 +74,13 @@ export class AmitisProvider {
|
|||||||
const tokenKey = this.getTokenKey(providerName, inquiryType);
|
const tokenKey = this.getTokenKey(providerName, inquiryType);
|
||||||
const latestToken = await this.generalTokenService.getLatestToken(tokenKey);
|
const latestToken = await this.generalTokenService.getLatestToken(tokenKey);
|
||||||
|
|
||||||
// Use cached token if valid and from same day
|
// Use cached token if valid and within 24-hour window
|
||||||
if (latestToken && !this.isExpired(latestToken) && this.isSameTokenDay(latestToken)) {
|
if (latestToken && !this.isExpired(latestToken) && this.isWithin24Hours(latestToken)) {
|
||||||
return latestToken.accessToken;
|
return latestToken.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to refresh if we have a refresh token and it's from the same day
|
// Try to refresh if we have a refresh token and it's within 24 hours
|
||||||
if (latestToken?.refreshToken && this.isSameTokenDay(latestToken)) {
|
if (latestToken?.refreshToken && this.isWithin24Hours(latestToken)) {
|
||||||
try {
|
try {
|
||||||
return await this.refreshAccessToken(latestToken, providerName, inquiryType);
|
return await this.refreshAccessToken(latestToken, providerName, inquiryType);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -100,18 +102,19 @@ export class AmitisProvider {
|
|||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Use URLSearchParams for application/x-www-form-urlencoded format
|
// Use FormData for multipart/form-data format
|
||||||
const formData = new URLSearchParams();
|
const FormData = require('form-data');
|
||||||
|
const formData = new FormData();
|
||||||
formData.append('username', username);
|
formData.append('username', username);
|
||||||
formData.append('password', password);
|
formData.append('password', password);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.httpClient.post<CentInsurTokenResponse>(
|
const response = await this.httpClient.post<CentInsurTokenResponse>(
|
||||||
this.config.loginPath,
|
this.config.loginPath,
|
||||||
formData.toString(),
|
formData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
...formData.getHeaders(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -198,25 +201,25 @@ export class AmitisProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isExpired(token: GeneralTokenDocument): boolean {
|
private isExpired(token: GeneralTokenDocument): boolean {
|
||||||
|
// Access token expires in 20 minutes, add 60 second buffer
|
||||||
const refreshBufferMs = 60 * 1000;
|
const refreshBufferMs = 60 * 1000;
|
||||||
return !token.expiresAt || Date.now() + refreshBufferMs >= token.expiresAt.getTime();
|
return !token.expiresAt || Date.now() + refreshBufferMs >= token.expiresAt.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private isSameTokenDay(token: GeneralTokenDocument): boolean {
|
/**
|
||||||
|
* Check if token is within 24-hour window from creation
|
||||||
|
* Refresh token is valid for 24 hours after initial login
|
||||||
|
*/
|
||||||
|
private isWithin24Hours(token: GeneralTokenDocument): boolean {
|
||||||
if (!token.createdAt) {
|
if (!token.createdAt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getDateKey(token.createdAt) === this.getDateKey(new Date());
|
const tokenCreationTime = token.createdAt.getTime();
|
||||||
}
|
const currentTime = Date.now();
|
||||||
|
const twentyFourHoursMs = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
private getDateKey(date: Date): string {
|
return currentTime - tokenCreationTime < twentyFourHoursMs;
|
||||||
return new Intl.DateTimeFormat('en-CA', {
|
|
||||||
timeZone: this.config.tokenTimeZone,
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
}).format(date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTokenKey(providerName: ProviderName, inquiryType: InquiryType): string {
|
private getTokenKey(providerName: ProviderName, inquiryType: InquiryType): string {
|
||||||
|
|||||||
@@ -4,15 +4,28 @@ import axios, { AxiosError } from 'axios';
|
|||||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||||
import { ProviderEnvConfig } from '../../config/configuration';
|
import { ProviderEnvConfig } from '../../config/configuration';
|
||||||
import { AmitisProvider } from './amitis.provider';
|
|
||||||
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
||||||
|
import { AmitisProvider } from './amitis.provider';
|
||||||
import {
|
import {
|
||||||
|
LegacyApiProvider,
|
||||||
LegacyInquiryPayload,
|
LegacyInquiryPayload,
|
||||||
LegacyInquiryResult,
|
LegacyInquiryResult,
|
||||||
LegacyApiProvider,
|
|
||||||
} from '../shared/legacy-api.provider.abstract';
|
} from '../shared/legacy-api.provider.abstract';
|
||||||
|
|
||||||
interface ShahkarInquiryPayload extends LegacyInquiryPayload {
|
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;
|
nationalCode?: string;
|
||||||
nationalCod?: string;
|
nationalCod?: string;
|
||||||
NationalCod?: string;
|
NationalCod?: string;
|
||||||
@@ -20,20 +33,24 @@ interface ShahkarInquiryPayload extends LegacyInquiryPayload {
|
|||||||
MobileNo?: string;
|
MobileNo?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SayahPayload extends LegacyInquiryPayload {
|
||||||
|
accountOwnerType?: string;
|
||||||
|
nationalId?: string;
|
||||||
|
legalId?: string | null;
|
||||||
|
shebaId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hamta provider — uses shared LegacyApiProvider base.
|
* Hamta provider — implements Hamta/CentInsur REST and SOAP inquiry services.
|
||||||
* Supports multiple inquiry types with different authentication methods.
|
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HamtaProvider extends LegacyApiProvider {
|
export class HamtaProvider extends LegacyApiProvider {
|
||||||
readonly name = ProviderName.HAMTA;
|
readonly name = ProviderName.HAMTA;
|
||||||
readonly supportedInquiryTypes = [
|
readonly supportedInquiryTypes = [
|
||||||
InquiryType.PERSON,
|
InquiryType.PERSON,
|
||||||
InquiryType.REAL_ESTATE,
|
|
||||||
InquiryType.SHEBA,
|
InquiryType.SHEBA,
|
||||||
InquiryType.SHAHKAR,
|
InquiryType.SHAHKAR,
|
||||||
InquiryType.POSTAL_CODE,
|
InquiryType.POSTAL_CODE,
|
||||||
InquiryType.LEGAL_PERSON,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly hamtaConfig: ProviderEnvConfig;
|
private readonly hamtaConfig: ProviderEnvConfig;
|
||||||
@@ -53,15 +70,113 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
payload: LegacyInquiryPayload,
|
payload: LegacyInquiryPayload,
|
||||||
context: ProviderExecutionContext,
|
context: ProviderExecutionContext,
|
||||||
): Promise<LegacyInquiryResult> {
|
): Promise<LegacyInquiryResult> {
|
||||||
// Shahkar uses SOAP authentication, handled separately
|
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) {
|
if (inquiryType === InquiryType.SHAHKAR) {
|
||||||
return this.inquireShahkar(payload as ShahkarInquiryPayload);
|
return this.inquireShahkar(payload as ShahkarPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inquiryType === InquiryType.SHEBA) {
|
||||||
|
return this.inquireSayah(payload as SayahPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.callProvider(inquiryType, payload, context);
|
return super.callProvider(inquiryType, payload, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async inquireShahkar(payload: ShahkarInquiryPayload): Promise<{ raw: unknown }> {
|
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(
|
const nationalCode = this.getRequiredString(
|
||||||
payload.nationalCode ?? payload.nationalCod ?? payload.NationalCod,
|
payload.nationalCode ?? payload.nationalCod ?? payload.NationalCod,
|
||||||
'nationalCode',
|
'nationalCode',
|
||||||
@@ -106,11 +221,86 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
String(error.response?.status ?? 'NETWORK_ERROR'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
||||||
|
const accountOwnerType = this.getRequiredString(payload.accountOwnerType, 'accountOwnerType');
|
||||||
|
const nationalId = payload.nationalId ?? '';
|
||||||
|
const legalId = payload.legalId ?? null;
|
||||||
|
const shebaId = this.getRequiredString(payload.shebaId, 'shebaId');
|
||||||
|
|
||||||
|
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(
|
private buildShahkarEnvelope(
|
||||||
nationalCode: string,
|
nationalCode: string,
|
||||||
mobileNo: string,
|
mobileNo: string,
|
||||||
@@ -141,10 +331,13 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
if (typeof value !== 'string' || !value.trim()) {
|
if (typeof value !== 'string' || !value.trim()) {
|
||||||
throw this.formatProviderError(undefined, undefined, `${fieldName} is required`);
|
throw this.formatProviderError(undefined, undefined, `${fieldName} is required`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.trim();
|
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 {
|
private escapeXml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -163,3 +356,5 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
.replace(/&/g, '&');
|
.replace(/&/g, '&');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Made with Bob
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export interface LegacyProviderApiResponse {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
TrackingCode?: string;
|
TrackingCode?: string;
|
||||||
|
// Sayah/Sheba API format
|
||||||
|
ReturnValue?: boolean;
|
||||||
|
HasError?: boolean;
|
||||||
|
Errors?: Record<string, string> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderConfigSlice = ProviderEnvConfig;
|
export type ProviderConfigSlice = ProviderEnvConfig;
|
||||||
|
|||||||
@@ -117,15 +117,33 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
|||||||
): Promise<{ raw: unknown }> {
|
): Promise<{ raw: unknown }> {
|
||||||
const inquiryConfig = this.getInquiryConfig(inquiryType);
|
const inquiryConfig = this.getInquiryConfig(inquiryType);
|
||||||
|
|
||||||
|
// Transform payload for Real Estate inquiry to match CentInsur API format
|
||||||
|
const requestPayload = this.transformPayloadForInquiryType(inquiryType, payload);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.httpClient.post<LegacyProviderApiResponse>(
|
const response = await this.httpClient.post<LegacyProviderApiResponse>(
|
||||||
inquiryConfig.url,
|
inquiryConfig.url,
|
||||||
payload,
|
requestPayload,
|
||||||
await this.buildRequestConfig(inquiryType, inquiryConfig),
|
await this.buildRequestConfig(inquiryType, inquiryConfig),
|
||||||
);
|
);
|
||||||
|
|
||||||
const body = response.data;
|
const body = response.data;
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { raw: body };
|
||||||
|
}
|
||||||
|
|
||||||
// Handle CentInsur API format (IsSucceed/Result)
|
// Handle CentInsur API format (IsSucceed/Result)
|
||||||
if ('IsSucceed' in body) {
|
if ('IsSucceed' in body) {
|
||||||
if (!body.IsSucceed) {
|
if (!body.IsSucceed) {
|
||||||
@@ -149,6 +167,17 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
|||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
const data = error.response?.data as LegacyProviderApiResponse | undefined;
|
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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for CentInsur format error
|
// Check for CentInsur format error
|
||||||
if (data && 'IsSucceed' in data && !data.IsSucceed) {
|
if (data && 'IsSucceed' in data && !data.IsSucceed) {
|
||||||
throw this.formatProviderError(
|
throw this.formatProviderError(
|
||||||
@@ -166,6 +195,25 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
protected getInquiryConfig(inquiryType: InquiryType): InquiryConfig {
|
protected getInquiryConfig(inquiryType: InquiryType): InquiryConfig {
|
||||||
const inquiryConfig = this.providerConfig.inquiries[inquiryType];
|
const inquiryConfig = this.providerConfig.inquiries[inquiryType];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user