initial commit

This commit is contained in:
2026-06-09 14:07:37 +03:30
parent 30ac533800
commit 996a4fcda7
121 changed files with 20557 additions and 3 deletions

View File

@@ -0,0 +1,165 @@
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 { ProviderEnvConfig } from '../../config/configuration';
import { AmitisProvider } from './amitis.provider';
import { ProviderExecutionContext } from '../../common/interfaces/inquiry-provider.interface';
import {
LegacyInquiryPayload,
LegacyInquiryResult,
LegacyApiProvider,
} from '../shared/legacy-api.provider.abstract';
interface ShahkarInquiryPayload extends LegacyInquiryPayload {
nationalCode?: string;
nationalCod?: string;
NationalCod?: string;
mobileNo?: string;
MobileNo?: string;
}
/**
* Hamta provider — uses shared LegacyApiProvider base.
* Supports multiple inquiry types with different authentication methods.
*/
@Injectable()
export class HamtaProvider extends LegacyApiProvider {
readonly name = ProviderName.HAMTA;
readonly supportedInquiryTypes = [
InquiryType.PERSON,
InquiryType.REAL_ESTATE,
InquiryType.SHEBA,
InquiryType.SHAHKAR,
InquiryType.POSTAL_CODE,
InquiryType.LEGAL_PERSON,
];
private readonly hamtaConfig: ProviderEnvConfig;
constructor(configService: ConfigService, private readonly amitisProvider: AmitisProvider) {
const config = configService.get<ProviderEnvConfig>('hamta')!;
super(
config,
(providerName, inquiryType, username, password) =>
amitisProvider.getAccessToken(providerName, inquiryType, username, password),
);
this.hamtaConfig = config;
}
protected async callProvider(
inquiryType: InquiryType,
payload: LegacyInquiryPayload,
context: ProviderExecutionContext,
): Promise<LegacyInquiryResult> {
// Shahkar uses SOAP authentication, handled separately
if (inquiryType === InquiryType.SHAHKAR) {
return this.inquireShahkar(payload as ShahkarInquiryPayload);
}
return super.callProvider(inquiryType, payload, context);
}
private async inquireShahkar(payload: ShahkarInquiryPayload): Promise<{ raw: unknown }> {
const nationalCode = this.getRequiredString(
payload.nationalCode ?? payload.nationalCod ?? payload.NationalCod,
'nationalCode',
);
const mobileNo = this.getRequiredString(payload.mobileNo ?? payload.MobileNo, 'mobileNo');
const inquiryConfig = this.getInquiryConfig(InquiryType.SHAHKAR);
try {
const response = await axios.post<string>(
inquiryConfig.url,
this.buildShahkarEnvelope(
nationalCode,
mobileNo,
inquiryConfig.username,
inquiryConfig.password,
),
{
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"http://tempuri.org/IShahkarInq/ShahkarInquery"',
},
timeout: this.hamtaConfig.timeout,
responseType: 'text',
},
);
const result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
return {
raw: {
nationalCode,
mobileNo,
result,
soap: response.data,
},
};
} catch (error) {
if (error instanceof AxiosError) {
throw this.formatProviderError(
error.message,
String(error.response?.status ?? 'NETWORK_ERROR'),
);
}
throw error;
}
}
private buildShahkarEnvelope(
nationalCode: string,
mobileNo: string,
username: string,
password: string,
): string {
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>
<ShahkarInquery xmlns="http://tempuri.org/">
<NationalCod>${this.escapeXml(nationalCode)}</NationalCod>
<MobileNo>${this.escapeXml(mobileNo)}</MobileNo>
<Username>${this.escapeXml(username)}</Username>
<Password>${this.escapeXml(password)}</Password>
</ShahkarInquery>
</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 getRequiredString(value: unknown, fieldName: string): string {
if (typeof value !== 'string' || !value.trim()) {
throw this.formatProviderError(undefined, undefined, `${fieldName} is required`);
}
return value.trim();
}
private escapeXml(value: string): string {
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, '&apos;');
}
private decodeXml(value: string): string {
return value
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/&/g, '&');
}
}