forked from Shared/esg
Merge pull request 'main' (#6) from s.hajizadeh/esg:main into main
Reviewed-on: Shared/esg#6
This commit is contained in:
85
src/common/helpers/soap-car-policy.helper.ts
Normal file
85
src/common/helpers/soap-car-policy.helper.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
function decodeXml(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/&/g, '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSoapTextValue(value: string): string {
|
||||||
|
if (/i:nil\s*=\s*["']true["']/i.test(value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestedStrings = [
|
||||||
|
...value.matchAll(/<(?:[\w]+:)?string[^>]*>([\s\S]*?)<\/(?:[\w]+:)?string>/gi),
|
||||||
|
]
|
||||||
|
.map((match) => decodeXml(match[1].trim()))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (nestedStrings.length > 0) {
|
||||||
|
return nestedStrings.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeXml(value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSoapFields(block: string): Record<string, string> {
|
||||||
|
const fields: Record<string, string> = {};
|
||||||
|
const tagRegex = /<(?:[\w]+:)?(\w+)(?:[^>]*)>([\s\S]*?)<\/(?:[\w]+:)?\1>/gi;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = tagRegex.exec(block)) !== null) {
|
||||||
|
fields[match[1]] = normalizeSoapTextValue(match[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseFirstCarPolicy(soapXml: string): Record<string, string> {
|
||||||
|
const resultMatch = soapXml.match(
|
||||||
|
/<(?:[\w]+:)?(?:CIIWSPolicyChassis|CIIWSPolicyVehicleMeli|CIIWSPolicyNationalId)Result[^>]*>([\s\S]*?)<\/(?:[\w]+:)?(?:CIIWSPolicyChassis|CIIWSPolicyVehicleMeli|CIIWSPolicyNationalId)Result>/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resultMatch) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPolicyMatch = resultMatch[1].match(
|
||||||
|
/<(?:[\w]+:)?Policy(?:\s[^>]*)?>\s*<(?:[\w]+:)?Policy(?:\s[^>]*)?>([\s\S]*?)<\/(?:[\w]+:)?Policy>/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!firstPolicyMatch) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseSoapFields(firstPolicyMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCarPolicyProviderError(
|
||||||
|
soapXml: string,
|
||||||
|
policy: Record<string, string>,
|
||||||
|
): { message: string; code: string } | null {
|
||||||
|
const resultMatch = soapXml.match(
|
||||||
|
/<(?:[\w]+:)?(?:CIIWSPolicyChassis|CIIWSPolicyVehicleMeli|CIIWSPolicyNationalId)Result[^>]*>([\s\S]*?)<\/(?:[\w]+:)?(?:CIIWSPolicyChassis|CIIWSPolicyVehicleMeli|CIIWSPolicyNationalId)Result>/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultMatch) {
|
||||||
|
const errorMatch = resultMatch[1].match(
|
||||||
|
/<(?:[\w]+:)?Error(?![^>]*i:nil\s*=\s*["']true["'])[^>]*>([\s\S]*?)<\/(?:[\w]+:)?Error>/i,
|
||||||
|
);
|
||||||
|
if (errorMatch) {
|
||||||
|
const errorText = normalizeSoapTextValue(errorMatch[1]).trim();
|
||||||
|
if (errorText) {
|
||||||
|
return { message: errorText, code: 'PROVIDER_ERROR' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(policy).length === 0) {
|
||||||
|
return { message: 'No policy record found', code: 'RECORD_NOT_FOUND' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '../../common/helpers/http-client.helper';
|
} from '../../common/helpers/http-client.helper';
|
||||||
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 { NormalizedErrorDto } from '../../common/dto/normalized-error.dto';
|
||||||
import { AmitisAuthServiceConfig } from '../../config/configuration';
|
import { AmitisAuthServiceConfig } from '../../config/configuration';
|
||||||
import { GeneralTokenDocument } from '../schemas/general-token.schema';
|
import { GeneralTokenDocument } from '../schemas/general-token.schema';
|
||||||
import { GeneralTokenService } from '../services/general-token.service';
|
import { GeneralTokenService } from '../services/general-token.service';
|
||||||
@@ -201,10 +202,21 @@ export class AmitisProvider {
|
|||||||
fallbackRefreshToken?: string,
|
fallbackRefreshToken?: string,
|
||||||
): AmitisAuthToken {
|
): AmitisAuthToken {
|
||||||
const body = responseBody.data ?? responseBody;
|
const body = responseBody.data ?? responseBody;
|
||||||
|
const loginStatus = body.LoginStatus;
|
||||||
|
|
||||||
if (body.IsSucceed === false) {
|
if (loginStatus === 2 || body.IsSucceed === false) {
|
||||||
throw new Error(
|
throw this.createAuthError(
|
||||||
`AMITIS login rejected (LoginStatus=${body.LoginStatus ?? 'unknown'})`,
|
loginStatus === 2
|
||||||
|
? 'AMITIS username or password is invalid for this service account'
|
||||||
|
: `AMITIS login rejected (LoginStatus=${loginStatus ?? 'unknown'})`,
|
||||||
|
loginStatus === 2 ? 'AUTH_INVALID_CREDENTIALS' : 'AUTH_REJECTED',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginStatus !== undefined && loginStatus !== 1 && body.IsSucceed !== true) {
|
||||||
|
throw this.createAuthError(
|
||||||
|
`AMITIS login rejected (LoginStatus=${loginStatus})`,
|
||||||
|
'AUTH_REJECTED',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +267,23 @@ export class AmitisProvider {
|
|||||||
return `${ProviderName.AMITIS}:${providerName}:${inquiryType}`;
|
return `${ProviderName.AMITIS}:${providerName}:${inquiryType}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createAuthError(message: string, code: string): Error {
|
||||||
|
const normalized: NormalizedErrorDto = {
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
providerMessage: message,
|
||||||
|
providerCode: code,
|
||||||
|
};
|
||||||
|
const err = new Error(message);
|
||||||
|
(err as Error & { normalizedError: NormalizedErrorDto }).normalizedError = normalized;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
private toAuthError(message: string, error: unknown): Error {
|
private toAuthError(message: string, error: unknown): Error {
|
||||||
|
if (error instanceof Error && 'normalizedError' in error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
const status = error.response?.status ?? 'NETWORK_ERROR';
|
const status = error.response?.status ?? 'NETWORK_ERROR';
|
||||||
const body =
|
const body =
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||||
|
import {
|
||||||
|
buildCivilRegistrationFullName,
|
||||||
|
getCivilRegistrationProviderError,
|
||||||
|
parseCiiEstelamResult,
|
||||||
|
} from '../../common/helpers/soap-civil-registration.helper';
|
||||||
import {
|
import {
|
||||||
getSayahProviderError,
|
getSayahProviderError,
|
||||||
SayahApiResponse,
|
SayahApiResponse,
|
||||||
@@ -20,6 +25,7 @@ import {
|
|||||||
LegacyApiProvider,
|
LegacyApiProvider,
|
||||||
LegacyInquiryPayload,
|
LegacyInquiryPayload,
|
||||||
LegacyInquiryResult,
|
LegacyInquiryResult,
|
||||||
|
PersonInquiryResult,
|
||||||
} from '../shared/legacy-api.provider.abstract';
|
} from '../shared/legacy-api.provider.abstract';
|
||||||
|
|
||||||
interface CivilRegistrationPayload extends LegacyInquiryPayload {
|
interface CivilRegistrationPayload extends LegacyInquiryPayload {
|
||||||
@@ -107,6 +113,7 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const inquiryConfig = this.getInquiryConfig(InquiryType.POSTAL_CODE);
|
const inquiryConfig = this.getInquiryConfig(InquiryType.POSTAL_CODE);
|
||||||
|
this.assertAmitisCredentials(InquiryType.POSTAL_CODE, inquiryConfig);
|
||||||
const token = await this.amitisProvider.getAccessToken(
|
const token = await this.amitisProvider.getAccessToken(
|
||||||
ProviderName.HAMTA,
|
ProviderName.HAMTA,
|
||||||
InquiryType.POSTAL_CODE,
|
InquiryType.POSTAL_CODE,
|
||||||
@@ -138,7 +145,7 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
|
|
||||||
private async inquireCivilRegistration(
|
private async inquireCivilRegistration(
|
||||||
payload: CivilRegistrationPayload,
|
payload: CivilRegistrationPayload,
|
||||||
): Promise<{ raw: unknown }> {
|
): Promise<PersonInquiryResult> {
|
||||||
const nationalCode = this.getRequiredString(
|
const nationalCode = this.getRequiredString(
|
||||||
payload.nationalCode ?? payload.NIN,
|
payload.nationalCode ?? payload.NIN,
|
||||||
'nationalCode',
|
'nationalCode',
|
||||||
@@ -151,6 +158,7 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
const response = await axios.post<string>(
|
const response = await axios.post<string>(
|
||||||
inquiryConfig.url,
|
inquiryConfig.url,
|
||||||
this.buildCivilRegistrationEnvelope(
|
this.buildCivilRegistrationEnvelope(
|
||||||
|
payload,
|
||||||
nationalCode,
|
nationalCode,
|
||||||
birthDate,
|
birthDate,
|
||||||
inquiryConfig.username,
|
inquiryConfig.username,
|
||||||
@@ -166,22 +174,23 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = this.extractSoapValue(response.data, 'SubmitInqDteStsWithPstCodResult');
|
const civilRegistration = parseCiiEstelamResult(response.data);
|
||||||
|
const providerError = getCivilRegistrationProviderError(civilRegistration);
|
||||||
|
if (providerError) {
|
||||||
|
throw this.formatProviderError(providerError.message, providerError.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullName = buildCivilRegistrationFullName(civilRegistration);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: {
|
|
||||||
nationalCode,
|
nationalCode,
|
||||||
birthDate,
|
birthDate,
|
||||||
result,
|
fullName: fullName || undefined,
|
||||||
soap: response.data,
|
raw: civilRegistration,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
throw this.formatProviderError(
|
throw this.formatAxiosError(error);
|
||||||
error.message,
|
|
||||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -251,6 +260,7 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
||||||
|
|
||||||
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
||||||
|
this.assertAmitisCredentials(InquiryType.SHEBA, inquiryConfig);
|
||||||
const token = await this.amitisProvider.getAccessToken(
|
const token = await this.amitisProvider.getAccessToken(
|
||||||
ProviderName.HAMTA,
|
ProviderName.HAMTA,
|
||||||
InquiryType.SHEBA,
|
InquiryType.SHEBA,
|
||||||
@@ -294,19 +304,35 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private buildCivilRegistrationEnvelope(
|
private buildCivilRegistrationEnvelope(
|
||||||
|
payload: CivilRegistrationPayload,
|
||||||
nationalCode: string,
|
nationalCode: string,
|
||||||
birthDate: string,
|
birthDate: string,
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
): string {
|
): string {
|
||||||
|
const birthDateCompact = birthDate.replace(/-/g, '');
|
||||||
|
const dateHasPostfix = payload.dateHasPostfix ?? 0;
|
||||||
|
|
||||||
return `<?xml version="1.0" encoding="utf-8"?>
|
return `<?xml version="1.0" encoding="utf-8"?>
|
||||||
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
<soap:Body>
|
<soap:Body>
|
||||||
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
|
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
|
||||||
<NIN>${this.escapeXml(nationalCode)}</NIN>
|
<req>
|
||||||
<BirthDate>${this.escapeXml(birthDate)}</BirthDate>
|
<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>
|
<Username>${this.escapeXml(username)}</Username>
|
||||||
<Password>${this.escapeXml(password)}</Password>
|
<Password>${this.escapeXml(password)}</Password>
|
||||||
</SubmitInqDteStsWithPstCod>
|
</SubmitInqDteStsWithPstCod>
|
||||||
@@ -335,9 +361,17 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
</soap:Envelope>`;
|
</soap:Envelope>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractSoapValue(xml: string, tagName: string): string | null {
|
private assertAmitisCredentials(
|
||||||
const match = xml.match(new RegExp(`<(?:\\w+:)?${tagName}[^>]*>([\\s\\S]*?)</(?:\\w+:)?${tagName}>`));
|
inquiryType: InquiryType,
|
||||||
return match ? this.decodeXml(match[1].trim()) : null;
|
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 {
|
private getRequiredString(value: unknown, fieldName: string): string {
|
||||||
@@ -359,15 +393,6 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
.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
|
// Made with Bob
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||||
import { getSayahProviderError, SayahApiResponse } from '../../common/helpers/sayah-response.helper';
|
|
||||||
import {
|
import {
|
||||||
describeShahkarResponse,
|
buildCivilRegistrationFullName,
|
||||||
getShahkarProviderError,
|
getCivilRegistrationProviderError,
|
||||||
normalizeShahkarFields,
|
parseCiiEstelamResult,
|
||||||
} from '../../common/helpers/shahkar-response.helper';
|
} from '../../common/helpers/soap-civil-registration.helper';
|
||||||
|
import {
|
||||||
|
getCarPolicyProviderError,
|
||||||
|
parseFirstCarPolicy,
|
||||||
|
} from '../../common/helpers/soap-car-policy.helper';
|
||||||
|
import { getSayahProviderError, SayahApiResponse } from '../../common/helpers/sayah-response.helper';
|
||||||
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 { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
||||||
@@ -25,6 +29,7 @@ interface ParsianCivilRegistrationPayload extends LegacyInquiryPayload {
|
|||||||
birthDate?: string;
|
birthDate?: string;
|
||||||
NIN?: string;
|
NIN?: string;
|
||||||
BirthDate?: string;
|
BirthDate?: string;
|
||||||
|
dateHasPostfix?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParsianShahkarPayload extends LegacyInquiryPayload {
|
interface ParsianShahkarPayload extends LegacyInquiryPayload {
|
||||||
@@ -43,7 +48,7 @@ interface ParsianShahkarResponse {
|
|||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
requestId?: string | null;
|
requestId?: string | null;
|
||||||
response?: number;
|
response?: number | string;
|
||||||
result?: string | null;
|
result?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +192,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
mobileNumber,
|
mobileNumber,
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
|
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
|
||||||
},
|
},
|
||||||
timeout: this.config.timeout,
|
timeout: this.config.timeout,
|
||||||
@@ -194,22 +200,24 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
);
|
);
|
||||||
|
|
||||||
const body = response.data;
|
const body = response.data;
|
||||||
const shahkarFields = normalizeShahkarFields(body as unknown as Record<string, unknown>);
|
const responseCode = Number(body.response);
|
||||||
const providerError = getShahkarProviderError(shahkarFields);
|
|
||||||
if (providerError) {
|
if (!Number.isFinite(responseCode) || responseCode !== 200) {
|
||||||
this.nestLogger.warn(
|
throw this.formatProviderError(
|
||||||
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
|
body.comment ?? body.errorNams ?? body.ErrorNams ?? 'Shahkar inquiry failed',
|
||||||
|
String(body.response ?? 'PROVIDER_ERROR'),
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
providerTrackingCode: body.requestId ?? body.id ?? undefined,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
|
||||||
providerTrackingCode: providerError.providerTrackingCode,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: {
|
raw: {
|
||||||
nationalCode,
|
nationalCode,
|
||||||
mobileNumber,
|
mobileNumber,
|
||||||
...shahkarFields,
|
...body,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -246,6 +254,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
const response = await axios.post<string>(
|
const response = await axios.post<string>(
|
||||||
inquiryConfig.url,
|
inquiryConfig.url,
|
||||||
this.buildCivilRegistrationEnvelope(
|
this.buildCivilRegistrationEnvelope(
|
||||||
|
payload,
|
||||||
nationalCode,
|
nationalCode,
|
||||||
birthDate,
|
birthDate,
|
||||||
inquiryConfig.username,
|
inquiryConfig.username,
|
||||||
@@ -261,15 +270,19 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const civilRegistration = parseCiiEstelamResult(response.data);
|
||||||
|
const providerError = getCivilRegistrationProviderError(civilRegistration);
|
||||||
|
if (providerError) {
|
||||||
|
throw this.formatProviderError(providerError.message, providerError.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullName = buildCivilRegistrationFullName(civilRegistration);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nationalCode,
|
nationalCode,
|
||||||
birthDate,
|
birthDate,
|
||||||
raw: {
|
fullName: fullName || undefined,
|
||||||
nationalCode,
|
raw: civilRegistration,
|
||||||
birthDate,
|
|
||||||
result: this.extractSoapValue(response.data, 'SubmitInqDteStsWithPstCodResult'),
|
|
||||||
soap: response.data,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
@@ -286,16 +299,16 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
payload: ParsianPolicyByChassisPayload,
|
payload: ParsianPolicyByChassisPayload,
|
||||||
): Promise<{ raw: unknown }> {
|
): Promise<{ raw: unknown }> {
|
||||||
const chassisNo = this.getRequiredString(payload.chassisNo, 'chassisNo');
|
const chassisNo = this.getRequiredString(payload.chassisNo, 'chassisNo');
|
||||||
const response = await this.callCarPolicySoap(
|
const policy = await this.callCarPolicySoap(
|
||||||
InquiryType.POLICY_BY_CHASSIS,
|
InquiryType.POLICY_BY_CHASSIS,
|
||||||
'CIIWSPolicyChassis',
|
'CIIWSPolicyChassis',
|
||||||
{ Chassisno: chassisNo },
|
{ ChassisNo: chassisNo },
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: {
|
raw: {
|
||||||
chassisNo,
|
chassisNo,
|
||||||
...response,
|
...policy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -304,16 +317,16 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
payload: ParsianPolicyByNationalCodePayload,
|
payload: ParsianPolicyByNationalCodePayload,
|
||||||
): Promise<{ raw: unknown }> {
|
): Promise<{ raw: unknown }> {
|
||||||
const nationalCode = this.getRequiredString(payload.nationalCode, 'nationalCode');
|
const nationalCode = this.getRequiredString(payload.nationalCode, 'nationalCode');
|
||||||
const response = await this.callCarPolicySoap(
|
const policy = await this.callCarPolicySoap(
|
||||||
InquiryType.POLICY_BY_NATIONAL_CODE,
|
InquiryType.POLICY_BY_NATIONAL_CODE,
|
||||||
'CIIWSPolicyNationalId',
|
'CIIWSPolicyNationalId',
|
||||||
{ nationalId: nationalCode },
|
{ NationalId: nationalCode },
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: {
|
raw: {
|
||||||
nationalCode,
|
nationalCode,
|
||||||
...response,
|
...policy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -325,11 +338,10 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
const plk2 = this.getRequiredString(payload.plk2, 'plk2');
|
const plk2 = this.getRequiredString(payload.plk2, 'plk2');
|
||||||
const plk3 = this.getRequiredString(payload.plk3, 'plk3');
|
const plk3 = this.getRequiredString(payload.plk3, 'plk3');
|
||||||
const plksrl = this.getRequiredString(payload.plksrl, 'plksrl');
|
const plksrl = this.getRequiredString(payload.plksrl, 'plksrl');
|
||||||
const response = await this.callCarPolicySoap(
|
const policy = await this.callCarPolicySoap(
|
||||||
InquiryType.POLICY_BY_PLATE,
|
InquiryType.POLICY_BY_PLATE,
|
||||||
'CIIWSPolicyVehicleMeli',
|
'CIIWSPolicyVehicleMeli',
|
||||||
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, Plksrl: plksrl },
|
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, PlkSrl: plksrl },
|
||||||
'PassWord',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -338,7 +350,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
plk2,
|
plk2,
|
||||||
plk3,
|
plk3,
|
||||||
plksrl,
|
plksrl,
|
||||||
...response,
|
...policy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -347,8 +359,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
inquiryType: InquiryType,
|
inquiryType: InquiryType,
|
||||||
methodName: string,
|
methodName: string,
|
||||||
fields: Record<string, string>,
|
fields: Record<string, string>,
|
||||||
passwordFieldName = 'Password',
|
): Promise<Record<string, string>> {
|
||||||
): Promise<{ result: string | null; soap: string }> {
|
|
||||||
const inquiryConfig = this.config.inquiries[inquiryType];
|
const inquiryConfig = this.config.inquiries[inquiryType];
|
||||||
|
|
||||||
if (!this.hasSoapCredentials(inquiryConfig)) {
|
if (!this.hasSoapCredentials(inquiryConfig)) {
|
||||||
@@ -367,22 +378,24 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
fields,
|
fields,
|
||||||
inquiryConfig.username,
|
inquiryConfig.username,
|
||||||
inquiryConfig.password,
|
inquiryConfig.password,
|
||||||
passwordFieldName,
|
|
||||||
),
|
),
|
||||||
mergeOutboundAxiosConfig({
|
mergeOutboundAxiosConfig({
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/xml; charset=utf-8',
|
'Content-Type': 'text/xml; charset=utf-8',
|
||||||
SOAPAction: `"http://tempuri.org/ICarAllPlcysV4/${methodName}"`,
|
SOAPAction: `"http://tempuri.org/ICarAllPlcys/${methodName}"`,
|
||||||
},
|
},
|
||||||
timeout: this.config.timeout,
|
timeout: this.config.timeout,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
const policy = parseFirstCarPolicy(response.data);
|
||||||
result: this.extractSoapValue(response.data, `${methodName}Result`),
|
const providerError = getCarPolicyProviderError(response.data, policy);
|
||||||
soap: response.data,
|
if (providerError) {
|
||||||
};
|
throw this.formatProviderError(providerError.message, providerError.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return policy;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
throw this.formatProviderError(
|
throw this.formatProviderError(
|
||||||
@@ -470,7 +483,6 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
fields: Record<string, string>,
|
fields: Record<string, string>,
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
passwordFieldName: string,
|
|
||||||
): string {
|
): string {
|
||||||
const fieldXml = Object.entries(fields)
|
const fieldXml = Object.entries(fields)
|
||||||
.map(([name, value]) => ` <${name}>${this.escapeXml(value)}</${name}>`)
|
.map(([name, value]) => ` <${name}>${this.escapeXml(value)}</${name}>`)
|
||||||
@@ -483,27 +495,47 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
|||||||
<soap:Body>
|
<soap:Body>
|
||||||
<${methodName} xmlns="http://tempuri.org/">
|
<${methodName} xmlns="http://tempuri.org/">
|
||||||
${fieldXml}
|
${fieldXml}
|
||||||
<UserName>${this.escapeXml(username)}</UserName>
|
<Username>${this.escapeXml(username)}</Username>
|
||||||
<${passwordFieldName}>${this.escapeXml(password)}</${passwordFieldName}>
|
<PassWrod>${this.escapeXml(password)}</PassWrod>
|
||||||
</${methodName}>
|
</${methodName}>
|
||||||
</soap:Body>
|
</soap:Body>
|
||||||
</soap:Envelope>`;
|
</soap:Envelope>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCivilRegistrationEnvelope(
|
private buildCivilRegistrationEnvelope(
|
||||||
|
payload: ParsianCivilRegistrationPayload,
|
||||||
nationalCode: string,
|
nationalCode: string,
|
||||||
birthDate: string,
|
birthDate: string,
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
): string {
|
): string {
|
||||||
|
const birthDateCompact = birthDate.replace(/-/g, '');
|
||||||
|
const dateHasPostfix = payload.dateHasPostfix ?? 0;
|
||||||
|
|
||||||
return `<?xml version="1.0" encoding="utf-8"?>
|
return `<?xml version="1.0" encoding="utf-8"?>
|
||||||
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
<soap:Body>
|
<soap:Body>
|
||||||
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
|
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
|
||||||
<NIN>${this.escapeXml(nationalCode)}</NIN>
|
<req>
|
||||||
<BirthDate>${this.escapeXml(birthDate)}</BirthDate>
|
<Nin>${this.escapeXml(nationalCode)}</Nin>
|
||||||
|
<Name xsi:nil="true"/>
|
||||||
|
<Family xsi:nil="true"/>
|
||||||
|
<Fathername xsi:nil="true"/>
|
||||||
|
<Shenasnameseri xsi:nil="true"/>
|
||||||
|
<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>
|
<Username>${this.escapeXml(username)}</Username>
|
||||||
<Password>${this.escapeXml(password)}</Password>
|
<Password>${this.escapeXml(password)}</Password>
|
||||||
</SubmitInqDteStsWithPstCod>
|
</SubmitInqDteStsWithPstCod>
|
||||||
@@ -511,13 +543,6 @@ ${fieldXml}
|
|||||||
</soap:Envelope>`;
|
</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 escapeXml(value: string): string {
|
private escapeXml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -527,15 +552,6 @@ ${fieldXml}
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeXml(value: string): string {
|
|
||||||
return value
|
|
||||||
.replace(/'/g, "'")
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/&/g, '&');
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasErrors(
|
private hasErrors(
|
||||||
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
|
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|||||||
@@ -216,7 +216,6 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
|||||||
return {
|
return {
|
||||||
NationalId: payload.nationalCode ?? payload.nationalId ?? payload.NationalId,
|
NationalId: payload.nationalCode ?? payload.nationalId ?? payload.NationalId,
|
||||||
PostalCode: payload.postalCode ?? payload.PostalCode,
|
PostalCode: payload.postalCode ?? payload.PostalCode,
|
||||||
...payload,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user