feat: add car inquiries with resilience, ownership privacy, and retry visibility

Introduce plate, chassis, and third-party car endpoints, a wall-clock inquiry
deadline with transport-only retries, and ownership-safe no-match errors so
another person's identity never leaks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 17:21:16 +03:30
parent b4be98b156
commit 61098f1bf4
40 changed files with 2595 additions and 374 deletions

View File

@@ -12,12 +12,18 @@ import {
parseFirstCarPolicy,
} from '../../common/helpers/soap-car-policy.helper';
import { getSayahProviderError, SayahApiResponse } from '../../common/helpers/sayah-response.helper';
import { assertCarPolicyOwnedBy } from '../../common/helpers/car-inquiry-safety.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';
import { ProviderEnvConfig } from '../../config/configuration';
import { BaseProvider } from '../base/base-provider.abstract';
import { AmitisProvider } from './amitis.provider';
import {
CentInsurCarProviderSupport,
CarChassisPayload,
CarPlatePayload,
} from '../shared/centinsur-car-provider.support';
import {
LegacyInquiryPayload,
LegacyInquiryResult,
@@ -95,6 +101,9 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
InquiryType.PERSON,
InquiryType.SHAHKAR,
InquiryType.SHEBA,
InquiryType.CAR_BY_PLATE,
InquiryType.CAR_BY_CHASSIS,
InquiryType.THIRD_PARTY_CAR,
InquiryType.POLICY_BY_CHASSIS,
InquiryType.POLICY_BY_PLATE,
InquiryType.POLICY_BY_NATIONAL_CODE,
@@ -103,6 +112,7 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
constructor(
configService: ConfigService,
private readonly amitisProvider: AmitisProvider,
private readonly centInsurCarSupport: CentInsurCarProviderSupport,
) {
super(configService.get<ProviderEnvConfig>('parsian')!);
}
@@ -125,7 +135,9 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
Boolean(shebaConfig?.password)) ||
this.hasSoapCredentials(policyByChassisConfig) ||
this.hasSoapCredentials(policyByPlateConfig) ||
this.hasSoapCredentials(policyByNationalCodeConfig))
this.hasSoapCredentials(policyByNationalCodeConfig) ||
this.centInsurCarSupport.hasCarPolicyConfig(this.config, InquiryType.CAR_BY_PLATE) ||
this.centInsurCarSupport.hasCarPolicyConfig(this.config, InquiryType.THIRD_PARTY_CAR))
);
}
@@ -146,6 +158,33 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
return this.inquireSayah(payload as ParsianSayahPayload);
}
if (inquiryType === InquiryType.CAR_BY_PLATE) {
return this.centInsurCarSupport.inquireCarByPlate(
this.config,
payload as CarPlatePayload,
(message, code, fallback, extras) =>
this.formatProviderError(message, code, fallback, extras as never),
);
}
if (inquiryType === InquiryType.CAR_BY_CHASSIS) {
return this.centInsurCarSupport.inquireCarByChassis(
this.config,
payload as CarChassisPayload,
(message, code, fallback, extras) =>
this.formatProviderError(message, code, fallback, extras as never),
);
}
if (inquiryType === InquiryType.THIRD_PARTY_CAR) {
return this.centInsurCarSupport.inquireThirdPartyCar(
this.config,
payload as CarPlatePayload,
(message, code, fallback, extras) =>
this.formatProviderError(message, code, fallback, extras as never),
);
}
if (inquiryType === InquiryType.POLICY_BY_CHASSIS) {
return this.inquirePolicyByChassis(payload as ParsianPolicyByChassisPayload);
}
@@ -346,20 +385,9 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
{ Plk1: plk1, Plk2: plk2, Plk3: plk3, PlkSrl: plksrl },
);
const policyNationalId = policy.NtnlId?.trim() ?? '';
if (!policyNationalId || !this.nationalIdsMatch(nationalCode, policyNationalId)) {
throw this.formatProviderError(
'عدم تطابق اطلاعات',
'INQUIRY_NO_MATCH',
undefined,
{
conflict: {
nationalCode,
NtnlId: policyNationalId,
},
},
);
}
assertCarPolicyOwnedBy(nationalCode, policy, (message, code, fallback, extras) =>
this.formatProviderError(message, code, fallback, extras as never),
);
return {
raw: {
@@ -373,10 +401,6 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
};
}
private nationalIdsMatch(requested: string, fromPolicy: string): boolean {
return requested.trim().padStart(10, '0') === fromPolicy.trim().padStart(10, '0');
}
private async callCarPolicySoap(
inquiryType: InquiryType,
methodName: string,