forked from Shared/esg
Merge pull request 'final update on moallm client' (#5) from s.hajizadeh/esg:main into main
Reviewed-on: Shared/esg#5
This commit is contained in:
@@ -18,11 +18,11 @@ export class BaseInquiryResponseDto<T = Record<string, unknown>> {
|
||||
@ApiPropertyOptional({ example: 'Inquiry completed successfully' })
|
||||
message?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
data?: T;
|
||||
@ApiPropertyOptional({ nullable: true })
|
||||
data?: T | null;
|
||||
|
||||
@ApiPropertyOptional({ type: NormalizedErrorDto })
|
||||
error?: NormalizedErrorDto;
|
||||
@ApiPropertyOptional({ type: NormalizedErrorDto, nullable: true })
|
||||
error?: NormalizedErrorDto | null;
|
||||
|
||||
@ApiProperty({ example: 342, description: 'Duration in milliseconds' })
|
||||
duration!: number;
|
||||
|
||||
@@ -17,6 +17,12 @@ export class NormalizedErrorDto {
|
||||
@ApiPropertyOptional({ example: '503' })
|
||||
providerCode?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'MjFiNDQwYjAtOTdkMy00YjFkLWEzN2UtMWEyOTk2NDE0MjRm',
|
||||
description: 'Upstream provider reference code when available',
|
||||
})
|
||||
providerTrackingCode?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: [{ field: 'sheba', constraints: ['sheba must start with IR and contain 24 digits'] }],
|
||||
description: 'Field-level validation errors when code is VALIDATION_ERROR',
|
||||
|
||||
@@ -6,6 +6,7 @@ export class InquiryException extends HttpException {
|
||||
message: string,
|
||||
public readonly normalizedError: NormalizedErrorDto,
|
||||
status: HttpStatus = HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
public readonly provider?: string,
|
||||
) {
|
||||
super({ message, error: normalizedError }, status);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto';
|
||||
import { NormalizedErrorDto } from '../dto/normalized-error.dto';
|
||||
import { buildInquiryResponse } from '../helpers/inquiry-response.helper';
|
||||
import { ProviderException } from '../exceptions/provider.exception';
|
||||
import { isNormalizedError } from '../helpers/validation-error.helper';
|
||||
|
||||
@@ -39,14 +39,14 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
||||
exception instanceof Error ? exception.stack : undefined,
|
||||
);
|
||||
|
||||
const body: BaseInquiryResponseDto = {
|
||||
const body = buildInquiryResponse({
|
||||
success: false,
|
||||
provider: 'GATEWAY',
|
||||
trackingCode,
|
||||
message: normalizedError.message,
|
||||
error: normalizedError,
|
||||
duration: 0,
|
||||
};
|
||||
error: normalizedError,
|
||||
});
|
||||
|
||||
response.status(status).json(body);
|
||||
}
|
||||
|
||||
72
src/common/helpers/centinsur-response.helper.ts
Normal file
72
src/common/helpers/centinsur-response.helper.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { InquiryType } from '../enums/inquiry-type.enum';
|
||||
|
||||
export interface CentInsurApiResponse {
|
||||
IsSucceed?: boolean;
|
||||
Result?: {
|
||||
Result?: boolean;
|
||||
ErrorMessage?: string | null;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
TrackingCode?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
export interface CentInsurProviderError {
|
||||
message: string;
|
||||
code: string;
|
||||
providerTrackingCode?: string;
|
||||
}
|
||||
|
||||
function getNoMatchMessage(inquiryType?: InquiryType): string {
|
||||
switch (inquiryType) {
|
||||
case InquiryType.REAL_ESTATE:
|
||||
return 'No property ownership found for the provided national code and postal code';
|
||||
default:
|
||||
return 'Inquiry returned no matching result';
|
||||
}
|
||||
}
|
||||
|
||||
export function getCentInsurProviderError(
|
||||
body: CentInsurApiResponse,
|
||||
inquiryType?: InquiryType,
|
||||
): CentInsurProviderError | null {
|
||||
if (!('IsSucceed' in body)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const providerTrackingCode = body.TrackingCode?.trim() || undefined;
|
||||
|
||||
if (body.IsSucceed === false) {
|
||||
return {
|
||||
message: body.Result?.ErrorMessage?.trim() || 'Request failed',
|
||||
code: 'API_ERROR',
|
||||
providerTrackingCode,
|
||||
};
|
||||
}
|
||||
|
||||
if (body.Result?.Result === false) {
|
||||
return {
|
||||
message: body.Result?.ErrorMessage?.trim() || getNoMatchMessage(inquiryType),
|
||||
code: 'INQUIRY_NO_MATCH',
|
||||
providerTrackingCode,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function describeCentInsurResponse(body: CentInsurApiResponse): string {
|
||||
const innerResult =
|
||||
body.Result && 'Result' in body.Result ? String(body.Result.Result) : 'undefined';
|
||||
|
||||
return [
|
||||
`IsSucceed=${String(body.IsSucceed)}`,
|
||||
`Result.Result=${innerResult}`,
|
||||
`ErrorMessage=${body.Result?.ErrorMessage ?? 'none'}`,
|
||||
body.TrackingCode ? `TrackingCode=${body.TrackingCode}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' | ');
|
||||
}
|
||||
32
src/common/helpers/inquiry-response.helper.ts
Normal file
32
src/common/helpers/inquiry-response.helper.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto';
|
||||
import { NormalizedErrorDto } from '../dto/normalized-error.dto';
|
||||
|
||||
export function buildInquiryResponse<T = Record<string, unknown>>(params: {
|
||||
success: boolean;
|
||||
provider: string;
|
||||
trackingCode: string;
|
||||
message: string;
|
||||
duration: number;
|
||||
data?: T | null;
|
||||
error?: NormalizedErrorDto | null;
|
||||
}): BaseInquiryResponseDto<T> {
|
||||
return {
|
||||
success: params.success,
|
||||
provider: params.provider,
|
||||
trackingCode: params.trackingCode,
|
||||
message: params.message,
|
||||
duration: params.duration,
|
||||
data: params.success ? (params.data ?? null) : null,
|
||||
error: params.success ? null : (params.error ?? null),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeInquiryResponse<T = Record<string, unknown>>(
|
||||
response: BaseInquiryResponseDto<T>,
|
||||
): BaseInquiryResponseDto<T> {
|
||||
return {
|
||||
...response,
|
||||
data: response.success ? (response.data ?? null) : null,
|
||||
error: response.success ? null : (response.error ?? null),
|
||||
};
|
||||
}
|
||||
124
src/common/helpers/shahkar-response.helper.ts
Normal file
124
src/common/helpers/shahkar-response.helper.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
function decodeXml(value: string): string {
|
||||
return value
|
||||
.replace(/'/g, "'")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
.replace(/&/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());
|
||||
}
|
||||
|
||||
export interface ShahkarInquiryFields {
|
||||
response?: string;
|
||||
result?: string;
|
||||
comment?: string;
|
||||
requestId?: string;
|
||||
id?: string;
|
||||
ErrorNams?: string;
|
||||
}
|
||||
|
||||
export function parseShahkarInqueryResult(soapXml: string): ShahkarInquiryFields {
|
||||
const blockMatch = soapXml.match(
|
||||
/<(?:[\w]+:)?ShahkarInqueryResult[^>]*>([\s\S]*?)<\/(?:[\w]+:)?ShahkarInqueryResult>/i,
|
||||
);
|
||||
|
||||
if (!blockMatch) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const fields: Record<string, string> = {};
|
||||
const tagRegex = /<(?:[\w]+:)?(\w+)(?:[^>]*)>([\s\S]*?)<\/(?:[\w]+:)?\1>/gi;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = tagRegex.exec(blockMatch[1])) !== null) {
|
||||
fields[match[1]] = normalizeSoapTextValue(match[2]);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function normalizeShahkarFields(raw: Record<string, unknown>): ShahkarInquiryFields {
|
||||
const readString = (value: unknown): string | undefined => {
|
||||
if (value === null || value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const text = String(value).trim();
|
||||
return text.length > 0 ? text : undefined;
|
||||
};
|
||||
|
||||
return {
|
||||
response: readString(raw.response ?? raw.Response),
|
||||
result: readString(raw.result ?? raw.Result),
|
||||
comment: readString(raw.comment ?? raw.Comment),
|
||||
requestId: readString(raw.requestId ?? raw.RequestId),
|
||||
id: readString(raw.id ?? raw.Id),
|
||||
ErrorNams: readString(raw.ErrorNams ?? raw.errorNams),
|
||||
};
|
||||
}
|
||||
|
||||
export function isShahkarSuccess(fields: ShahkarInquiryFields): boolean {
|
||||
const responseCode = fields.response?.trim();
|
||||
const resultText = fields.result?.trim().toUpperCase() ?? '';
|
||||
|
||||
return responseCode === '200' && resultText.startsWith('OK');
|
||||
}
|
||||
|
||||
export function getShahkarProviderError(
|
||||
fields: ShahkarInquiryFields,
|
||||
): { message: string; code: string; providerCode?: string; providerTrackingCode?: string } | null {
|
||||
if (isShahkarSuccess(fields)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const comment = fields.comment?.trim();
|
||||
const errorNams = fields.ErrorNams?.trim();
|
||||
const resultText = fields.result?.trim();
|
||||
const responseCode = fields.response?.trim();
|
||||
const providerTrackingCode = fields.requestId?.trim() || undefined;
|
||||
|
||||
if (resultText === 'NotIdentifiedException' || responseCode === '600') {
|
||||
return {
|
||||
message:
|
||||
comment ||
|
||||
'No match found between the provided national code and mobile number',
|
||||
code: 'INQUIRY_NO_MATCH',
|
||||
providerCode: resultText ?? responseCode,
|
||||
providerTrackingCode,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message: comment || errorNams || resultText || 'Shahkar inquiry failed',
|
||||
code: 'PROVIDER_ERROR',
|
||||
providerCode: resultText ?? responseCode,
|
||||
providerTrackingCode,
|
||||
};
|
||||
}
|
||||
|
||||
export function describeShahkarResponse(fields: ShahkarInquiryFields): string {
|
||||
return [
|
||||
fields.response ? `response=${fields.response}` : null,
|
||||
fields.result ? `result=${fields.result}` : null,
|
||||
fields.comment ? `comment=${fields.comment}` : null,
|
||||
fields.requestId ? `requestId=${fields.requestId}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' | ');
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto';
|
||||
import { RequestLogger } from '../helpers/request-logger.helper';
|
||||
import { formatHttpExceptionMessage } from '../helpers/validation-error.helper';
|
||||
|
||||
@@ -25,11 +26,21 @@ export class LoggingInterceptor implements NestInterceptor {
|
||||
|
||||
return next.handle().pipe(
|
||||
tap({
|
||||
next: () => {
|
||||
this.requestLogger.logSuccess(
|
||||
{ requestId: req.requestId ?? 'unknown' },
|
||||
`${req.method} ${req.url} completed in ${Date.now() - start}ms`,
|
||||
);
|
||||
next: (body: unknown) => {
|
||||
const durationMs = Date.now() - start;
|
||||
const requestId = req.requestId ?? 'unknown';
|
||||
const summary = `${req.method} ${req.url} completed in ${durationMs}ms`;
|
||||
|
||||
if (this.isBusinessFailure(body)) {
|
||||
const response = body as BaseInquiryResponseDto;
|
||||
this.requestLogger.logFailure(
|
||||
{ requestId, durationMs },
|
||||
`${summary} | businessSuccess=false | ${response.message ?? response.error?.code ?? 'failed'}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.requestLogger.logSuccess({ requestId }, summary);
|
||||
},
|
||||
error: (err: unknown) => {
|
||||
const detail = formatHttpExceptionMessage(err);
|
||||
@@ -42,4 +53,13 @@ export class LoggingInterceptor implements NestInterceptor {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private isBusinessFailure(body: unknown): body is BaseInquiryResponseDto {
|
||||
return Boolean(
|
||||
body &&
|
||||
typeof body === 'object' &&
|
||||
'success' in body &&
|
||||
(body as BaseInquiryResponseDto).success === false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto';
|
||||
import { normalizeInquiryResponse } from '../helpers/inquiry-response.helper';
|
||||
|
||||
/**
|
||||
* Ensures inquiry endpoints always emit BaseInquiryResponseDto shape.
|
||||
@@ -15,7 +16,7 @@ import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto';
|
||||
export class ResponseTransformInterceptor implements NestInterceptor {
|
||||
intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
return next.handle().pipe(
|
||||
map((data: BaseInquiryResponseDto) => data),
|
||||
map((data: BaseInquiryResponseDto) => normalizeInquiryResponse(data)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,23 @@ export class SayahRequestDto {
|
||||
@Matches(/^\d{10}$/, { message: 'nationalCode must be exactly 10 digits' })
|
||||
nationalCode!: string;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true })
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
example: '',
|
||||
nullable: true,
|
||||
description: 'Legal entity ID; use empty string for natural persons',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Transform(({ value }: { value: unknown }) => {
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
})
|
||||
legalId?: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ProviderName } from '../common/enums/provider-name.enum';
|
||||
import { BaseInquiryResponseDto } from '../common/dto/base-inquiry-response.dto';
|
||||
import { NormalizedErrorDto } from '../common/dto/normalized-error.dto';
|
||||
import { generateTrackingCode } from '../common/helpers/tracking-code.helper';
|
||||
import { buildInquiryResponse } from '../common/helpers/inquiry-response.helper';
|
||||
import { InquiryException } from '../common/exceptions/inquiry.exception';
|
||||
import { InquiryLogService } from '../logging/inquiry-log.service';
|
||||
import {
|
||||
@@ -54,19 +55,20 @@ export class InquiryService {
|
||||
trackingCode,
|
||||
);
|
||||
|
||||
const response = this.buildSuccessResponse(
|
||||
result.data,
|
||||
result.provider,
|
||||
const response = buildInquiryResponse({
|
||||
success: true,
|
||||
provider: result.provider,
|
||||
trackingCode,
|
||||
result.duration,
|
||||
`${this.getInquiryLabel(inquiryType)} completed successfully`,
|
||||
);
|
||||
message: `${this.getInquiryLabel(inquiryType)} completed successfully`,
|
||||
duration: result.duration,
|
||||
data: this.toResponseData(result.data),
|
||||
});
|
||||
|
||||
await this.persistLog({
|
||||
inquiryType,
|
||||
provider: result.provider,
|
||||
requestPayload: payload,
|
||||
responsePayload: response.data,
|
||||
responsePayload: response.data ?? undefined,
|
||||
status: InquiryStatus.SUCCESS,
|
||||
duration: result.duration,
|
||||
requestId,
|
||||
@@ -76,20 +78,20 @@ export class InquiryService {
|
||||
return response;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
const normalized = this.extractError(error);
|
||||
const { normalized, provider } = this.extractFailure(error);
|
||||
|
||||
const response: BaseInquiryResponseDto<Record<string, unknown>> = {
|
||||
const response = buildInquiryResponse({
|
||||
success: false,
|
||||
provider: 'GATEWAY',
|
||||
provider: provider ?? 'GATEWAY',
|
||||
trackingCode,
|
||||
message: normalized.message,
|
||||
error: normalized,
|
||||
duration,
|
||||
};
|
||||
error: normalized,
|
||||
});
|
||||
|
||||
await this.persistLog({
|
||||
inquiryType,
|
||||
provider: ProviderName.HAMTA,
|
||||
provider: (provider as ProviderName | undefined) ?? ProviderName.HAMTA,
|
||||
requestPayload: payload,
|
||||
status: InquiryStatus.FAILURE,
|
||||
duration,
|
||||
@@ -114,23 +116,6 @@ export class InquiryService {
|
||||
});
|
||||
}
|
||||
|
||||
private buildSuccessResponse(
|
||||
result: unknown,
|
||||
provider: string,
|
||||
trackingCode: string,
|
||||
duration: number,
|
||||
message: string,
|
||||
): BaseInquiryResponseDto<Record<string, unknown>> {
|
||||
return {
|
||||
success: true,
|
||||
provider,
|
||||
trackingCode,
|
||||
message,
|
||||
duration,
|
||||
data: this.toResponseData(result),
|
||||
};
|
||||
}
|
||||
|
||||
private toResponseData(result: unknown): Record<string, unknown> {
|
||||
if (this.isPersonInquiryResult(result)) {
|
||||
const raw =
|
||||
@@ -169,16 +154,23 @@ export class InquiryService {
|
||||
return inquiryType.replace(/_INQUIRY$/, '').toLowerCase().replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
private extractError(error: unknown): NormalizedErrorDto {
|
||||
private extractFailure(error: unknown): {
|
||||
normalized: NormalizedErrorDto;
|
||||
provider?: string;
|
||||
} {
|
||||
if (error instanceof InquiryException) {
|
||||
return error.normalizedError;
|
||||
return { normalized: error.normalizedError, provider: error.provider };
|
||||
}
|
||||
if (error && typeof error === 'object' && 'normalizedError' in error) {
|
||||
return (error as { normalizedError: NormalizedErrorDto }).normalizedError;
|
||||
return {
|
||||
normalized: (error as { normalizedError: NormalizedErrorDto }).normalizedError,
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: 'INQUIRY_FAILED',
|
||||
message: error instanceof Error ? error.message : 'Inquiry failed',
|
||||
normalized: {
|
||||
code: 'INQUIRY_FAILED',
|
||||
message: error instanceof Error ? error.message : 'Inquiry failed',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,8 @@ export abstract class BaseProvider<TRequest = unknown, TResponse = unknown>
|
||||
message: partial.message ?? 'Provider request failed',
|
||||
providerMessage: partial.providerMessage,
|
||||
providerCode: partial.providerCode,
|
||||
providerTrackingCode: partial.providerTrackingCode,
|
||||
details: partial.details,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,6 +135,7 @@ export abstract class BaseProvider<TRequest = unknown, TResponse = unknown>
|
||||
providerMessage?: string,
|
||||
providerCode?: string,
|
||||
fallbackMessage = 'Provider request failed',
|
||||
extras?: Pick<NormalizedErrorDto, 'providerTrackingCode' | 'details'>,
|
||||
): Error {
|
||||
const message = providerMessage?.trim() || fallbackMessage;
|
||||
const code = providerCode?.trim() || 'PROVIDER_ERROR';
|
||||
@@ -141,6 +144,7 @@ export abstract class BaseProvider<TRequest = unknown, TResponse = unknown>
|
||||
message,
|
||||
providerMessage: message,
|
||||
providerCode: code,
|
||||
...extras,
|
||||
});
|
||||
const err = new Error(message);
|
||||
(err as Error & { normalizedError: NormalizedErrorDto }).normalizedError = normalized;
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
getSayahProviderError,
|
||||
SayahApiResponse,
|
||||
} from '../../common/helpers/sayah-response.helper';
|
||||
import {
|
||||
describeShahkarResponse,
|
||||
getShahkarProviderError,
|
||||
parseShahkarInqueryResult,
|
||||
} from '../../common/helpers/shahkar-response.helper';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
import { ProviderEnvConfig } from '../../config/configuration';
|
||||
@@ -210,14 +215,22 @@ export class HamtaProvider extends LegacyApiProvider {
|
||||
}),
|
||||
);
|
||||
|
||||
const result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
|
||||
const shahkarFields = parseShahkarInqueryResult(response.data);
|
||||
const providerError = getShahkarProviderError(shahkarFields);
|
||||
if (providerError) {
|
||||
this.nestLogger.warn(
|
||||
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
|
||||
);
|
||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
||||
providerTrackingCode: providerError.providerTrackingCode,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
mobileNo,
|
||||
result,
|
||||
soap: response.data,
|
||||
...shahkarFields,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
getCivilRegistrationProviderError,
|
||||
parseCiiEstelamResult,
|
||||
} from '../../common/helpers/soap-civil-registration.helper';
|
||||
import {
|
||||
getShahkarProviderError,
|
||||
parseShahkarInqueryResult,
|
||||
describeShahkarResponse,
|
||||
} from '../../common/helpers/shahkar-response.helper';
|
||||
import {
|
||||
getSayahProviderError,
|
||||
SayahApiResponse,
|
||||
@@ -218,14 +223,22 @@ export class MoallemProvider extends LegacyApiProvider {
|
||||
}),
|
||||
);
|
||||
|
||||
const result = this.extractSoapValue(response.data, 'ShahkarInqueryResult');
|
||||
const shahkarFields = parseShahkarInqueryResult(response.data);
|
||||
const providerError = getShahkarProviderError(shahkarFields);
|
||||
if (providerError) {
|
||||
this.nestLogger.warn(
|
||||
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
|
||||
);
|
||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
||||
providerTrackingCode: providerError.providerTrackingCode,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
mobileNo,
|
||||
result,
|
||||
soap: response.data,
|
||||
...shahkarFields,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,10 +2,12 @@ import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { mergeOutboundAxiosConfig } from '../../common/helpers/http-client.helper';
|
||||
import { getSayahProviderError, SayahApiResponse } from '../../common/helpers/sayah-response.helper';
|
||||
import {
|
||||
getSayahProviderError,
|
||||
SayahApiResponse,
|
||||
} from '../../common/helpers/sayah-response.helper';
|
||||
describeShahkarResponse,
|
||||
getShahkarProviderError,
|
||||
normalizeShahkarFields,
|
||||
} from '../../common/helpers/shahkar-response.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';
|
||||
@@ -192,11 +194,22 @@ export class ParsianProvider extends BaseProvider<LegacyInquiryPayload, LegacyIn
|
||||
);
|
||||
|
||||
const body = response.data;
|
||||
const shahkarFields = normalizeShahkarFields(body as unknown as Record<string, unknown>);
|
||||
const providerError = getShahkarProviderError(shahkarFields);
|
||||
if (providerError) {
|
||||
this.nestLogger.warn(
|
||||
`SHAHKAR provider business failure | ${describeShahkarResponse(shahkarFields)}`,
|
||||
);
|
||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
||||
providerTrackingCode: providerError.providerTrackingCode,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
raw: {
|
||||
nationalCode,
|
||||
mobileNumber,
|
||||
...body,
|
||||
...shahkarFields,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { AxiosError, AxiosInstance } from 'axios';
|
||||
import { createOutboundAxiosInstance } from '../../common/helpers/http-client.helper';
|
||||
import {
|
||||
CentInsurApiResponse,
|
||||
describeCentInsurResponse,
|
||||
getCentInsurProviderError,
|
||||
} from '../../common/helpers/centinsur-response.helper';
|
||||
import { getSayahProviderError } from '../../common/helpers/sayah-response.helper';
|
||||
import { InquiryType } from '../../common/enums/inquiry-type.enum';
|
||||
import { ProviderName } from '../../common/enums/provider-name.enum';
|
||||
@@ -131,6 +136,12 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
|
||||
const body = response.data;
|
||||
|
||||
if ('IsSucceed' in body) {
|
||||
this.nestLogger.log(
|
||||
`${inquiryType} provider response | http=${response.status} | ${describeCentInsurResponse(body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Handle Sayah/Sheba API format (ReturnValue/HasError)
|
||||
if ('ReturnValue' in body || 'HasError' in body) {
|
||||
const providerError = getSayahProviderError(body);
|
||||
@@ -142,11 +153,14 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
|
||||
// Handle CentInsur API format (IsSucceed/Result)
|
||||
if ('IsSucceed' in body) {
|
||||
if (!body.IsSucceed) {
|
||||
throw this.formatProviderError(
|
||||
body.Result?.ErrorMessage ?? 'Request failed',
|
||||
'API_ERROR',
|
||||
const providerError = getCentInsurProviderError(body, inquiryType);
|
||||
if (providerError) {
|
||||
this.nestLogger.warn(
|
||||
`${inquiryType} provider business failure | ${describeCentInsurResponse(body)}`,
|
||||
);
|
||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
||||
providerTrackingCode: providerError.providerTrackingCode,
|
||||
});
|
||||
}
|
||||
return { raw: body };
|
||||
}
|
||||
@@ -172,11 +186,13 @@ export abstract class LegacyApiProvider extends BaseProvider<
|
||||
}
|
||||
|
||||
// Check for CentInsur format error
|
||||
if (data && 'IsSucceed' in data && !data.IsSucceed) {
|
||||
throw this.formatProviderError(
|
||||
data.Result?.ErrorMessage ?? error.message,
|
||||
String(error.response?.status ?? 'API_ERROR'),
|
||||
);
|
||||
if (data && 'IsSucceed' in data) {
|
||||
const providerError = getCentInsurProviderError(data, inquiryType);
|
||||
if (providerError) {
|
||||
throw this.formatProviderError(providerError.message, providerError.code, undefined, {
|
||||
providerTrackingCode: providerError.providerTrackingCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw this.formatProviderError(
|
||||
|
||||
@@ -71,9 +71,10 @@ export class ProviderOrchestratorService {
|
||||
const lastError = errors[errors.length - 1]!;
|
||||
|
||||
if (providers.length === 1) {
|
||||
throw new InquiryException(lastError.message, lastError);
|
||||
throw new InquiryException(lastError.message, lastError, undefined, providers[0]!.name);
|
||||
}
|
||||
|
||||
const lastProvider = providers[providers.length - 1]!.name;
|
||||
throw new InquiryException(
|
||||
errors.map((error) => error.message).join('; '),
|
||||
{
|
||||
@@ -82,6 +83,8 @@ export class ProviderOrchestratorService {
|
||||
providerMessage: lastError.providerMessage ?? lastError.message,
|
||||
providerCode: lastError.providerCode ?? lastError.code,
|
||||
},
|
||||
undefined,
|
||||
lastProvider,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user