forked from Yara724/api
658 lines
21 KiB
TypeScript
658 lines
21 KiB
TypeScript
import { HttpService } from "@nestjs/axios";
|
|
import {
|
|
BadGatewayException,
|
|
BadRequestException,
|
|
GatewayTimeoutException,
|
|
Injectable,
|
|
Logger,
|
|
NotFoundException,
|
|
ServiceUnavailableException,
|
|
UnauthorizedException,
|
|
} from "@nestjs/common";
|
|
import axios from "axios";
|
|
import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service";
|
|
import { SandHubModel } from "src/sand-hub/entity/schema/sand-hub.schema";
|
|
import { SandHubDetailDto } from "./dto/sand-hub.dto";
|
|
|
|
@Injectable()
|
|
export class SandHubService {
|
|
private readonly logger = new Logger(SandHubService.name);
|
|
private loginToken: string | null = null;
|
|
private tokenExpiry: Date | null = null;
|
|
|
|
// Tejarat inquiry auth (82.99.202.245:3027)
|
|
private tejaratAccessToken: string | null = null;
|
|
private tejaratTokenExpiry: Date | null = null;
|
|
|
|
constructor(
|
|
private readonly httpService: HttpService,
|
|
private readonly sandHubDbService: SandHubDbService,
|
|
) {}
|
|
|
|
private async getAccessToken(): Promise<string> {
|
|
if (this.loginToken && this.tokenExpiry && this.tokenExpiry > new Date()) {
|
|
return this.loginToken;
|
|
}
|
|
|
|
this.logger.log(
|
|
"SandHub token is missing or expired. Fetching a new one...",
|
|
);
|
|
|
|
try {
|
|
const response = await this.httpService.axiosRef.post(
|
|
process.env.SANDHUB_URL_LOGIN,
|
|
{
|
|
email: process.env.SANDHUB_USERNAME,
|
|
password: process.env.SANDHUB_PASSWORD,
|
|
},
|
|
);
|
|
|
|
const token = response.data.accessToken;
|
|
|
|
if (!token)
|
|
throw new Error("No access token returned from SandHub login");
|
|
|
|
this.loginToken = token;
|
|
this.tokenExpiry = new Date(Date.now() + 55 * 60 * 1000); // 55-minute expiry
|
|
|
|
this.logger.log("Successfully obtained a new SandHub token.");
|
|
|
|
return this.loginToken;
|
|
} catch (er) {
|
|
this.logger.error("Failed to login to SandHub:", er.message);
|
|
this.loginToken = null;
|
|
this.tokenExpiry = null;
|
|
throw new UnauthorizedException("SandHub authentication failed");
|
|
}
|
|
}
|
|
|
|
private async getTejaratAccessToken(): Promise<string> {
|
|
if (
|
|
this.tejaratAccessToken &&
|
|
this.tejaratTokenExpiry &&
|
|
this.tejaratTokenExpiry > new Date()
|
|
) {
|
|
return this.tejaratAccessToken;
|
|
}
|
|
|
|
const baseUrl =
|
|
process.env.TEJARAT_INQUIRY_BASE_URL ?? "http://82.99.202.245:3027";
|
|
const email = process.env.TEJARAT_INQUIRY_EMAIL;
|
|
const password = process.env.TEJARAT_INQUIRY_PASSWORD;
|
|
|
|
if (!email || !password) {
|
|
throw new UnauthorizedException(
|
|
"Tejarat inquiry credentials are not configured (TEJARAT_INQUIRY_EMAIL/TEJARAT_INQUIRY_PASSWORD)",
|
|
);
|
|
}
|
|
|
|
this.logger.log(
|
|
"Tejarat inquiry token is missing or expired. Fetching a new one...",
|
|
);
|
|
|
|
try {
|
|
const response = await this.httpService.axiosRef.post(
|
|
`${baseUrl}/user/login`,
|
|
{ email, password },
|
|
{
|
|
headers: {
|
|
Accept: "*/*",
|
|
"Content-Type": "application/json",
|
|
},
|
|
timeout: 30000,
|
|
},
|
|
);
|
|
|
|
const token = response.data?.accessToken;
|
|
if (!token) {
|
|
throw new Error("No access token returned from Tejarat login");
|
|
}
|
|
|
|
this.tejaratAccessToken = token;
|
|
// No explicit expiry in response; keep a conservative cache window.
|
|
this.tejaratTokenExpiry = new Date(Date.now() + 55 * 60 * 1000);
|
|
|
|
return this.tejaratAccessToken;
|
|
} catch (er) {
|
|
this.logger.error("Failed to login to Tejarat inquiry:", er?.message || er);
|
|
this.tejaratAccessToken = null;
|
|
this.tejaratTokenExpiry = null;
|
|
throw new UnauthorizedException("Tejarat inquiry authentication failed");
|
|
}
|
|
}
|
|
|
|
private async makeTejaratRequest(url: string, payload: any, maxRetries = 2) {
|
|
const INITIAL_DELAY = 500;
|
|
const BACKOFF_FACTOR = 2;
|
|
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
const token = await this.getTejaratAccessToken();
|
|
try {
|
|
const response = await axios.post(url, payload, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
timeout: 30000,
|
|
});
|
|
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
|
return response.data;
|
|
} catch (err: any) {
|
|
const status = err?.response?.status;
|
|
const data = err?.response?.data;
|
|
|
|
this.logger.error(
|
|
`Tejarat request failed (attempt ${attempt + 1}/${maxRetries}) to ${url} with status ${
|
|
status ?? "NO_STATUS"
|
|
}`,
|
|
data ? JSON.stringify(data) : err?.message || err,
|
|
);
|
|
|
|
// If unauthorized, clear token and retry once
|
|
if (status === 401) {
|
|
this.tejaratAccessToken = null;
|
|
this.tejaratTokenExpiry = null;
|
|
}
|
|
|
|
const delay = INITIAL_DELAY * Math.pow(BACKOFF_FACTOR, attempt);
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
if (attempt === maxRetries - 1) {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tejarat block inquiry (replaces SandHub call for V2 flows).
|
|
* Returns both raw + mapped (old-format) response.
|
|
*/
|
|
async getTejaratBlockInquiry(userDetail: SandHubDetailDto): Promise<{
|
|
raw: any;
|
|
mapped: any;
|
|
}> {
|
|
const baseUrl =
|
|
process.env.TEJARAT_INQUIRY_BASE_URL ?? "http://82.99.202.245:3027";
|
|
const requestPayload = {
|
|
leftTwoDigits: String(userDetail.plate.leftDigits),
|
|
serialLetter: String(userDetail.plate.centerAlphabet),
|
|
threeDigits: String(userDetail.plate.centerDigits),
|
|
rightTwoDigits: String(userDetail.plate.ir),
|
|
nationalCode: userDetail.nationalCodeOfInsurer,
|
|
};
|
|
|
|
const requestUrl = `${baseUrl}/block-inquiry-tejarat`;
|
|
// const raw = await this.makeTejaratRequest(requestUrl, requestPayload);
|
|
const raw = {
|
|
"PrntPlcyCmpDocNo": "1403/1143-70591/200/35",
|
|
"MapTypNam": "پرايد هاچ بک -111",
|
|
"MtrNum": "5215907",
|
|
"ShsNum": "NAS431100E5798656",
|
|
"DisFnYrNum": "0",
|
|
"DisLfYrNum": "0",
|
|
"DisPrsnYrNum": "0",
|
|
"DisPrsnYrPrcnt": "0",
|
|
"DisFnYrPrcnt": "0",
|
|
"DisLfYrPrcnt": "0",
|
|
"vin": "IRPC941V2BD798656",
|
|
"MapVehicleSystemName": "ثبت نشده",
|
|
"LfCvrCptl": 0,
|
|
"FnCvrCptl": 0,
|
|
"PrsnCvrCptl": 0,
|
|
"VehicleSystemCode": 1,
|
|
"EdrsJson": "[{\"id\":1,\"Dsc\":\" الحاقيه توضيحات ندارد\"}]",
|
|
"PersonCvrCptl": 12000000000,
|
|
"LifeCvrCptl": 16000000000,
|
|
"FinancialCvrCptl": 4000000000,
|
|
"CarGroupCode": 3,
|
|
"CylCnt": 4,
|
|
"LastCompanyDocumentNumber": "03/1031/2835/1001/662",
|
|
"UsageCode": "8",
|
|
"MapUsageCode": 1,
|
|
"MapUsageName": "شخصي",
|
|
"Plk1": 59,
|
|
"Plk2": 16,
|
|
"Plk3": 419,
|
|
"PlkSrl": 78,
|
|
"PrintEndorsCompanyDocumentNumber": "بيمه نامه الحاقيه ندارد.",
|
|
"EndorseDate": null,
|
|
"InsuranceFullName": null,
|
|
"SystemField": "سايپا",
|
|
"TypeField": "111SE",
|
|
"UsageField": "سواري",
|
|
"MainColorField": "سفيد",
|
|
"SecondColorField": "سفيد شيري",
|
|
"ModelField": "1394",
|
|
"CapacityField": "جمعا 4 نفر",
|
|
"CylinderNumberField": "4",
|
|
"EngineNumberField": "5215907",
|
|
"ChassisNumberField": "NAS431100E5798656",
|
|
"VinNumberField": "IRPC941V2BD798656",
|
|
"InstallDateField": "1403/03/01",
|
|
"AxelNumberField": "2",
|
|
"WheelNumberField": "4",
|
|
"CompanyName": "بیمه سامان",
|
|
"CompanyCode": "15",
|
|
"IssueDate": "1403/04/06",
|
|
"SatrtDate": "1403/04/06",
|
|
"EndDate": "1404/04/06",
|
|
"Thrname": "",
|
|
"EndorseText": null,
|
|
"PolicyHealthLossCount": 0,
|
|
"PolicyFinancialLossCount": 0,
|
|
"PolicyPersonLossCount": 0,
|
|
"Tonage": 0,
|
|
"ThirdPolicyCode": 10502330579,
|
|
"DiscountPersonPercent": null,
|
|
"DiscountThirdPercent": null,
|
|
"SystemCodeCii": 0,
|
|
"SystemNameCii": "ثبت نشده",
|
|
"TypeCodeCii": 12189,
|
|
"TypeNameCii": "پرايد هاچ بک -111",
|
|
"UsageNameCii": "سواري",
|
|
"UsageCodeCii": 1,
|
|
"ModelCii": 1394,
|
|
"damageTypes": [],
|
|
"StatusTypeCode": 1
|
|
}
|
|
const mapped = this.mapNewApiResponseToOldFormat(raw);
|
|
return { raw, mapped };
|
|
}
|
|
|
|
async getSandHubDataFromId(sandHubId) {
|
|
return await this.sandHubDbService.findOneBySandHubId(sandHubId);
|
|
}
|
|
|
|
private async makeSandHubRequest(url: string, payload: any, maxRetries = 3) {
|
|
const token = await this.getAccessToken();
|
|
const INITIAL_DELAY = 1000;
|
|
const BACKOFF_FACTOR = 2;
|
|
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
try {
|
|
const response = await axios.post(url, payload, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
timeout: 50000,
|
|
});
|
|
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
|
return response.data;
|
|
} catch (err) {
|
|
this.handleSandHubError(err, attempt, maxRetries);
|
|
const delay = INITIAL_DELAY * Math.pow(BACKOFF_FACTOR, attempt);
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
throw new BadGatewayException(
|
|
"Failed to fetch data from SandHub after multiple retries",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Maps the new SandHub API response format to the old format expected by the application
|
|
*/
|
|
private mapNewApiResponseToOldFormat(newResponse: any): any {
|
|
if (!newResponse) return newResponse;
|
|
|
|
// Map the new field names to the old field names
|
|
return {
|
|
...newResponse,
|
|
// Company information
|
|
CompanyCode: newResponse.companyId || newResponse.CompanyCode,
|
|
CompanyName: newResponse.companyPersianName || newResponse.CompanyName,
|
|
|
|
// Vehicle information
|
|
MapTypNam: newResponse.vehiclePersianName || newResponse.MapTypNam,
|
|
UsageField: newResponse.persianCarType || newResponse.UsageField || (newResponse.usgCod === "8" ? "شخصی" : "نامشخص"),
|
|
MapUsageName: newResponse.MapUsageName || newResponse.MapUsageName,
|
|
|
|
// Financial coverage
|
|
FinancialCvrCptl: newResponse.financeCoverage || newResponse.FinancialCvrCptl || "0",
|
|
|
|
// Dates
|
|
IssueDate: newResponse.persianStartDate || newResponse.IssueDate,
|
|
EndDate: newResponse.persianEndDate || newResponse.EndDate,
|
|
|
|
// Insurance details
|
|
LastCompanyDocumentNumber: newResponse.lastCompanyInsuranceNumber || newResponse.LastCompanyDocumentNumber || newResponse.insuranceNumber,
|
|
|
|
// Technical details
|
|
MtrNum: newResponse.MtrNum || newResponse.mtrnum,
|
|
ShsNum: newResponse.ShsNum || newResponse.shsNam || newResponse.ChassisNumberField,
|
|
vin: newResponse.vin || newResponse.VinNumberField || newResponse.vin,
|
|
VinNumberField: newResponse.vin || newResponse.VinNumberField,
|
|
ChassisNumberField: newResponse.ChassisNumberField || newResponse.shsNam,
|
|
EngineNumberField: newResponse.EngineNumberField || newResponse.mtrnum,
|
|
ModelField: newResponse.ModelField || newResponse.ModelField,
|
|
|
|
// Discount information
|
|
DisFnYrPrcnt: newResponse.DisFnYrPrcnt || newResponse.disFnYrPrcnt || "0",
|
|
DisLfYrPrcnt: newResponse.DisLfYrPrcnt || newResponse.disLfYrPrcnt || "0",
|
|
DisPrsnYrPrcnt: newResponse.DisPrsnYrPrcnt || newResponse.disPrsnYrPrcnt || "0",
|
|
DisFnYrNum: newResponse.DisFnYrNum || newResponse.disFnYrNum,
|
|
DisLfYrNum: newResponse.DisLfYrNum || newResponse.disLfYrNum,
|
|
DisPrsnYrNum: newResponse.DisPrsnYrNum || newResponse.disPrsnYrNum,
|
|
|
|
// Usage and vehicle system codes
|
|
UsageCode: newResponse.UsageCode || newResponse.usgCod,
|
|
VehicleSystemCode: newResponse.VehicleSystemCode || newResponse.vehSysCod,
|
|
CarGroupCode: newResponse.CarGroupCode || newResponse.carGrpCod,
|
|
|
|
// Policy information
|
|
ThirdPolicyCode: newResponse.ThirdPolicyCode || newResponse.ThirdPolicyCode,
|
|
|
|
// Endorsement data
|
|
EdrsJson: newResponse.EdrsJson || (newResponse.edrSes ? JSON.stringify(newResponse.edrSes) : undefined),
|
|
|
|
// Insurance fullname
|
|
InsuranceFullName: newResponse.InsuranceFullName || newResponse.fullname,
|
|
|
|
// National code
|
|
nationalCode: newResponse.nationalCode || newResponse.ntnlId,
|
|
};
|
|
}
|
|
|
|
async getSandHubResponse(userDetail: SandHubDetailDto) {
|
|
try {
|
|
const requestPayload = {
|
|
leftTwoDigits: String(userDetail.plate.leftDigits),
|
|
serialLetter: String(userDetail.plate.centerAlphabet),
|
|
threeDigits: String(userDetail.plate.centerDigits),
|
|
rightTwoDigits: String(userDetail.plate.ir),
|
|
nationalCode: userDetail.nationalCodeOfInsurer,
|
|
};
|
|
// const requestUrl = `${process.env.SANDHUB_BASE_URL}/block-inquiry-tejarat`;
|
|
// const response = await this.makeSandHubRequest(requestUrl, requestPayload);
|
|
|
|
let response = {
|
|
"PrntPlcyCmpDocNo": "1403/1143-70591/200/35",
|
|
"MapTypNam": "پرايد هاچ بک -111",
|
|
"MtrNum": "5215907",
|
|
"ShsNum": "NAS431100E5798656",
|
|
"DisFnYrNum": "0",
|
|
"DisLfYrNum": "0",
|
|
"DisPrsnYrNum": "0",
|
|
"DisPrsnYrPrcnt": "0",
|
|
"DisFnYrPrcnt": "0",
|
|
"DisLfYrPrcnt": "0",
|
|
"vin": "IRPC941V2BD798656",
|
|
"MapVehicleSystemName": "ثبت نشده",
|
|
"LfCvrCptl": 0,
|
|
"FnCvrCptl": 0,
|
|
"PrsnCvrCptl": 0,
|
|
"VehicleSystemCode": 1,
|
|
"EdrsJson": "[{\"id\":1,\"Dsc\":\" الحاقيه توضيحات ندارد\"}]",
|
|
"PersonCvrCptl": 12000000000,
|
|
"LifeCvrCptl": 16000000000,
|
|
"FinancialCvrCptl": 4000000000,
|
|
"CarGroupCode": 3,
|
|
"CylCnt": 4,
|
|
"LastCompanyDocumentNumber": "03/1031/2835/1001/662",
|
|
"UsageCode": "8",
|
|
"MapUsageCode": 1,
|
|
"MapUsageName": "شخصي",
|
|
"Plk1": 59,
|
|
"Plk2": 16,
|
|
"Plk3": 419,
|
|
"PlkSrl": 78,
|
|
"PrintEndorsCompanyDocumentNumber": "بيمه نامه الحاقيه ندارد.",
|
|
"EndorseDate": null,
|
|
"InsuranceFullName": null,
|
|
"SystemField": "سايپا",
|
|
"TypeField": "111SE",
|
|
"UsageField": "سواري",
|
|
"MainColorField": "سفيد",
|
|
"SecondColorField": "سفيد شيري",
|
|
"ModelField": "1394",
|
|
"CapacityField": "جمعا 4 نفر",
|
|
"CylinderNumberField": "4",
|
|
"EngineNumberField": "5215907",
|
|
"ChassisNumberField": "NAS431100E5798656",
|
|
"VinNumberField": "IRPC941V2BD798656",
|
|
"InstallDateField": "1403/03/01",
|
|
"AxelNumberField": "2",
|
|
"WheelNumberField": "4",
|
|
"CompanyName": "بیمه سامان",
|
|
"CompanyCode": "15",
|
|
"IssueDate": "1403/04/06",
|
|
"SatrtDate": "1403/04/06",
|
|
"EndDate": "1404/04/06",
|
|
"Thrname": "",
|
|
"EndorseText": null,
|
|
"PolicyHealthLossCount": 0,
|
|
"PolicyFinancialLossCount": 0,
|
|
"PolicyPersonLossCount": 0,
|
|
"Tonage": 0,
|
|
"ThirdPolicyCode": 10502330579,
|
|
"DiscountPersonPercent": null,
|
|
"DiscountThirdPercent": null,
|
|
"SystemCodeCii": 0,
|
|
"SystemNameCii": "ثبت نشده",
|
|
"TypeCodeCii": 12189,
|
|
"TypeNameCii": "پرايد هاچ بک -111",
|
|
"UsageNameCii": "سواري",
|
|
"UsageCodeCii": 1,
|
|
"ModelCii": 1394,
|
|
"damageTypes": [],
|
|
"StatusTypeCode": 1
|
|
}
|
|
|
|
// // Map the new API response format to the old format
|
|
let result = this.mapNewApiResponseToOldFormat(response);
|
|
|
|
// if (result.usgCod !== "8") {
|
|
// throw new Error("خودرو شما شخصی / سواری نمی باشد")
|
|
// }
|
|
|
|
return result;
|
|
} catch (err) {
|
|
throw new Error(err);
|
|
}
|
|
}
|
|
|
|
async getPersonalInquiry(nationalCode: string, birthDate: number) {
|
|
try {
|
|
const requestUrl = `${process.env.SANDHUB_BASE_URL}/personal-inquiry/asia`;
|
|
const requestPayload = {
|
|
nin: nationalCode,
|
|
birthDate: birthDate,
|
|
persianBirthDate: "",
|
|
};
|
|
|
|
const response = await this.makeSandHubRequest(
|
|
requestUrl,
|
|
requestPayload,
|
|
);
|
|
|
|
if (response?.message?.includes("err.record.not.found")) {
|
|
throw new NotFoundException(
|
|
"Personal inquiry failed: Record not found for the given national code and birth date.",
|
|
);
|
|
}
|
|
return response.data;
|
|
} catch (err) {
|
|
throw new Error(`Error in finding personal inquiry: ${err}`);
|
|
}
|
|
}
|
|
|
|
async getDrivingLicenseInfo(
|
|
nationalCode: string,
|
|
driverLicenseNumber: string,
|
|
) {
|
|
const requestUrl = `${process.env.SANDHUB_BASE_URL}/driver-license-check`;
|
|
const requestPayload = {
|
|
driverLicenseNumber,
|
|
nationalCode,
|
|
};
|
|
|
|
this.logger.log(
|
|
`Fetching driving license info for national code: ${nationalCode}`,
|
|
);
|
|
|
|
try {
|
|
const response = await this.makeSandHubRequest(
|
|
requestUrl,
|
|
requestPayload,
|
|
);
|
|
|
|
if (response?.data?.IsSucceed === false) {
|
|
throw new NotFoundException(
|
|
"Driving license check failed: The license is not valid or could not be found.",
|
|
);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
if (
|
|
error instanceof BadGatewayException &&
|
|
error.message.includes("multiple retries")
|
|
) {
|
|
throw new BadGatewayException(
|
|
`Driving license check failed after multiple retries. The service may be down.`,
|
|
);
|
|
}
|
|
// For all other errors (like 400, 404, etc.), re-throw them as-is.
|
|
throw new Error(`Error in finding driving license: ${error}`);
|
|
}
|
|
}
|
|
|
|
async getCarOwnershipInfo(plate: any, nationalCode: string) {
|
|
try {
|
|
const requestUrl = `${process.env.SANDHUB_BASE_URL}/ownership`;
|
|
const requestPayload = {
|
|
Plk1: String(plate.leftDigits),
|
|
Plk2: String(plate.centerAlphabet),
|
|
Plk3: String(plate.centerDigits),
|
|
plkSrl: String(plate.ir),
|
|
nationalCode: nationalCode,
|
|
};
|
|
|
|
this.logger.log(
|
|
`Checking car ownership for national code: ${nationalCode}`,
|
|
);
|
|
const response = await this.makeSandHubRequest(
|
|
requestUrl,
|
|
requestPayload,
|
|
);
|
|
|
|
// Check the 'IsSuccess' field in the nested 'data' object.
|
|
if (response?.data?.IsSuccess === false) {
|
|
this.logger.warn(
|
|
`Car ownership check failed for national code ${nationalCode}. Response:`,
|
|
response,
|
|
);
|
|
throw new BadRequestException(
|
|
"Ownership validation failed: The provided national ID is not the owner of this vehicle.",
|
|
);
|
|
}
|
|
|
|
return response;
|
|
} catch (err) {
|
|
throw new Error(`Error in finding car ownership: ${err}`);
|
|
}
|
|
}
|
|
|
|
async getShebaValidation(nationalId: string, shebaId: string) {
|
|
try {
|
|
const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba`;
|
|
const requestPayload = {
|
|
accountOwnerType: "1",
|
|
nationalId: nationalId,
|
|
legalId: "",
|
|
shebaId: shebaId,
|
|
};
|
|
|
|
this.logger.log(`Validating Sheba ID for national code: ${nationalId}`);
|
|
const response = await this.makeSandHubRequest(
|
|
requestUrl,
|
|
requestPayload,
|
|
);
|
|
|
|
if (response?.ReturnValue === false || response?.HasError === true) {
|
|
this.logger.warn(
|
|
`Sheba validation failed for national code ${nationalId}. Response:`,
|
|
response,
|
|
);
|
|
throw new BadRequestException(
|
|
"Sheba ID validation failed. The provided Sheba ID does not match the national ID.",
|
|
);
|
|
}
|
|
|
|
return response;
|
|
} catch (err) {
|
|
throw new Error(`Error in matching sheba validation: ${err}`);
|
|
}
|
|
}
|
|
|
|
private handleSandHubError(
|
|
err: any,
|
|
attempt: number,
|
|
maxRetries: number,
|
|
): void {
|
|
if (err.response) {
|
|
this.logger.error(
|
|
`❌ SandHub responded with status ${err.response.status} on attempt ${attempt + 1}:`,
|
|
JSON.stringify(err.response.data),
|
|
);
|
|
|
|
if (err.response.status === 400) {
|
|
throw new BadGatewayException(
|
|
`SandHub rejected the request with a 400 Bad Request. Details: ${JSON.stringify(err.response.data)}`,
|
|
);
|
|
}
|
|
} else {
|
|
this.logger.error(
|
|
`❌ Network error during SandHub request (attempt ${attempt + 1}):`,
|
|
err.message,
|
|
);
|
|
}
|
|
|
|
if (err.message === "EMPTY_RESPONSE") {
|
|
throw new BadGatewayException(
|
|
"SandHub is offline or returned an empty response",
|
|
);
|
|
}
|
|
if (err.code === "ECONNABORTED") {
|
|
throw new GatewayTimeoutException("SandHub request timed out");
|
|
}
|
|
if (err.code === "ECONNRESET" || err.message.includes("socket hang up")) {
|
|
throw new ServiceUnavailableException(
|
|
"SandHub connection was reset or closed unexpectedly",
|
|
);
|
|
}
|
|
|
|
// This final check is for when all retries have failed for a retryable error.
|
|
if (attempt >= maxRetries) {
|
|
throw new BadGatewayException(
|
|
"Failed to fetch data from SandHub after multiple retries",
|
|
err.message,
|
|
);
|
|
}
|
|
}
|
|
|
|
async sandHubDocumentCreator(
|
|
userId: string,
|
|
requestId: string,
|
|
doc: Partial<SandHubModel>,
|
|
) {
|
|
const existingDoc = await this.sandHubDbService.findOneByRequestId(
|
|
requestId,
|
|
userId,
|
|
);
|
|
if (!existingDoc) {
|
|
return await this.sandHubDbService.create({ ...doc, userId, requestId });
|
|
} else {
|
|
const updatePayload = { ...existingDoc, ...doc };
|
|
return await this.sandHubDbService.findOneByRequestIdAndUpdate(
|
|
requestId,
|
|
userId,
|
|
updatePayload,
|
|
);
|
|
}
|
|
}
|
|
}
|