forked from Yara724/api
248 lines
9.2 KiB
TypeScript
248 lines
9.2 KiB
TypeScript
export type InquiryErrorContext =
|
||
| "thirdPartyPlate"
|
||
| "thirdPartyVin"
|
||
| "carBodyPlate"
|
||
| "carBodyVin"
|
||
| "personalIdentity"
|
||
| "drivingLicense"
|
||
| "carOwnership"
|
||
| "sheba"
|
||
| "generic";
|
||
|
||
type UnknownRecord = Record<string, unknown>;
|
||
|
||
const NOT_FOUND_MESSAGES: Record<InquiryErrorContext, string> = {
|
||
thirdPartyPlate: "بیمهنامه شخص ثالثی مطابق پلاک و کد ملی واردشده یافت نشد.",
|
||
thirdPartyVin:
|
||
"بیمهنامه شخص ثالثی مطابق شماره شاسی (VIN) و کد ملی واردشده یافت نشد.",
|
||
carBodyPlate: "بیمهنامه بدنه فعالی مطابق پلاک و کد ملی واردشده یافت نشد.",
|
||
carBodyVin:
|
||
"بیمهنامه بدنه فعالی مطابق شماره شاسی (VIN) و کد ملی واردشده یافت نشد.",
|
||
personalIdentity: "اطلاعات هویتی مطابق کد ملی و تاریخ تولد واردشده یافت نشد.",
|
||
drivingLicense:
|
||
"گواهینامهای مطابق کد ملی و شماره گواهینامه واردشده یافت نشد.",
|
||
carOwnership: "مالکیتی مطابق پلاک و کد ملی واردشده یافت نشد.",
|
||
sheba: "اطلاعاتی مطابق شماره شبا و کد ملی واردشده یافت نشد.",
|
||
generic: "موردی مطابق اطلاعات واردشده یافت نشد.",
|
||
};
|
||
|
||
const INVALID_MESSAGES: Record<InquiryErrorContext, string> = {
|
||
thirdPartyPlate: "پلاک یا کد ملی واردشده برای استعلام شخص ثالث معتبر نیست.",
|
||
thirdPartyVin:
|
||
"شماره شاسی (VIN) یا کد ملی واردشده برای استعلام شخص ثالث معتبر نیست.",
|
||
carBodyPlate: "پلاک یا کد ملی واردشده برای استعلام بیمه بدنه معتبر نیست.",
|
||
carBodyVin:
|
||
"شماره شاسی (VIN) یا کد ملی واردشده برای استعلام بیمه بدنه معتبر نیست.",
|
||
personalIdentity: "کد ملی یا تاریخ تولد واردشده معتبر نیست.",
|
||
drivingLicense: "کد ملی یا شماره گواهینامه واردشده معتبر نیست.",
|
||
carOwnership: "پلاک یا کد ملی واردشده برای استعلام مالکیت معتبر نیست.",
|
||
sheba: "شماره شبا یا کد ملی واردشده معتبر نیست.",
|
||
generic: "اطلاعات ارسالشده برای استعلام معتبر نیست.",
|
||
};
|
||
|
||
const asRecord = (value: unknown): UnknownRecord | undefined =>
|
||
value && typeof value === "object" && !Array.isArray(value)
|
||
? (value as UnknownRecord)
|
||
: undefined;
|
||
|
||
const cleanMessage = (value: unknown): string => {
|
||
if (typeof value === "string") return value.trim();
|
||
if (Array.isArray(value)) {
|
||
return value
|
||
.filter((item): item is string => typeof item === "string")
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
.join("، ");
|
||
}
|
||
return "";
|
||
};
|
||
|
||
function errorRecords(error: unknown): UnknownRecord[] {
|
||
const root = asRecord(error);
|
||
if (!root) return [];
|
||
|
||
const response = asRecord(root.response);
|
||
const responseData = asRecord(response?.data);
|
||
const data = asRecord(root.data);
|
||
const nestedError = asRecord(root.Error) ?? asRecord(root.error);
|
||
const responseError =
|
||
asRecord(responseData?.Error) ?? asRecord(responseData?.error);
|
||
|
||
return [root, responseData, data, nestedError, responseError].filter(
|
||
(item): item is UnknownRecord => !!item,
|
||
);
|
||
}
|
||
|
||
export function inquiryErrorStatus(error: unknown): number | undefined {
|
||
for (const record of errorRecords(error)) {
|
||
const response = asRecord(record.response);
|
||
const value =
|
||
record.statusCode ?? record.status ?? response?.status ?? record.code;
|
||
const parsed = Number(value);
|
||
if (Number.isFinite(parsed) && parsed >= 100 && parsed <= 599) {
|
||
return parsed;
|
||
}
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
export function extractInquiryProviderMessage(error: unknown): string {
|
||
const records = errorRecords(error);
|
||
|
||
// The normalized ESG/Parsian envelope carries the safe user-facing text in
|
||
// messageFa while `message` and `providerMessage` may remain technical.
|
||
// Search every envelope level for that explicit Persian field before
|
||
// considering generic message fields on an outer object.
|
||
for (const record of records) {
|
||
for (const key of [
|
||
"messageFa",
|
||
"MessageFa",
|
||
"messageFA",
|
||
"persianMessage",
|
||
] as const) {
|
||
const message = cleanMessage(record[key]);
|
||
if (message) return message;
|
||
}
|
||
}
|
||
|
||
for (const record of records) {
|
||
for (const key of ["message", "Message", "detail", "title"] as const) {
|
||
const message = cleanMessage(record[key]);
|
||
if (message) return message;
|
||
}
|
||
const scalarData = cleanMessage(record.data);
|
||
if (scalarData) return scalarData;
|
||
}
|
||
return error instanceof Error ? error.message.trim() : cleanMessage(error);
|
||
}
|
||
|
||
export function isInquiryFailurePayload(value: unknown): boolean {
|
||
const root = asRecord(value);
|
||
if (!root) return false;
|
||
|
||
return (
|
||
root.success === false ||
|
||
root.isSuccess === false ||
|
||
root.IsSuccess === false ||
|
||
root.IsSucceed === false ||
|
||
root.ReturnValue === false ||
|
||
root.HasError === true ||
|
||
root.hasError === true ||
|
||
root.Error != null ||
|
||
root.error != null
|
||
);
|
||
}
|
||
|
||
export function isInquiryTimeout(error: unknown): boolean {
|
||
const root = asRecord(error);
|
||
const code = String(root?.code ?? "").toUpperCase();
|
||
const message = extractInquiryProviderMessage(error);
|
||
return (
|
||
["ECONNABORTED", "ETIMEDOUT", "ESOCKETTIMEDOUT"].includes(code) ||
|
||
/timeout|timed out|مهلت|زمان.*پایان/i.test(message)
|
||
);
|
||
}
|
||
|
||
const hasPersian = (value: string): boolean => /[\u0600-\u06ff]/.test(value);
|
||
|
||
const isNotFound = (error: unknown, message: string): boolean => {
|
||
const codes = errorRecords(error).flatMap((record) =>
|
||
[record.code, record.providerCode]
|
||
.map((code) => String(code ?? "").toUpperCase())
|
||
.filter(Boolean),
|
||
);
|
||
return (
|
||
inquiryErrorStatus(error) === 404 ||
|
||
codes.some((code) =>
|
||
[
|
||
"NOT_FOUND",
|
||
"POLICY_NOT_FOUND",
|
||
"NO_POLICY",
|
||
"RECORD_NOT_FOUND",
|
||
"INQUIRY_NO_MATCH",
|
||
].includes(code),
|
||
) ||
|
||
/\bnot[ -]?found\b|\bno (?:active |relevant )?(?:record|policy|item)\b|record\.not\.found|موردی یافت نشد|یافت نشد|پیدا نشد|فاقد بیمه(?:| )?نامه/i.test(
|
||
message,
|
||
)
|
||
);
|
||
};
|
||
|
||
const isInvalidInput = (message: string): boolean =>
|
||
/invalid|malformed|required|must contain|bad request|نامعتبر|الزامی|وارد نشده|صحیح نیست/i.test(
|
||
message,
|
||
);
|
||
|
||
const isUnavailable = (error: unknown, message: string): boolean => {
|
||
const root = asRecord(error);
|
||
const code = String(root?.code ?? "").toUpperCase();
|
||
const status = inquiryErrorStatus(error);
|
||
return (
|
||
isInquiryTimeout(error) ||
|
||
["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ERR_NETWORK"].includes(code) ||
|
||
status === 401 ||
|
||
status === 502 ||
|
||
status === 503 ||
|
||
status === 504 ||
|
||
/offline|unavailable|connection|socket|network|authentication|credentials|empty response|در دسترس نیست|عدم دسترسی/i.test(
|
||
message,
|
||
)
|
||
);
|
||
};
|
||
|
||
/**
|
||
* Converts provider and transport failures into a stable, user-facing Persian
|
||
* message. Provider details remain in server logs; raw English or technical
|
||
* messages are never returned to clients.
|
||
*/
|
||
export function getInquiryErrorMessage(
|
||
error: unknown,
|
||
context: InquiryErrorContext = "generic",
|
||
): string {
|
||
const providerMessage = extractInquiryProviderMessage(error);
|
||
|
||
// Persian text supplied by the provider is already the intended client
|
||
// message. Preserve it verbatim instead of replacing it with a local
|
||
// contextual fallback such as "inquiry not found".
|
||
if (providerMessage && hasPersian(providerMessage)) return providerMessage;
|
||
|
||
if (isNotFound(error, providerMessage)) return NOT_FOUND_MESSAGES[context];
|
||
|
||
if (
|
||
context === "carOwnership" &&
|
||
/not the owner|مالک.*نیست/i.test(providerMessage)
|
||
) {
|
||
return "پلاک واردشده متعلق به کد ملی واردشده نیست.";
|
||
}
|
||
if (
|
||
context === "sheba" &&
|
||
/does not match|تطابق ندارد/i.test(providerMessage)
|
||
) {
|
||
return "شماره شبا متعلق به کد ملی واردشده نیست.";
|
||
}
|
||
if (
|
||
context === "drivingLicense" &&
|
||
/not valid|نامعتبر/i.test(providerMessage)
|
||
) {
|
||
return "گواهینامه واردشده معتبر نیست.";
|
||
}
|
||
if (
|
||
/policy insurer does not match|بیمه.*متعلق.*نیست/i.test(providerMessage)
|
||
) {
|
||
return "بیمهنامه یافتشده متعلق به شرکت بیمه این سامانه نیست.";
|
||
}
|
||
|
||
if (isInquiryTimeout(error)) {
|
||
return "زمان پاسخگویی سرویس استعلام به پایان رسید. لطفاً دوباره تلاش کنید.";
|
||
}
|
||
if (isUnavailable(error, providerMessage)) {
|
||
return "سرویس استعلام در دسترس نیست. لطفاً کمی بعد دوباره تلاش کنید.";
|
||
}
|
||
|
||
if (isInvalidInput(providerMessage) || inquiryErrorStatus(error) === 422) {
|
||
return INVALID_MESSAGES[context];
|
||
}
|
||
|
||
return "انجام استعلام با خطا مواجه شد. لطفاً دوباره تلاش کنید.";
|
||
}
|