From 1b7f6785384a3ec4dfb690fd0aa8fa5fd3932c1d Mon Sep 17 00:00:00 2001 From: "s.hajizadeh" Date: Mon, 15 Jun 2026 10:23:00 +0330 Subject: [PATCH] final update on moallm client --- src/common/dto/base-inquiry-response.dto.ts | 8 +- src/common/dto/normalized-error.dto.ts | 6 + src/common/exceptions/inquiry.exception.ts | 1 + src/common/filters/all-exceptions.filter.ts | 8 +- .../helpers/centinsur-response.helper.ts | 72 ++++++++++ src/common/helpers/inquiry-response.helper.ts | 32 +++++ src/common/helpers/shahkar-response.helper.ts | 124 ++++++++++++++++++ .../interceptors/logging.interceptor.ts | 30 ++++- .../response-transform.interceptor.ts | 3 +- src/inquiry/dto/sayah-request.dto.ts | 16 ++- src/inquiry/inquiry.service.ts | 62 ++++----- src/providers/base/base-provider.abstract.ts | 4 + .../implementations/hamta.provider.ts | 19 ++- .../implementations/moallem.provider.ts | 19 ++- .../implementations/parsian.provider.ts | 21 ++- .../shared/legacy-api.provider.abstract.ts | 34 +++-- .../strategy/provider-orchestrator.service.ts | 5 +- 17 files changed, 394 insertions(+), 70 deletions(-) create mode 100644 src/common/helpers/centinsur-response.helper.ts create mode 100644 src/common/helpers/inquiry-response.helper.ts create mode 100644 src/common/helpers/shahkar-response.helper.ts diff --git a/src/common/dto/base-inquiry-response.dto.ts b/src/common/dto/base-inquiry-response.dto.ts index 2816df4..3684e2f 100644 --- a/src/common/dto/base-inquiry-response.dto.ts +++ b/src/common/dto/base-inquiry-response.dto.ts @@ -18,11 +18,11 @@ export class BaseInquiryResponseDto> { @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; diff --git a/src/common/dto/normalized-error.dto.ts b/src/common/dto/normalized-error.dto.ts index 3e46f48..816dc6b 100644 --- a/src/common/dto/normalized-error.dto.ts +++ b/src/common/dto/normalized-error.dto.ts @@ -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', diff --git a/src/common/exceptions/inquiry.exception.ts b/src/common/exceptions/inquiry.exception.ts index 1f8d678..7c55dd2 100644 --- a/src/common/exceptions/inquiry.exception.ts +++ b/src/common/exceptions/inquiry.exception.ts @@ -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); } diff --git a/src/common/filters/all-exceptions.filter.ts b/src/common/filters/all-exceptions.filter.ts index a427f52..c87a744 100644 --- a/src/common/filters/all-exceptions.filter.ts +++ b/src/common/filters/all-exceptions.filter.ts @@ -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); } diff --git a/src/common/helpers/centinsur-response.helper.ts b/src/common/helpers/centinsur-response.helper.ts new file mode 100644 index 0000000..8a307e2 --- /dev/null +++ b/src/common/helpers/centinsur-response.helper.ts @@ -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(' | '); +} diff --git a/src/common/helpers/inquiry-response.helper.ts b/src/common/helpers/inquiry-response.helper.ts new file mode 100644 index 0000000..41db395 --- /dev/null +++ b/src/common/helpers/inquiry-response.helper.ts @@ -0,0 +1,32 @@ +import { BaseInquiryResponseDto } from '../dto/base-inquiry-response.dto'; +import { NormalizedErrorDto } from '../dto/normalized-error.dto'; + +export function buildInquiryResponse>(params: { + success: boolean; + provider: string; + trackingCode: string; + message: string; + duration: number; + data?: T | null; + error?: NormalizedErrorDto | null; +}): BaseInquiryResponseDto { + 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>( + response: BaseInquiryResponseDto, +): BaseInquiryResponseDto { + return { + ...response, + data: response.success ? (response.data ?? null) : null, + error: response.success ? null : (response.error ?? null), + }; +} diff --git a/src/common/helpers/shahkar-response.helper.ts b/src/common/helpers/shahkar-response.helper.ts new file mode 100644 index 0000000..5e22bef --- /dev/null +++ b/src/common/helpers/shahkar-response.helper.ts @@ -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 = {}; + 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): 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(' | '); +} diff --git a/src/common/interceptors/logging.interceptor.ts b/src/common/interceptors/logging.interceptor.ts index 3bc38e8..b6fc357 100644 --- a/src/common/interceptors/logging.interceptor.ts +++ b/src/common/interceptors/logging.interceptor.ts @@ -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, + ); + } } diff --git a/src/common/interceptors/response-transform.interceptor.ts b/src/common/interceptors/response-transform.interceptor.ts index 449ed49..2761bdc 100644 --- a/src/common/interceptors/response-transform.interceptor.ts +++ b/src/common/interceptors/response-transform.interceptor.ts @@ -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 { return next.handle().pipe( - map((data: BaseInquiryResponseDto) => data), + map((data: BaseInquiryResponseDto) => normalizeInquiryResponse(data)), ); } } diff --git a/src/inquiry/dto/sayah-request.dto.ts b/src/inquiry/dto/sayah-request.dto.ts index 25a47c0..d6cf9d4 100644 --- a/src/inquiry/dto/sayah-request.dto.ts +++ b/src/inquiry/dto/sayah-request.dto.ts @@ -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({ diff --git a/src/inquiry/inquiry.service.ts b/src/inquiry/inquiry.service.ts index b24bb25..f5186e9 100644 --- a/src/inquiry/inquiry.service.ts +++ b/src/inquiry/inquiry.service.ts @@ -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> = { + 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> { - return { - success: true, - provider, - trackingCode, - message, - duration, - data: this.toResponseData(result), - }; - } - private toResponseData(result: unknown): Record { 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', + }, }; } diff --git a/src/providers/base/base-provider.abstract.ts b/src/providers/base/base-provider.abstract.ts index b4aa443..470273c 100644 --- a/src/providers/base/base-provider.abstract.ts +++ b/src/providers/base/base-provider.abstract.ts @@ -126,6 +126,8 @@ export abstract class BaseProvider 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 providerMessage?: string, providerCode?: string, fallbackMessage = 'Provider request failed', + extras?: Pick, ): Error { const message = providerMessage?.trim() || fallbackMessage; const code = providerCode?.trim() || 'PROVIDER_ERROR'; @@ -141,6 +144,7 @@ export abstract class BaseProvider message, providerMessage: message, providerCode: code, + ...extras, }); const err = new Error(message); (err as Error & { normalizedError: NormalizedErrorDto }).normalizedError = normalized; diff --git a/src/providers/implementations/hamta.provider.ts b/src/providers/implementations/hamta.provider.ts index 617cb76..d3f1293 100644 --- a/src/providers/implementations/hamta.provider.ts +++ b/src/providers/implementations/hamta.provider.ts @@ -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) { diff --git a/src/providers/implementations/moallem.provider.ts b/src/providers/implementations/moallem.provider.ts index 7045b2c..ac3028e 100644 --- a/src/providers/implementations/moallem.provider.ts +++ b/src/providers/implementations/moallem.provider.ts @@ -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) { diff --git a/src/providers/implementations/parsian.provider.ts b/src/providers/implementations/parsian.provider.ts index 8a04d09..a75027a 100644 --- a/src/providers/implementations/parsian.provider.ts +++ b/src/providers/implementations/parsian.provider.ts @@ -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); + 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) { diff --git a/src/providers/shared/legacy-api.provider.abstract.ts b/src/providers/shared/legacy-api.provider.abstract.ts index f2a192e..cee4d47 100644 --- a/src/providers/shared/legacy-api.provider.abstract.ts +++ b/src/providers/shared/legacy-api.provider.abstract.ts @@ -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( diff --git a/src/providers/strategy/provider-orchestrator.service.ts b/src/providers/strategy/provider-orchestrator.service.ts index 8104066..aada18d 100644 --- a/src/providers/strategy/provider-orchestrator.service.ts +++ b/src/providers/strategy/provider-orchestrator.service.ts @@ -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, ); }