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

@@ -0,0 +1,85 @@
function decodeXml(value: string): string {
return value
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/>/g, '>')
.replace(/&lt;/g, '<')
.replace(/&amp;/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;
}

View File

@@ -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 =

View File

@@ -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, fullName: fullName || undefined,
result, raw: civilRegistration,
soap: response.data,
},
}; };
} 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, '&quot;') .replace(/"/g, '&quot;')
.replace(/'/g, '&apos;'); .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, '&');
}
} }
// Made with Bob // Made with Bob

View File

@@ -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, '&amp;') .replace(/&/g, '&amp;')
@@ -527,15 +552,6 @@ ${fieldXml}
.replace(/'/g, '&apos;'); .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( private hasErrors(
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null, errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
): boolean { ): boolean {

View File

@@ -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,
}; };
} }