forked from Shared/esg
parsian inquiries implemented
This commit is contained in:
39
.env.example
39
.env.example
@@ -22,9 +22,24 @@ THROTTLE_LIMIT=100
|
|||||||
PERSON_DEFAULT_PROVIDER=HAMTA
|
PERSON_DEFAULT_PROVIDER=HAMTA
|
||||||
PERSON_FALLBACK_ENABLED=true
|
PERSON_FALLBACK_ENABLED=true
|
||||||
PERSON_FALLBACK_PROVIDERS=MOALLEM,TEJARATNOU
|
PERSON_FALLBACK_PROVIDERS=MOALLEM,TEJARATNOU
|
||||||
|
SHAHKAR_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
SHAHKAR_FALLBACK_ENABLED=false
|
||||||
|
SHAHKAR_FALLBACK_PROVIDERS=
|
||||||
|
SHEBA_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
SHEBA_FALLBACK_ENABLED=false
|
||||||
|
SHEBA_FALLBACK_PROVIDERS=
|
||||||
CAR_PLATE_DEFAULT_PROVIDER=HAMTA
|
CAR_PLATE_DEFAULT_PROVIDER=HAMTA
|
||||||
CAR_PLATE_FALLBACK_ENABLED=true
|
CAR_PLATE_FALLBACK_ENABLED=true
|
||||||
CAR_PLATE_FALLBACK_PROVIDERS=
|
CAR_PLATE_FALLBACK_PROVIDERS=
|
||||||
|
POLICY_BY_CHASSIS_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
POLICY_BY_CHASSIS_FALLBACK_ENABLED=false
|
||||||
|
POLICY_BY_CHASSIS_FALLBACK_PROVIDERS=
|
||||||
|
POLICY_BY_PLATE_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
POLICY_BY_PLATE_FALLBACK_ENABLED=false
|
||||||
|
POLICY_BY_PLATE_FALLBACK_PROVIDERS=
|
||||||
|
POLICY_BY_NATIONAL_CODE_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
POLICY_BY_NATIONAL_CODE_FALLBACK_ENABLED=false
|
||||||
|
POLICY_BY_NATIONAL_CODE_FALLBACK_PROVIDERS=
|
||||||
|
|
||||||
# Hamta provider
|
# Hamta provider
|
||||||
HAMTA_BASE_URL=https://api.hamta.example.com
|
HAMTA_BASE_URL=https://api.hamta.example.com
|
||||||
@@ -44,6 +59,30 @@ MOALLEM_TIMEOUT=10000
|
|||||||
MOALLEM_ENABLED=true
|
MOALLEM_ENABLED=true
|
||||||
MOALLEM_MAX_RETRIES=2
|
MOALLEM_MAX_RETRIES=2
|
||||||
|
|
||||||
|
# Parsian provider
|
||||||
|
PARSIAN_ENABLED=true
|
||||||
|
PARSIAN_TIMEOUT=10000
|
||||||
|
PARSIAN_MAX_RETRIES=2
|
||||||
|
PARSIAN_SHAHKAR_URL=https://apigateway.parsianinsurance.com/shahkarinqOut
|
||||||
|
PARSIAN_SHAHKAR_API_KEY=
|
||||||
|
PARSIAN_SHAHKAR_AUTH_METHOD=NONE
|
||||||
|
PARSIAN_SHEBA_URL=https://sayah.services.centinsur.ir/api/Cisb/TatbighServiceAsync
|
||||||
|
PARSIAN_SHEBA_USERNAME=
|
||||||
|
PARSIAN_SHEBA_PASSWORD=
|
||||||
|
PARSIAN_SHEBA_AUTH_METHOD=AMITIS
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_USERNAME=
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_PASSWORD=
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_AUTH_METHOD=SOAP
|
||||||
|
PARSIAN_POLICY_BY_PLATE_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_PLATE_USERNAME=
|
||||||
|
PARSIAN_POLICY_BY_PLATE_PASSWORD=
|
||||||
|
PARSIAN_POLICY_BY_PLATE_AUTH_METHOD=SOAP
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_USERNAME=
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_PASSWORD=
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_AUTH_METHOD=SOAP
|
||||||
|
|
||||||
# TejaratNou provider
|
# TejaratNou provider
|
||||||
TEJARATNOU_BASE_URL=https://accounts.tejaratnoins.ir
|
TEJARATNOU_BASE_URL=https://accounts.tejaratnoins.ir
|
||||||
TEJARATNOU_INQUIRY_BASE_URL=https://gateway.tejaratnoins.ir
|
TEJARATNOU_INQUIRY_BASE_URL=https://gateway.tejaratnoins.ir
|
||||||
|
|||||||
@@ -134,10 +134,13 @@ See [Environment Configuration Guide](docs/ENVIRONMENT_CONFIGURATION.md) for com
|
|||||||
| `PERSON_INQUIRY` | HAMTA, MOALLEM | Person information by national code and birth date |
|
| `PERSON_INQUIRY` | HAMTA, MOALLEM | Person information by national code and birth date |
|
||||||
| `REAL_ESTATE_INQUIRY` | HAMTA | Real estate ownership information |
|
| `REAL_ESTATE_INQUIRY` | HAMTA | Real estate ownership information |
|
||||||
| `POSTAL_CODE_INQUIRY` | HAMTA, MOALLEM | Address validation by postal code |
|
| `POSTAL_CODE_INQUIRY` | HAMTA, MOALLEM | Address validation by postal code |
|
||||||
| `SHAHKAR_INQUIRY` | HAMTA, MOALLEM | Mobile number verification |
|
| `SHAHKAR_INQUIRY` | PARSIAN, HAMTA, MOALLEM | Mobile number verification |
|
||||||
| `CIVIL_REGISTRATION_INQUIRY` | MOALLEM | Civil registration data |
|
| `CIVIL_REGISTRATION_INQUIRY` | MOALLEM | Civil registration data |
|
||||||
| `SHEBA_INQUIRY` | MOALLEM | Bank account (SHEBA) validation |
|
| `SHEBA_INQUIRY` | PARSIAN, HAMTA, MOALLEM | Bank account (SHEBA) validation |
|
||||||
| `SAYAH_INQUIRY` | TEJARATNOU | Sayah system inquiry |
|
| `SAYAH_INQUIRY` | TEJARATNOU | Sayah system inquiry |
|
||||||
|
| `POLICY_BY_CHASSIS_INQUIRY` | PARSIAN | Car policy history by chassis number |
|
||||||
|
| `POLICY_BY_PLATE_INQUIRY` | PARSIAN | Car policy history by national plate |
|
||||||
|
| `POLICY_BY_NATIONAL_CODE_INQUIRY` | PARSIAN | Car policy history by national code |
|
||||||
|
|
||||||
See [API Documentation](docs/API_DOCUMENTATION.md) for detailed request/response formats.
|
See [API Documentation](docs/API_DOCUMENTATION.md) for detailed request/response formats.
|
||||||
|
|
||||||
|
|||||||
@@ -352,9 +352,10 @@ POST /api/inquiry/sheba
|
|||||||
### Request Body
|
### Request Body
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"accountOwnerType": "REAL",
|
"accountOwnerType": "1",
|
||||||
"nationalId": "0123456789",
|
"nationalCode": "0123456789",
|
||||||
"shebaId": "IR123456789012345678901234"
|
"legalId": null,
|
||||||
|
"sheba": "IR123456789012345678901234"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -362,10 +363,10 @@ POST /api/inquiry/sheba
|
|||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
|-------|------|----------|-------------|
|
|-------|------|----------|-------------|
|
||||||
| accountOwnerType | string | Yes | Account owner type: "REAL" (person) or "LEGAL" (company) |
|
| accountOwnerType | string | No | Account owner type; defaults to `"1"` in providers |
|
||||||
| nationalId | string | Conditional | Required if accountOwnerType is "REAL" |
|
| nationalCode | string | Yes | Iranian national code (10 digits) |
|
||||||
| legalId | string | Conditional | Required if accountOwnerType is "LEGAL" |
|
| legalId | string | No | Legal identifier when needed by the selected account owner type |
|
||||||
| shebaId | string | Yes | SHEBA/IBAN number (26 characters starting with IR) |
|
| sheba | string | Yes | SHEBA/IBAN number (26 characters starting with IR) |
|
||||||
|
|
||||||
### Success Response (200 OK)
|
### Success Response (200 OK)
|
||||||
|
|
||||||
@@ -398,12 +399,57 @@ curl -X POST https://api.example.com/api/inquiry/sheba \
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "X-API-Key: your-api-key-here" \
|
-H "X-API-Key: your-api-key-here" \
|
||||||
-d '{
|
-d '{
|
||||||
"accountOwnerType": "REAL",
|
"accountOwnerType": "1",
|
||||||
"nationalId": "0123456789",
|
"nationalCode": "0123456789",
|
||||||
"shebaId": "IR123456789012345678901234"
|
"sheba": "IR123456789012345678901234"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Car Policy By Chassis
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
```
|
||||||
|
POST /api/inquiry/policyByChassis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Body
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chassisNo": "NAAM01E15HK123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Car Policy By Plate
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
```
|
||||||
|
POST /api/inquiry/policyByPlate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Body
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plk1": "12",
|
||||||
|
"plk2": "ب",
|
||||||
|
"plk3": "345",
|
||||||
|
"plksrl": "67"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Car Policy By National Code
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
```
|
||||||
|
POST /api/inquiry/policyByNationalCode
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Body
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nationalCode": "0123456789"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Error Responses
|
## Error Responses
|
||||||
|
|
||||||
### 400 Bad Request - Invalid Input
|
### 400 Bad Request - Invalid Input
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ HAMTA_LEGAL_PERSON_URL=https://api.hamta.example.com/api/inquiry/legalPerson
|
|||||||
HAMTA_LEGAL_PERSON_USERNAME=SabtAsnadHamta
|
HAMTA_LEGAL_PERSON_USERNAME=SabtAsnadHamta
|
||||||
HAMTA_LEGAL_PERSON_PASSWORD=HSY1f6?e@kSJ
|
HAMTA_LEGAL_PERSON_PASSWORD=HSY1f6?e@kSJ
|
||||||
HAMTA_LEGAL_PERSON_AUTH_METHOD=AMITIS
|
HAMTA_LEGAL_PERSON_AUTH_METHOD=AMITIS
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### MOALLEM Provider
|
### MOALLEM Provider
|
||||||
@@ -194,6 +195,44 @@ MOALLEM_LEGAL_PERSON_PASSWORD=
|
|||||||
MOALLEM_LEGAL_PERSON_AUTH_METHOD=SOAP
|
MOALLEM_LEGAL_PERSON_AUTH_METHOD=SOAP
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### PARSIAN Provider
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Global Settings
|
||||||
|
PARSIAN_ENABLED=true
|
||||||
|
PARSIAN_TIMEOUT=10000
|
||||||
|
PARSIAN_MAX_RETRIES=2
|
||||||
|
|
||||||
|
# Shahkar Inquiry (X-PACKAGE-API-KEY header)
|
||||||
|
PARSIAN_SHAHKAR_URL=https://apigateway.parsianinsurance.com/shahkarinqOut
|
||||||
|
PARSIAN_SHAHKAR_API_KEY=your-package-api-key
|
||||||
|
PARSIAN_SHAHKAR_AUTH_METHOD=NONE
|
||||||
|
|
||||||
|
# Sheba/Sayah Inquiry (AMITIS auth)
|
||||||
|
PARSIAN_SHEBA_URL=https://sayah.services.centinsur.ir/api/Cisb/TatbighServiceAsync
|
||||||
|
PARSIAN_SHEBA_USERNAME=parsian.sayah
|
||||||
|
PARSIAN_SHEBA_PASSWORD=your-password
|
||||||
|
PARSIAN_SHEBA_AUTH_METHOD=AMITIS
|
||||||
|
|
||||||
|
# Car policy inquiry by chassis (SOAP auth)
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_USERNAME=pa6476
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_PASSWORD=your-password
|
||||||
|
PARSIAN_POLICY_BY_CHASSIS_AUTH_METHOD=SOAP
|
||||||
|
|
||||||
|
# Car policy inquiry by national plate (SOAP auth)
|
||||||
|
PARSIAN_POLICY_BY_PLATE_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_PLATE_USERNAME=pa6476
|
||||||
|
PARSIAN_POLICY_BY_PLATE_PASSWORD=your-password
|
||||||
|
PARSIAN_POLICY_BY_PLATE_AUTH_METHOD=SOAP
|
||||||
|
|
||||||
|
# Car policy inquiry by national code (SOAP auth)
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_URL=http://reinsure.centinsur.ir/CarAllPlcysV4
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_USERNAME=pa6476
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_PASSWORD=your-password
|
||||||
|
PARSIAN_POLICY_BY_NATIONAL_CODE_AUTH_METHOD=SOAP
|
||||||
|
```
|
||||||
|
|
||||||
### TEJARATNOU Provider
|
### TEJARATNOU Provider
|
||||||
|
|
||||||
```env
|
```env
|
||||||
@@ -265,11 +304,18 @@ REAL_ESTATE_FALLBACK_PROVIDERS=
|
|||||||
|
|
||||||
#### Shahkar Inquiry
|
#### Shahkar Inquiry
|
||||||
```env
|
```env
|
||||||
SHAHKAR_DEFAULT_PROVIDER=MOALLEM
|
SHAHKAR_DEFAULT_PROVIDER=PARSIAN
|
||||||
SHAHKAR_FALLBACK_ENABLED=false
|
SHAHKAR_FALLBACK_ENABLED=false
|
||||||
SHAHKAR_FALLBACK_PROVIDERS=
|
SHAHKAR_FALLBACK_PROVIDERS=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Sheba/Sayah Inquiry
|
||||||
|
```env
|
||||||
|
SHEBA_DEFAULT_PROVIDER=PARSIAN
|
||||||
|
SHEBA_FALLBACK_ENABLED=false
|
||||||
|
SHEBA_FALLBACK_PROVIDERS=
|
||||||
|
```
|
||||||
|
|
||||||
## Provider Behavior Examples
|
## Provider Behavior Examples
|
||||||
|
|
||||||
### Scenario 1: All Providers Enabled
|
### Scenario 1: All Providers Enabled
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ export class InquiryAccessGuard implements CanActivate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inquiryKey = requiredInquiry as string;
|
// Temporarily disabled: allow every authenticated user/client to call every inquiry.
|
||||||
if (!user.allowedInquiries.includes(inquiryKey)) {
|
// const inquiryKey = requiredInquiry as string;
|
||||||
throw new ForbiddenException(`Access denied for inquiry: ${inquiryKey}`);
|
// if (!user.allowedInquiries.includes(inquiryKey)) {
|
||||||
}
|
// throw new ForbiddenException(`Access denied for inquiry: ${inquiryKey}`);
|
||||||
|
// }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,7 @@ export enum InquiryType {
|
|||||||
POSTAL_CODE = 'POSTAL_CODE_INQUIRY',
|
POSTAL_CODE = 'POSTAL_CODE_INQUIRY',
|
||||||
LEGAL_PERSON = 'LEGAL_PERSON_INQUIRY',
|
LEGAL_PERSON = 'LEGAL_PERSON_INQUIRY',
|
||||||
CAR_PLATE = 'CAR_PLATE_INQUIRY',
|
CAR_PLATE = 'CAR_PLATE_INQUIRY',
|
||||||
|
POLICY_BY_CHASSIS = 'POLICY_BY_CHASSIS_INQUIRY',
|
||||||
|
POLICY_BY_PLATE = 'POLICY_BY_PLATE_INQUIRY',
|
||||||
|
POLICY_BY_NATIONAL_CODE = 'POLICY_BY_NATIONAL_CODE_INQUIRY',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
export enum ProviderName {
|
export enum ProviderName {
|
||||||
HAMTA = 'HAMTA',
|
HAMTA = 'HAMTA',
|
||||||
MOALLEM = 'MOALLEM',
|
MOALLEM = 'MOALLEM',
|
||||||
|
PARSIAN = 'PARSIAN',
|
||||||
TEJARATNOU = 'TEJARATNOU',
|
TEJARATNOU = 'TEJARATNOU',
|
||||||
AMITIS = 'AMITIS',
|
AMITIS = 'AMITIS',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface InquiryConfig {
|
|||||||
url: string;
|
url: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
apiKey: string;
|
||||||
authMethod: AuthMethod;
|
authMethod: AuthMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ export default () => ({
|
|||||||
} satisfies AmitisAuthServiceConfig,
|
} satisfies AmitisAuthServiceConfig,
|
||||||
hamta: buildProviderConfig('HAMTA'),
|
hamta: buildProviderConfig('HAMTA'),
|
||||||
moallem: buildProviderConfig('MOALLEM'),
|
moallem: buildProviderConfig('MOALLEM'),
|
||||||
|
parsian: buildProviderConfig('PARSIAN'),
|
||||||
tejaratnou: {
|
tejaratnou: {
|
||||||
enabled: process.env.TEJARATNOU_ENABLED !== 'false',
|
enabled: process.env.TEJARATNOU_ENABLED !== 'false',
|
||||||
timeout: parseInt(process.env.TEJARATNOU_TIMEOUT ?? '15000', 10),
|
timeout: parseInt(process.env.TEJARATNOU_TIMEOUT ?? '15000', 10),
|
||||||
@@ -97,10 +99,10 @@ export default () => ({
|
|||||||
[InquiryType.REAL_ESTATE]: buildInquiryRoutingConfig('REAL_ESTATE', ProviderName.HAMTA, [
|
[InquiryType.REAL_ESTATE]: buildInquiryRoutingConfig('REAL_ESTATE', ProviderName.HAMTA, [
|
||||||
ProviderName.MOALLEM,
|
ProviderName.MOALLEM,
|
||||||
]),
|
]),
|
||||||
[InquiryType.SHEBA]: buildInquiryRoutingConfig('SHEBA', ProviderName.MOALLEM, [
|
[InquiryType.SHEBA]: buildInquiryRoutingConfig('SHEBA', ProviderName.PARSIAN, [
|
||||||
ProviderName.HAMTA,
|
ProviderName.HAMTA,
|
||||||
]),
|
]),
|
||||||
[InquiryType.SHAHKAR]: buildInquiryRoutingConfig('SHAHKAR', ProviderName.MOALLEM, [
|
[InquiryType.SHAHKAR]: buildInquiryRoutingConfig('SHAHKAR', ProviderName.PARSIAN, [
|
||||||
ProviderName.HAMTA,
|
ProviderName.HAMTA,
|
||||||
]),
|
]),
|
||||||
[InquiryType.POSTAL_CODE]: buildInquiryRoutingConfig('POSTAL_CODE', ProviderName.MOALLEM, [
|
[InquiryType.POSTAL_CODE]: buildInquiryRoutingConfig('POSTAL_CODE', ProviderName.MOALLEM, [
|
||||||
@@ -110,6 +112,21 @@ export default () => ({
|
|||||||
ProviderName.MOALLEM,
|
ProviderName.MOALLEM,
|
||||||
]),
|
]),
|
||||||
[InquiryType.CAR_PLATE]: buildInquiryRoutingConfig('CAR_PLATE', ProviderName.HAMTA, []),
|
[InquiryType.CAR_PLATE]: buildInquiryRoutingConfig('CAR_PLATE', ProviderName.HAMTA, []),
|
||||||
|
[InquiryType.POLICY_BY_CHASSIS]: buildInquiryRoutingConfig(
|
||||||
|
'POLICY_BY_CHASSIS',
|
||||||
|
ProviderName.PARSIAN,
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
[InquiryType.POLICY_BY_PLATE]: buildInquiryRoutingConfig(
|
||||||
|
'POLICY_BY_PLATE',
|
||||||
|
ProviderName.PARSIAN,
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
[InquiryType.POLICY_BY_NATIONAL_CODE]: buildInquiryRoutingConfig(
|
||||||
|
'POLICY_BY_NATIONAL_CODE',
|
||||||
|
ProviderName.PARSIAN,
|
||||||
|
[],
|
||||||
|
),
|
||||||
} satisfies Record<InquiryType, InquiryRoutingConfig>,
|
} satisfies Record<InquiryType, InquiryRoutingConfig>,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,6 +139,9 @@ function buildProviderConfig(prefix: string): ProviderEnvConfig {
|
|||||||
InquiryType.POSTAL_CODE,
|
InquiryType.POSTAL_CODE,
|
||||||
InquiryType.LEGAL_PERSON,
|
InquiryType.LEGAL_PERSON,
|
||||||
InquiryType.CAR_PLATE,
|
InquiryType.CAR_PLATE,
|
||||||
|
InquiryType.POLICY_BY_CHASSIS,
|
||||||
|
InquiryType.POLICY_BY_PLATE,
|
||||||
|
InquiryType.POLICY_BY_NATIONAL_CODE,
|
||||||
];
|
];
|
||||||
|
|
||||||
const inquiries: Partial<Record<InquiryType, InquiryConfig>> = {};
|
const inquiries: Partial<Record<InquiryType, InquiryConfig>> = {};
|
||||||
@@ -146,6 +166,7 @@ function buildInquiryConfig(providerPrefix: string, inquiryPrefix: string): Inqu
|
|||||||
url: process.env[`${providerPrefix}_${inquiryPrefix}_URL`] ?? '',
|
url: process.env[`${providerPrefix}_${inquiryPrefix}_URL`] ?? '',
|
||||||
username: process.env[`${providerPrefix}_${inquiryPrefix}_USERNAME`] ?? '',
|
username: process.env[`${providerPrefix}_${inquiryPrefix}_USERNAME`] ?? '',
|
||||||
password: process.env[`${providerPrefix}_${inquiryPrefix}_PASSWORD`] ?? '',
|
password: process.env[`${providerPrefix}_${inquiryPrefix}_PASSWORD`] ?? '',
|
||||||
|
apiKey: process.env[`${providerPrefix}_${inquiryPrefix}_API_KEY`] ?? '',
|
||||||
authMethod: (process.env[`${providerPrefix}_${inquiryPrefix}_AUTH_METHOD`] ?? 'NONE') as AuthMethod,
|
authMethod: (process.env[`${providerPrefix}_${inquiryPrefix}_AUTH_METHOD`] ?? 'NONE') as AuthMethod,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -159,6 +180,9 @@ function inquiryTypeToEnvName(inquiryType: InquiryType): string {
|
|||||||
[InquiryType.POSTAL_CODE]: 'POSTAL_CODE',
|
[InquiryType.POSTAL_CODE]: 'POSTAL_CODE',
|
||||||
[InquiryType.LEGAL_PERSON]: 'LEGAL_PERSON',
|
[InquiryType.LEGAL_PERSON]: 'LEGAL_PERSON',
|
||||||
[InquiryType.CAR_PLATE]: 'CAR_PLATE',
|
[InquiryType.CAR_PLATE]: 'CAR_PLATE',
|
||||||
|
[InquiryType.POLICY_BY_CHASSIS]: 'POLICY_BY_CHASSIS',
|
||||||
|
[InquiryType.POLICY_BY_PLATE]: 'POLICY_BY_PLATE',
|
||||||
|
[InquiryType.POLICY_BY_NATIONAL_CODE]: 'POLICY_BY_NATIONAL_CODE',
|
||||||
};
|
};
|
||||||
return mapping[inquiryType];
|
return mapping[inquiryType];
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/inquiry/dto/policy-by-chassis-request.dto.ts
Normal file
9
src/inquiry/dto/policy-by-chassis-request.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class PolicyByChassisRequestDto {
|
||||||
|
@ApiProperty({ example: 'NAAM01E15HK123456', description: 'Vehicle chassis number' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
chassisNo!: string;
|
||||||
|
}
|
||||||
11
src/inquiry/dto/policy-by-national-code-request.dto.ts
Normal file
11
src/inquiry/dto/policy-by-national-code-request.dto.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';
|
||||||
|
|
||||||
|
export class PolicyByNationalCodeRequestDto {
|
||||||
|
@ApiProperty({ example: '4311402422', description: 'Iranian national code (10 digits)' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Length(10, 10)
|
||||||
|
@Matches(/^\d{10}$/, { message: 'nationalCode must be exactly 10 digits' })
|
||||||
|
nationalCode!: string;
|
||||||
|
}
|
||||||
24
src/inquiry/dto/policy-by-plate-request.dto.ts
Normal file
24
src/inquiry/dto/policy-by-plate-request.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString, Matches } from 'class-validator';
|
||||||
|
|
||||||
|
export class PolicyByPlateRequestDto {
|
||||||
|
@ApiProperty({ example: '12', description: 'Two digits on the left side of the plate' })
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^\d{2}$/, { message: 'plk1 must be exactly 2 digits' })
|
||||||
|
plk1!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'ب', description: 'Middle plate letter' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
plk2!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '345', description: 'Three digits on the right side of the plate' })
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^\d{3}$/, { message: 'plk3 must be exactly 3 digits' })
|
||||||
|
plk3!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '67', description: 'Plate serial' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
plksrl!: string;
|
||||||
|
}
|
||||||
@@ -1,27 +1,33 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
import { IsIn, IsNotEmpty, IsOptional, IsString, Length, Matches } from 'class-validator';
|
import { IsIn, IsNotEmpty, IsOptional, IsString, Length, Matches } from 'class-validator';
|
||||||
|
|
||||||
export class SayahRequestDto {
|
export class SayahRequestDto {
|
||||||
@ApiProperty({ example: '1', description: '1 for natural person (always use "1")' })
|
@ApiPropertyOptional({ example: '1', description: '1 for natural person' })
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsIn(['1'])
|
@IsIn(['1'])
|
||||||
accountOwnerType!: string;
|
accountOwnerType?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '4311402422', description: 'Iranian national code (10 digits)' })
|
@ApiProperty({ example: '4311402422', description: 'Iranian national code (10 digits)' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@Length(10, 10)
|
@Length(10, 10)
|
||||||
@Matches(/^\d{10}$/, { message: 'nationalId must be exactly 10 digits' })
|
@Matches(/^\d{10}$/, { message: 'nationalCode must be exactly 10 digits' })
|
||||||
nationalId!: string;
|
nationalCode!: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ nullable: true })
|
@ApiPropertyOptional({ nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
legalId?: string | null;
|
legalId?: string | null;
|
||||||
|
|
||||||
@ApiProperty({ example: 'IR800560611828005105117001', description: 'Iranian Sheba ID (26 characters)' })
|
@ApiProperty({
|
||||||
|
example: 'IR800560611828005105117001',
|
||||||
|
description: 'Iranian Sheba ID (26 characters)',
|
||||||
|
})
|
||||||
|
@Transform(({ value }: { value?: string }) => value?.toUpperCase())
|
||||||
@IsString()
|
@IsString()
|
||||||
@Length(26, 26)
|
@Length(26, 26)
|
||||||
@Matches(/^IR\d{24}$/, { message: 'shebaId must start with IR and contain 24 digits' })
|
@Matches(/^IR\d{24}$/, { message: 'sheba must start with IR and contain 24 digits' })
|
||||||
shebaId!: string;
|
sheba!: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import { GenericInquiryResponseDto } from './dto/generic-inquiry-response.dto';
|
|||||||
import { PostalCodeRequestDto } from './dto/postal-code-request.dto';
|
import { PostalCodeRequestDto } from './dto/postal-code-request.dto';
|
||||||
import { ShahkarRequestDto } from './dto/shahkar-request.dto';
|
import { ShahkarRequestDto } from './dto/shahkar-request.dto';
|
||||||
import { SayahRequestDto } from './dto/sayah-request.dto';
|
import { SayahRequestDto } from './dto/sayah-request.dto';
|
||||||
|
import { PolicyByChassisRequestDto } from './dto/policy-by-chassis-request.dto';
|
||||||
|
import { PolicyByNationalCodeRequestDto } from './dto/policy-by-national-code-request.dto';
|
||||||
|
import { PolicyByPlateRequestDto } from './dto/policy-by-plate-request.dto';
|
||||||
import { InquiryService } from './inquiry.service';
|
import { InquiryService } from './inquiry.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,6 +130,54 @@ export class InquiryController {
|
|||||||
return this.inquireGeneric(InquiryType.LEGAL_PERSON, payload, req);
|
return this.inquireGeneric(InquiryType.LEGAL_PERSON, payload, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('policyByChassis')
|
||||||
|
@InquiryAccess(InquiryType.POLICY_BY_CHASSIS)
|
||||||
|
@Throttle({ default: { limit: 30, ttl: 60000 } })
|
||||||
|
@ApiOperation({ summary: 'Policy inquiry by chassis number' })
|
||||||
|
@ApiResponse({ status: 200, type: GenericInquiryResponseDto })
|
||||||
|
async inquirePolicyByChassis(
|
||||||
|
@Body() payload: PolicyByChassisRequestDto,
|
||||||
|
@Req() req: Request & { requestId?: string; trackingCode?: string },
|
||||||
|
): Promise<BaseInquiryResponseDto<Record<string, unknown>>> {
|
||||||
|
return this.inquireGeneric(
|
||||||
|
InquiryType.POLICY_BY_CHASSIS,
|
||||||
|
payload as unknown as Record<string, unknown>,
|
||||||
|
req,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('policyByPlate')
|
||||||
|
@InquiryAccess(InquiryType.POLICY_BY_PLATE)
|
||||||
|
@Throttle({ default: { limit: 30, ttl: 60000 } })
|
||||||
|
@ApiOperation({ summary: 'Policy inquiry by national vehicle plate' })
|
||||||
|
@ApiResponse({ status: 200, type: GenericInquiryResponseDto })
|
||||||
|
async inquirePolicyByPlate(
|
||||||
|
@Body() payload: PolicyByPlateRequestDto,
|
||||||
|
@Req() req: Request & { requestId?: string; trackingCode?: string },
|
||||||
|
): Promise<BaseInquiryResponseDto<Record<string, unknown>>> {
|
||||||
|
return this.inquireGeneric(
|
||||||
|
InquiryType.POLICY_BY_PLATE,
|
||||||
|
payload as unknown as Record<string, unknown>,
|
||||||
|
req,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('policyByNationalCode')
|
||||||
|
@InquiryAccess(InquiryType.POLICY_BY_NATIONAL_CODE)
|
||||||
|
@Throttle({ default: { limit: 30, ttl: 60000 } })
|
||||||
|
@ApiOperation({ summary: 'Policy inquiry by national code' })
|
||||||
|
@ApiResponse({ status: 200, type: GenericInquiryResponseDto })
|
||||||
|
async inquirePolicyByNationalCode(
|
||||||
|
@Body() payload: PolicyByNationalCodeRequestDto,
|
||||||
|
@Req() req: Request & { requestId?: string; trackingCode?: string },
|
||||||
|
): Promise<BaseInquiryResponseDto<Record<string, unknown>>> {
|
||||||
|
return this.inquireGeneric(
|
||||||
|
InquiryType.POLICY_BY_NATIONAL_CODE,
|
||||||
|
payload as unknown as Record<string, unknown>,
|
||||||
|
req,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async inquireGeneric(
|
private async inquireGeneric(
|
||||||
inquiryType: InquiryType,
|
inquiryType: InquiryType,
|
||||||
payload: Record<string, unknown>,
|
payload: Record<string, unknown>,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { InquiryProvider } from '../../common/interfaces/inquiry-provider.interf
|
|||||||
import { InquiryRoutingConfig } from '../../config/configuration';
|
import { InquiryRoutingConfig } from '../../config/configuration';
|
||||||
import { HamtaProvider } from '../implementations/hamta.provider';
|
import { HamtaProvider } from '../implementations/hamta.provider';
|
||||||
import { MoallemProvider } from '../implementations/moallem.provider';
|
import { MoallemProvider } from '../implementations/moallem.provider';
|
||||||
|
import { ParsianProvider } from '../implementations/parsian.provider';
|
||||||
import { TejaratNouProvider } from '../implementations/tejaratnou.provider';
|
import { TejaratNouProvider } from '../implementations/tejaratnou.provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,11 +23,13 @@ export class ProviderFactory {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
hamta: HamtaProvider,
|
hamta: HamtaProvider,
|
||||||
moallem: MoallemProvider,
|
moallem: MoallemProvider,
|
||||||
|
parsian: ParsianProvider,
|
||||||
tejaratnou: TejaratNouProvider,
|
tejaratnou: TejaratNouProvider,
|
||||||
) {
|
) {
|
||||||
this.registry = new Map<ProviderName, InquiryProvider>([
|
this.registry = new Map<ProviderName, InquiryProvider>([
|
||||||
[ProviderName.HAMTA, hamta],
|
[ProviderName.HAMTA, hamta],
|
||||||
[ProviderName.MOALLEM, moallem],
|
[ProviderName.MOALLEM, moallem],
|
||||||
|
[ProviderName.PARSIAN, parsian],
|
||||||
[ProviderName.TEJARATNOU, tejaratnou],
|
[ProviderName.TEJARATNOU, tejaratnou],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ interface ShahkarPayload extends LegacyInquiryPayload {
|
|||||||
|
|
||||||
interface SayahPayload extends LegacyInquiryPayload {
|
interface SayahPayload extends LegacyInquiryPayload {
|
||||||
accountOwnerType?: string;
|
accountOwnerType?: string;
|
||||||
nationalId?: string;
|
nationalCode?: string;
|
||||||
legalId?: string | null;
|
legalId?: string | null;
|
||||||
shebaId?: string;
|
sheba?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,10 +226,10 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
||||||
const accountOwnerType = this.getRequiredString(payload.accountOwnerType, 'accountOwnerType');
|
const accountOwnerType = payload.accountOwnerType ?? '1';
|
||||||
const nationalId = payload.nationalId ?? '';
|
const nationalId = payload.nationalCode ?? '';
|
||||||
const legalId = payload.legalId ?? null;
|
const legalId = payload.legalId ?? null;
|
||||||
const shebaId = this.getRequiredString(payload.shebaId, 'shebaId');
|
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
||||||
|
|
||||||
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
||||||
const token = await this.amitisProvider.getAccessToken(
|
const token = await this.amitisProvider.getAccessToken(
|
||||||
@@ -340,20 +340,20 @@ export class HamtaProvider extends LegacyApiProvider {
|
|||||||
|
|
||||||
private escapeXml(value: string): string {
|
private escapeXml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeXml(value: string): string {
|
private decodeXml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/'/g, "'")
|
.replace(/'/g, "'")
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/&/g, '&');
|
.replace(/&/g, '&');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ interface ShahkarPayload extends LegacyInquiryPayload {
|
|||||||
|
|
||||||
interface SayahPayload extends LegacyInquiryPayload {
|
interface SayahPayload extends LegacyInquiryPayload {
|
||||||
accountOwnerType?: string;
|
accountOwnerType?: string;
|
||||||
nationalId?: string;
|
nationalCode?: string;
|
||||||
legalId?: string | null;
|
legalId?: string | null;
|
||||||
shebaId?: string;
|
sheba?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,10 +226,10 @@ export class MoallemProvider extends LegacyApiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
private async inquireSayah(payload: SayahPayload): Promise<{ raw: unknown }> {
|
||||||
const accountOwnerType = this.getRequiredString(payload.accountOwnerType, 'accountOwnerType');
|
const accountOwnerType = payload.accountOwnerType ?? '1';
|
||||||
const nationalId = payload.nationalId ?? '';
|
const nationalId = payload.nationalCode ?? '';
|
||||||
const legalId = payload.legalId ?? null;
|
const legalId = payload.legalId ?? null;
|
||||||
const shebaId = this.getRequiredString(payload.shebaId, 'shebaId');
|
const shebaId = this.getRequiredString(payload.sheba, 'sheba');
|
||||||
|
|
||||||
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
const inquiryConfig = this.getInquiryConfig(InquiryType.SHEBA);
|
||||||
const token = await this.amitisProvider.getAccessToken(
|
const token = await this.amitisProvider.getAccessToken(
|
||||||
|
|||||||
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(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { MongooseModule } from '@nestjs/mongoose';
|
|||||||
import { ProviderFactory } from './factory/provider.factory';
|
import { ProviderFactory } from './factory/provider.factory';
|
||||||
import { HamtaProvider } from './implementations/hamta.provider';
|
import { HamtaProvider } from './implementations/hamta.provider';
|
||||||
import { MoallemProvider } from './implementations/moallem.provider';
|
import { MoallemProvider } from './implementations/moallem.provider';
|
||||||
|
import { ParsianProvider } from './implementations/parsian.provider';
|
||||||
import { TejaratNouProvider } from './implementations/tejaratnou.provider';
|
import { TejaratNouProvider } from './implementations/tejaratnou.provider';
|
||||||
import { AmitisProvider } from './implementations/amitis.provider';
|
import { AmitisProvider } from './implementations/amitis.provider';
|
||||||
import { ProviderOrchestratorService } from './strategy/provider-orchestrator.service';
|
import { ProviderOrchestratorService } from './strategy/provider-orchestrator.service';
|
||||||
@@ -24,6 +25,7 @@ import { CachedInquiryResultService } from './services/cached-inquiry-result.ser
|
|||||||
providers: [
|
providers: [
|
||||||
HamtaProvider,
|
HamtaProvider,
|
||||||
MoallemProvider,
|
MoallemProvider,
|
||||||
|
ParsianProvider,
|
||||||
TejaratNouProvider,
|
TejaratNouProvider,
|
||||||
AmitisProvider,
|
AmitisProvider,
|
||||||
GeneralTokenService,
|
GeneralTokenService,
|
||||||
|
|||||||
@@ -94,7 +94,10 @@
|
|||||||
"SHEBA_INQUIRY",
|
"SHEBA_INQUIRY",
|
||||||
"SHAHKAR_INQUIRY",
|
"SHAHKAR_INQUIRY",
|
||||||
"LEGAL_PERSON_INQUIRY",
|
"LEGAL_PERSON_INQUIRY",
|
||||||
"CAR_PLATE_INQUIRY"
|
"CAR_PLATE_INQUIRY",
|
||||||
|
"POLICY_BY_CHASSIS_INQUIRY",
|
||||||
|
"POLICY_BY_PLATE_INQUIRY",
|
||||||
|
"POLICY_BY_NATIONAL_CODE_INQUIRY"
|
||||||
],
|
],
|
||||||
"requestLimitPerMinute": 60,
|
"requestLimitPerMinute": 60,
|
||||||
"requestLimitPerDay": 10000,
|
"requestLimitPerDay": 10000,
|
||||||
|
|||||||
Reference in New Issue
Block a user