Files
edr-platform/apps/edr-freight-api/src/modules/companies/services/etrade.service.ts
2026-08-02 18:33:38 +00:00

130 lines
4.6 KiB
TypeScript

import { Injectable, BadRequestException } from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { Agent } from "https";
import { firstValueFrom } from "rxjs";
import {
ETradeCompanyInfo,
ETradeBusinessInfo,
CompanyRegistrationData,
normalizeRegion,
} from "@edr/types";
@Injectable()
export class ETradeService {
private readonly baseUrl = "https://etrade.gov.et/api";
private readonly referer = "https://etrade.gov.et/business-license-checker";
/**
* The eTrade server serves an incomplete TLS chain (it omits the intermediate
* CA cert), so Node rejects the handshake with UNABLE_TO_GET_ISSUER_CERT.
* Scope a relaxed agent to these outbound calls only — the rest of the app
* keeps full certificate verification.
*/
private readonly httpsAgent = new Agent({ rejectUnauthorized: false });
constructor(private readonly httpService: HttpService) {}
async getCompanyInfoByTin(tin: string): Promise<ETradeCompanyInfo> {
const url = `${this.baseUrl}/Registration/GetRegistrationInfoByTin/${tin}/en`;
try {
const response = await firstValueFrom(
this.httpService.get<ETradeCompanyInfo>(url, {
headers: { Referer: this.referer },
httpsAgent: this.httpsAgent,
}),
);
return response.data;
} catch (error: any) {
throw new BadRequestException(
`Failed to fetch company info from eTrade: ${error.message}`,
);
}
}
async getBusinessByLicenseNo(
licenseNo: string,
tin: string,
): Promise<ETradeBusinessInfo> {
const url = `${this.baseUrl}/BusinessMain/GetBusinessByLicenseNo`;
try {
const response = await firstValueFrom(
this.httpService.get<ETradeBusinessInfo>(url, {
params: {
LicenseNo: licenseNo,
Tin: tin,
Lang: "en",
},
headers: { Referer: this.referer },
httpsAgent: this.httpsAgent,
}),
);
return response.data;
} catch (error: any) {
throw new BadRequestException(
`Failed to fetch business info from eTrade: ${error.message}`,
);
}
}
async resolveCompanyData(tin: string): Promise<{
companyInfo: ETradeCompanyInfo;
businessInfo: ETradeBusinessInfo | null;
}> {
const companyInfo = await this.getCompanyInfoByTin(tin);
if (!companyInfo.Businesses || companyInfo.Businesses.length === 0) {
return { companyInfo, businessInfo: null };
}
const latestBusiness = companyInfo.Businesses[0];
try {
const businessInfo = await this.getBusinessByLicenseNo(
latestBusiness.LicenceNumber,
tin,
);
return { companyInfo, businessInfo };
} catch {
return { companyInfo, businessInfo: null };
}
}
/**
* `companyInfo` carries the registered organization name (`BusinessName`);
* `businessInfo` only carries the licence's `TradeName`. Pass both so the
* company name resolves to the legal entity rather than the trade name — and
* never to `ManagerNameEng`, which is the manager's personal name.
*/
extractRegistrationData(
businessInfo: ETradeBusinessInfo,
companyInfo?: ETradeCompanyInfo,
): CompanyRegistrationData {
const primaryManager = businessInfo.AssociateShortInfos?.[0];
return {
companyName:
companyInfo?.BusinessName?.trim() || businessInfo.TradeName?.trim() || "",
licenceNumber: businessInfo.LicenceNumber,
statusDescription: businessInfo.StatusDescription,
dateRegistered: businessInfo.DateRegistered,
renewedFrom: businessInfo.RenewedFrom,
renewalDate: businessInfo.RenewalDate,
// RenewedTo is ISO ("2018-07-07T00:00:00"); RenewedToDateString matches
// RenewedFrom/RenewalDate's "M/D/YYYY" format — use that for consistency.
renewedTo: businessInfo.RenewedToDateString,
// eTrade returns uncoded uppercase text and sometimes a zone name in the
// Region slot. Map it onto the canonical list; an unresolved value yields
// "" so the form asks the user to pick rather than failing validation on
// save with a value they never typed.
region: normalizeRegion(businessInfo.AddressInfo?.Region) ?? "",
zone: businessInfo.AddressInfo?.Zone || "",
woreda: businessInfo.AddressInfo?.Woreda || "",
kebele: businessInfo.AddressInfo?.Kebele || "",
houseNo: businessInfo.AddressInfo?.HouseNo || "",
mobilePhone: businessInfo.AddressInfo?.MobilePhone || "",
regularPhone: businessInfo.AddressInfo?.RegularPhone || "",
managerName: primaryManager?.ManagerNameEng || "",
managerPhone: primaryManager?.RegularPhone || "",
};
}
}