forked from Shared/esg
parsian inquiries implemented
This commit is contained in:
449
src/providers/implementations/parsian.provider.ts
Normal file
449
src/providers/implementations/parsian.provider.ts
Normal file
@@ -0,0 +1,449 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
|
||||
import { ProviderEnvConfig } from '../../config/configuration';
|
||||
import { BaseProvider } from '../base/base-provider.abstract';
|
||||
import { AmitisProvider } from './amitis.provider';
|
||||
import {
|
||||
LegacyInquiryPayload,
|
||||
LegacyInquiryResult,
|
||||
} from '../shared/legacy-api.provider.abstract';
|
||||
|
||||
interface ParsianShahkarPayload extends LegacyInquiryPayload {
|
||||
nationalCode?: string;
|
||||
nationalCod?: string;
|
||||
NationalCod?: string;
|
||||
mobileNo?: string;
|
||||
MobileNo?: string;
|
||||
mobileNumber?: string;
|
||||
MobileNumber?: string;
|
||||
}
|
||||
|
||||
interface ParsianShahkarResponse {
|
||||
errorNams?: string | null;
|
||||
ErrorNams?: string | null;
|
||||
comment?: string | null;
|
||||
id?: string | null;
|
||||
requestId?: string | null;
|
||||
response?: number;
|
||||
result?: string | null;
|
||||
}
|
||||
|
||||
interface ParsianSayahPayload extends LegacyInquiryPayload {
|
||||
accountOwnerType?: string;
|
||||
nationalCode?: string;
|
||||
legalId?: string | null;
|
||||
sheba?: string;
|
||||
}
|
||||
|
||||
interface ParsianSayahResponse {
|
||||
ReturnValue?: boolean;
|
||||
returnValue?: boolean;
|
||||
HasError?: boolean;
|
||||
hasError?: boolean;
|
||||
Errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null;
|
||||
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null;
|
||||
IsSucceed?: boolean;
|
||||
isSucceed?: boolean;
|
||||
Result?: unknown;
|
||||
result?: unknown;
|
||||
}
|
||||
|
||||
interface ParsianPolicyByChassisPayload extends LegacyInquiryPayload {
|
||||
chassisNo?: string;
|
||||
}
|
||||
|
||||
interface ParsianPolicyByNationalCodePayload extends LegacyInquiryPayload {
|
||||
nationalCode?: string;
|
||||
}
|
||||
|
||||
interface ParsianPolicyByPlatePayload extends LegacyInquiryPayload {
|
||||
plk1?: string;
|
||||
plk2?: string;
|
||||
plk3?: string;
|
||||
plksrl?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyInquiryResult> {
|
||||
readonly name = ProviderName.PARSIAN;
|
||||
readonly supportedInquiryTypes = [
|
||||
InquiryType.SHAHKAR,
|
||||
InquiryType.SHEBA,
|
||||
InquiryType.POLICY_BY_CHASSIS,
|
||||
InquiryType.POLICY_BY_PLATE,
|
||||
InquiryType.POLICY_BY_NATIONAL_CODE,
|
||||
];
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private readonly amitisProvider: AmitisProvider,
|
||||
) {
|
||||
super(configService.get<ProviderEnvConfig>('parsian')!);
|
||||
}
|
||||
|
||||
isEnabled(): boolean {
|
||||
const shahkarConfig = this.config.inquiries[InquiryType.SHAHKAR];
|
||||
const shebaConfig = this.config.inquiries[InquiryType.SHEBA];
|
||||
const policyByChassisConfig = this.config.inquiries[InquiryType.POLICY_BY_CHASSIS];
|
||||
const policyByPlateConfig = this.config.inquiries[InquiryType.POLICY_BY_PLATE];
|
||||
const policyByNationalCodeConfig =
|
||||
this.config.inquiries[InquiryType.POLICY_BY_NATIONAL_CODE];
|
||||
|
||||
return (
|
||||
this.config.enabled &&
|
||||
((Boolean(shahkarConfig?.url) && Boolean(shahkarConfig?.apiKey)) ||
|
||||
(Boolean(shebaConfig?.url) &&
|
||||
Boolean(shebaConfig?.username) &&
|
||||
Boolean(shebaConfig?.password)) ||
|
||||
this.hasSoapCredentials(policyByChassisConfig) ||
|
||||
this.hasSoapCredentials(policyByPlateConfig) ||
|
||||
this.hasSoapCredentials(policyByNationalCodeConfig))
|
||||
);
|
||||
}
|
||||
|
||||
protected async callProvider(
|
||||
inquiryType: InquiryType,
|
||||
payload: LegacyInquiryPayload,
|
||||
_context: ProviderExecutionContext,
|
||||
): Promise<LegacyInquiryResult> {
|
||||
if (inquiryType === InquiryType.SHAHKAR) {
|
||||
return this.inquireShahkar(payload as ParsianShahkarPayload);
|
||||
}
|
||||
|
||||
if (inquiryType === InquiryType.SHEBA) {
|
||||
return this.inquireSayah(payload as ParsianSayahPayload);
|
||||
}
|
||||
|
||||
if (inquiryType === InquiryType.POLICY_BY_CHASSIS) {
|
||||
return this.inquirePolicyByChassis(payload as ParsianPolicyByChassisPayload);
|
||||
}
|
||||
|
||||
if (inquiryType === InquiryType.POLICY_BY_PLATE) {
|
||||
return this.inquirePolicyByPlate(payload as ParsianPolicyByPlatePayload);
|
||||
}
|
||||
|
||||
if (inquiryType === InquiryType.POLICY_BY_NATIONAL_CODE) {
|
||||
return this.inquirePolicyByNationalCode(payload as ParsianPolicyByNationalCodePayload);
|
||||
}
|
||||
|
||||
throw this.formatProviderError(
|
||||
undefined,
|
||||
'UNSUPPORTED_INQUIRY',
|
||||
`Parsian does not support ${inquiryType}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async inquireShahkar(payload: ParsianShahkarPayload): Promise<{ raw: unknown }> {
|
||||
const nationalCode = this.getRequiredString(
|
||||
payload.nationalCode ?? payload.nationalCod ?? payload.NationalCod,
|
||||
'nationalCode',
|
||||
);
|
||||
const mobileNumber = this.getRequiredString(
|
||||
payload.mobileNumber ?? payload.MobileNumber ?? payload.mobileNo ?? payload.MobileNo,
|
||||
'mobileNo',
|
||||
);
|
||||
const inquiryConfig = this.config.inquiries[InquiryType.SHAHKAR];
|
||||
|
||||
if (!inquiryConfig?.url || !inquiryConfig.apiKey) {
|
||||
throw this.formatProviderError(
|
||||
undefined,
|
||||
undefined,
|
||||
'Parsian Shahkar inquiry is not configured',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get<ParsianShahkarResponse>(inquiryConfig.url, {
|
||||
params: {
|
||||
nationalCode,
|
||||
mobileNumber,
|
||||
},
|
||||
headers: {
|
||||
'X-PACKAGE-API-KEY': inquiryConfig.apiKey,
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
});
|
||||
|
||||
const body = response.data;
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
mobileNumber,
|
||||
...body,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
const data = error.response?.data as ParsianShahkarResponse | undefined;
|
||||
throw this.formatProviderError(
|
||||
data?.comment ?? data?.errorNams ?? data?.ErrorNams ?? error.message,
|
||||
String(data?.response ?? error.response?.status ?? 'NETWORK_ERROR'),
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async inquirePolicyByChassis(
|
||||
payload: ParsianPolicyByChassisPayload,
|
||||
): Promise<{ raw: unknown }> {
|
||||
const chassisNo = this.getRequiredString(payload.chassisNo, 'chassisNo');
|
||||
const response = await this.callCarPolicySoap(
|
||||
InquiryType.POLICY_BY_CHASSIS,
|
||||
'CIIWSPolicyChassis',
|
||||
{ Chassisno: chassisNo },
|
||||
);
|
||||
|
||||
return {
|
||||
raw: {
|
||||
chassisNo,
|
||||
...response,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async inquirePolicyByNationalCode(
|
||||
payload: ParsianPolicyByNationalCodePayload,
|
||||
): Promise<{ raw: unknown }> {
|
||||
const nationalCode = this.getRequiredString(payload.nationalCode, 'nationalCode');
|
||||
const response = await this.callCarPolicySoap(
|
||||
InquiryType.POLICY_BY_NATIONAL_CODE,
|
||||
'CIIWSPolicyNationalId',
|
||||
{ nationalId: nationalCode },
|
||||
);
|
||||
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
...response,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async inquirePolicyByPlate(
|
||||
payload: ParsianPolicyByPlatePayload,
|
||||
): Promise<{ raw: unknown }> {
|
||||
const plk1 = this.getRequiredString(payload.plk1, 'plk1');
|
||||
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(
|
||||
InquiryType.POLICY_BY_PLATE,
|
||||
'CIIWSPolicyVehicleMeli',
|
||||
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, Plksrl: plksrl },
|
||||
'PassWord',
|
||||
);
|
||||
|
||||
return {
|
||||
raw: {
|
||||
plk1,
|
||||
plk2,
|
||||
plk3,
|
||||
plksrl,
|
||||
...response,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async callCarPolicySoap(
|
||||
inquiryType: InquiryType,
|
||||
methodName: string,
|
||||
fields: Record<string, string>,
|
||||
passwordFieldName = 'Password',
|
||||
): Promise<{ result: string | null; soap: string }> {
|
||||
const inquiryConfig = this.config.inquiries[inquiryType];
|
||||
|
||||
if (!this.hasSoapCredentials(inquiryConfig)) {
|
||||
throw this.formatProviderError(
|
||||
undefined,
|
||||
undefined,
|
||||
`Parsian ${inquiryType} is not configured`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post<string>(
|
||||
inquiryConfig.url,
|
||||
this.buildCarPolicyEnvelope(
|
||||
methodName,
|
||||
fields,
|
||||
inquiryConfig.username,
|
||||
inquiryConfig.password,
|
||||
passwordFieldName,
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
SOAPAction: `"http://tempuri.org/ICarAllPlcysV4/${methodName}"`,
|
||||
},
|
||||
timeout: this.config.timeout,
|
||||
responseType: 'text',
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
result: this.extractSoapValue(response.data, `${methodName}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 inquireSayah(payload: ParsianSayahPayload): Promise<{ raw: unknown }> {
|
||||
const accountOwnerType = payload.accountOwnerType ?? '1';
|
||||
const nationalId = payload.nationalCode ?? '';
|
||||
const legalId = payload.legalId ?? null;
|
||||
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
||||
const inquiryConfig = this.config.inquiries[InquiryType.SHEBA];
|
||||
|
||||
if (!inquiryConfig?.url || !inquiryConfig.username || !inquiryConfig.password) {
|
||||
throw this.formatProviderError(
|
||||
undefined,
|
||||
undefined,
|
||||
'Parsian Sayah inquiry is not configured',
|
||||
);
|
||||
}
|
||||
|
||||
const token = await this.amitisProvider.getAccessToken(
|
||||
ProviderName.PARSIAN,
|
||||
InquiryType.SHEBA,
|
||||
inquiryConfig.username,
|
||||
inquiryConfig.password,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await axios.post<ParsianSayahResponse>(
|
||||
inquiryConfig.url,
|
||||
{
|
||||
AccountOwnerType: accountOwnerType,
|
||||
NationalId: nationalId,
|
||||
LegalId: legalId,
|
||||
ShebaId: shebaId,
|
||||
},
|
||||
{
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
return { raw: response.data };
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
const data = error.response?.data as ParsianSayahResponse | undefined;
|
||||
throw this.formatProviderError(
|
||||
this.formatErrors(data?.Errors ?? data?.errors) ?? error.message,
|
||||
String(error.response?.status ?? 'NETWORK_ERROR'),
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getRequiredString(value: unknown, fieldName: string): string {
|
||||
if (typeof value !== 'string' || !value.trim()) {
|
||||
throw this.formatProviderError(undefined, undefined, `${fieldName} is required`);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private hasSoapCredentials(
|
||||
inquiryConfig: ProviderEnvConfig['inquiries'][InquiryType],
|
||||
): inquiryConfig is NonNullable<ProviderEnvConfig['inquiries'][InquiryType]> {
|
||||
return Boolean(inquiryConfig?.url && inquiryConfig.username && inquiryConfig.password);
|
||||
}
|
||||
|
||||
private buildCarPolicyEnvelope(
|
||||
methodName: string,
|
||||
fields: Record<string, string>,
|
||||
username: string,
|
||||
password: string,
|
||||
passwordFieldName: string,
|
||||
): string {
|
||||
const fieldXml = Object.entries(fields)
|
||||
.map(([name, value]) => ` <${name}>${this.escapeXml(value)}</${name}>`)
|
||||
.join('\n');
|
||||
|
||||
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>
|
||||
<${methodName} xmlns="http://tempuri.org/">
|
||||
${fieldXml}
|
||||
<UserName>${this.escapeXml(username)}</UserName>
|
||||
<${passwordFieldName}>${this.escapeXml(password)}</${passwordFieldName}>
|
||||
</${methodName}>
|
||||
</soap:Body>
|
||||
</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, '&')
|
||||
.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, '&');
|
||||
}
|
||||
|
||||
private hasErrors(
|
||||
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
|
||||
): boolean {
|
||||
if (!errors) return false;
|
||||
return Array.isArray(errors) ? errors.length > 0 : Object.keys(errors).length > 0;
|
||||
}
|
||||
|
||||
private formatErrors(
|
||||
errors?: Record<string, string> | Array<{ Code?: string; Message?: string }> | null,
|
||||
): string | undefined {
|
||||
if (!this.hasErrors(errors)) return undefined;
|
||||
|
||||
if (Array.isArray(errors)) {
|
||||
return errors.map((error) => `${error.Code}: ${error.Message}`).join(', ');
|
||||
}
|
||||
|
||||
return Object.entries(errors ?? {})
|
||||
.map(([code, field]) => `${field} (code: ${code})`)
|
||||
.join(', ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user