parsian done

This commit is contained in:
2026-06-15 17:13:21 +03:30
parent 1b7f678538
commit 873fb1a1e2
5 changed files with 243 additions and 90 deletions

View File

@@ -2,12 +2,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 { getSayahProviderError, SayahApiResponse } from '../../common/helpers/sayah-response.helper';
import {
describeShahkarResponse,
getShahkarProviderError,
normalizeShahkarFields,
} from '../../common/helpers/shahkar-response.helper';
buildCivilRegistrationFullName,
getCivilRegistrationProviderError,
parseCiiEstelamResult,
} 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 { ProviderName } from '../../common/enums/provider-name.enum';
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
@@ -25,6 +29,7 @@ interface ParsianCivilRegistrationPayload extends LegacyInquiryPayload {
birthDate?: string;
NIN?: string;
BirthDate?: string;
dateHasPostfix?: number;
}
interface ParsianShahkarPayload extends LegacyInquiryPayload {
@@ -43,7 +48,7 @@ interface ParsianShahkarResponse {
comment?: string | null;
id?: string | null;
requestId?: string | null;
response?: number;
response?: number | string;
result?: string | null;
}
@@ -187,6 +192,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
mobileNumber,
},
headers: {
Accept: 'application/json',
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
},
timeout: this.config.timeout,
@@ -194,22 +200,24 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
);
const body = response.data;
const shahkarFields = normalizeShahkarFields(body as unknown as Record<string, unknown>);
const providerError = getShahkarProviderError(shahkarFields);
if (providerError) {
this.nestLogger.warn(
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
const responseCode = Number(body.response);
if (!Number.isFinite(responseCode) || responseCode !== 200) {
throw this.formatProviderError(
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 {
raw: {
nationalCode,
mobileNumber,
...shahkarFields,
...body,
},
};
} catch (error) {
@@ -246,6 +254,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
const response = await axios.post<string>(
inquiryConfig.url,
this.buildCivilRegistrationEnvelope(
payload,
nationalCode,
birthDate,
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 {
nationalCode,
birthDate,
raw: {
nationalCode,
birthDate,
result: this.extractSoapValue(response.data, 'SubmitInqDteStsWithPstCodResult'),
soap: response.data,
},
fullName: fullName || undefined,
raw: civilRegistration,
};
} catch (error) {
if (error instanceof AxiosError) {
@@ -286,16 +299,16 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
payload: ParsianPolicyByChassisPayload,
): Promise<{ raw: unknown }> {
const chassisNo = this.getRequiredString(payload.chassisNo, 'chassisNo');
const response = await this.callCarPolicySoap(
const policy = await this.callCarPolicySoap(
InquiryType.POLICY_BY_CHASSIS,
'CIIWSPolicyChassis',
{ Chassisno: chassisNo },
{ ChassisNo: chassisNo },
);
return {
raw: {
chassisNo,
...response,
...policy,
},
};
}
@@ -304,16 +317,16 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
payload: ParsianPolicyByNationalCodePayload,
): Promise<{ raw: unknown }> {
const nationalCode = this.getRequiredString(payload.nationalCode, 'nationalCode');
const response = await this.callCarPolicySoap(
const policy = await this.callCarPolicySoap(
InquiryType.POLICY_BY_NATIONAL_CODE,
'CIIWSPolicyNationalId',
{ nationalId: nationalCode },
{ NationalId: nationalCode },
);
return {
raw: {
nationalCode,
...response,
...policy,
},
};
}
@@ -325,11 +338,10 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
const plk2 = this.getRequiredString(payload.plk2, 'plk2');
const plk3 = this.getRequiredString(payload.plk3, 'plk3');
const plksrl = this.getRequiredString(payload.plksrl, 'plksrl');
const response = await this.callCarPolicySoap(
const policy = await this.callCarPolicySoap(
InquiryType.POLICY_BY_PLATE,
'CIIWSPolicyVehicleMeli',
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, Plksrl: plksrl },
'PassWord',
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, PlkSrl: plksrl },
);
return {
@@ -338,7 +350,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
plk2,
plk3,
plksrl,
...response,
...policy,
},
};
}
@@ -347,8 +359,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
inquiryType: InquiryType,
methodName: string,
fields: Record<string, string>,
passwordFieldName = 'Password',
): Promise<{ result: string | null; soap: string }> {
): Promise<Record<string, string>> {
const inquiryConfig = this.config.inquiries[inquiryType];
if (!this.hasSoapCredentials(inquiryConfig)) {
@@ -367,22 +378,24 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
fields,
inquiryConfig.username,
inquiryConfig.password,
passwordFieldName,
),
mergeOutboundAxiosConfig({
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: `"http://tempuri.org/ICarAllPlcysV4/${methodName}"`,
SOAPAction: `"http://tempuri.org/ICarAllPlcys/${methodName}"`,
},
timeout: this.config.timeout,
responseType: 'text',
}),
);
return {
result: this.extractSoapValue(response.data, `${methodName}Result`),
soap: response.data,
};
const policy = parseFirstCarPolicy(response.data);
const providerError = getCarPolicyProviderError(response.data, policy);
if (providerError) {
throw this.formatProviderError(providerError.message, providerError.code);
}
return policy;
} catch (error) {
if (error instanceof AxiosError) {
throw this.formatProviderError(
@@ -470,7 +483,6 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
fields: Record<string, string>,
username: string,
password: string,
passwordFieldName: string,
): string {
const fieldXml = Object.entries(fields)
.map(([name, value]) => ` <${name}>${this.escapeXml(value)}</${name}>`)
@@ -483,27 +495,47 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
<soap:Body>
<${methodName} xmlns="http://tempuri.org/">
${fieldXml}
<UserName>${this.escapeXml(username)}</UserName>
<${passwordFieldName}>${this.escapeXml(password)}</${passwordFieldName}>
<Username>${this.escapeXml(username)}</Username>
<PassWrod>${this.escapeXml(password)}</PassWrod>
</${methodName}>
</soap:Body>
</soap:Envelope>`;
}
private buildCivilRegistrationEnvelope(
payload: ParsianCivilRegistrationPayload,
nationalCode: string,
birthDate: string,
username: string,
password: string,
): string {
const birthDateCompact = birthDate.replace(/-/g, '');
const dateHasPostfix = payload.dateHasPostfix ?? 0;
return `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SubmitInqDteStsWithPstCod xmlns="http://tempuri.org/">
<NIN>${this.escapeXml(nationalCode)}</NIN>
<BirthDate>${this.escapeXml(birthDate)}</BirthDate>
<req>
<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>
<Password>${this.escapeXml(password)}</Password>
</SubmitInqDteStsWithPstCod>
@@ -511,13 +543,6 @@ ${fieldXml}
</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 {
return value
.replace(/&/g, '&amp;')
@@ -527,15 +552,6 @@ ${fieldXml}
.replace(/'/g, '&apos;');
}
private decodeXml(value: string): string {
return value
.replace(/&apos;/g, "'")
.replace(/&quot;/g, '"')
.replace(/&gt;/g, '>')
.replace(/&lt;/g, '<')
.replace(/&amp;/g, '&');
}
private hasErrors(
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
): boolean {