fix(build): add pg and @types/pg dependencies, add master courier fallbacks and compile live tracking (v2.4.1)
This commit is contained in:
Vendored
+158
-18
@@ -9,13 +9,17 @@ export class PathaoAdapter {
|
||||
baseUrl;
|
||||
accessToken = null;
|
||||
tokenExpiresAt = 0;
|
||||
constructor(clientId, clientSecret, username, password, storeId, baseUrl) {
|
||||
// In-memory caches for fast sub-second routing
|
||||
cityCache = null;
|
||||
zoneCache = new Map();
|
||||
areaCache = new Map();
|
||||
constructor(clientId, clientSecret, username, password, storeId = "", baseUrl = "https://api-hermes.pathao.com") {
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.storeId = storeId;
|
||||
this.baseUrl = baseUrl;
|
||||
this.storeId = storeId || process.env.PATHAO_STORE_ID || "";
|
||||
this.baseUrl = baseUrl || "https://api-hermes.pathao.com";
|
||||
}
|
||||
isConfigured() {
|
||||
return Boolean(this.clientId && this.clientSecret);
|
||||
@@ -44,31 +48,167 @@ export class PathaoAdapter {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 10000,
|
||||
timeout: 12000,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Dynamically resolve merchant store_id from Pathao portal if missing or default
|
||||
*/
|
||||
async resolveStoreId() {
|
||||
if (this.storeId && Number(this.storeId) > 1) {
|
||||
return Number(this.storeId);
|
||||
}
|
||||
try {
|
||||
const client = await this.getClient();
|
||||
const res = await client.get("/aladdin/api/v1/stores");
|
||||
const stores = res.data?.data?.data || [];
|
||||
const activeStore = stores.find((s) => s.is_active) || stores[0];
|
||||
if (activeStore && activeStore.store_id) {
|
||||
this.storeId = String(activeStore.store_id);
|
||||
return Number(activeStore.store_id);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.warn("Pathao store resolution notice:", err.message);
|
||||
}
|
||||
return Number(this.storeId) || 0; // Aura Merchant registered store id
|
||||
}
|
||||
/**
|
||||
* DeepMind Intelligent Spatial Hierarchy Resolver for Pathao
|
||||
* Eliminates 422 Unprocessable Entity by matching address tokens against live Pathao geo catalog
|
||||
*/
|
||||
async resolveLocation(address, req) {
|
||||
const client = await this.getClient();
|
||||
if (req?.delivery_area_id && typeof req.delivery_area_id === "number") {
|
||||
return { cityId: 1, zoneId: Number(req.delivery_area_id) };
|
||||
}
|
||||
const addrLower = (address || "").toLowerCase();
|
||||
// 1. Resolve City
|
||||
let cityId = 1; // Default Dhaka city ID
|
||||
try {
|
||||
if (!this.cityCache) {
|
||||
const cRes = await client.get("/aladdin/api/v1/countries/1/city-list");
|
||||
this.cityCache = cRes.data?.data?.data || [];
|
||||
}
|
||||
for (const c of this.cityCache || []) {
|
||||
const cName = String(c.city_name || "").toLowerCase().trim();
|
||||
if (cName && cName !== "dhaka" && addrLower.includes(cName)) {
|
||||
cityId = c.city_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
cityId = 1;
|
||||
}
|
||||
// 2. Resolve Zone for City
|
||||
let zoneId = cityId === 1 ? 62 : 1; // Default 62 (Dhanmondi, Dhaka)
|
||||
try {
|
||||
if (!this.zoneCache.has(cityId)) {
|
||||
const zRes = await client.get(`/aladdin/api/v1/cities/${cityId}/zone-list`);
|
||||
const zones = zRes.data?.data?.data || [];
|
||||
this.zoneCache.set(cityId, zones);
|
||||
}
|
||||
const zones = this.zoneCache.get(cityId) || [];
|
||||
const sortedZones = [...zones].sort((a, b) => b.zone_name.length - a.zone_name.length);
|
||||
const cleanAddrNoCity = addrLower
|
||||
.replace(/bangladesh|dhaka/gi, " ")
|
||||
.replace(/[,.-]/g, " ")
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
let matchedZone = null;
|
||||
for (const z of sortedZones) {
|
||||
const zName = z.zone_name.toLowerCase().trim();
|
||||
if (zName === "dhaka" || zName === "dhaka city")
|
||||
continue;
|
||||
if (cleanAddrNoCity.includes(zName)) {
|
||||
matchedZone = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchedZone) {
|
||||
for (const z of sortedZones) {
|
||||
const words = z.zone_name
|
||||
.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter((w) => w.length >= 4 && !["road", "block", "sector", "area"].includes(w));
|
||||
if (words.some((w) => cleanAddrNoCity.includes(w))) {
|
||||
matchedZone = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matchedZone) {
|
||||
zoneId = matchedZone.zone_id;
|
||||
}
|
||||
else if (zones.length > 0) {
|
||||
// Safe fallback zone for that city
|
||||
zoneId = zones[0].zone_id;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.warn("Pathao zone resolution notice:", err.message);
|
||||
}
|
||||
// 3. Resolve Area for Zone
|
||||
let areaId = undefined;
|
||||
try {
|
||||
if (!this.areaCache.has(zoneId)) {
|
||||
const aRes = await client.get(`/aladdin/api/v1/zones/${zoneId}/area-list`);
|
||||
const areas = aRes.data?.data?.data || [];
|
||||
this.areaCache.set(zoneId, areas);
|
||||
}
|
||||
const areas = this.areaCache.get(zoneId) || [];
|
||||
if (areas.length > 0) {
|
||||
for (const a of areas) {
|
||||
const aName = a.area_name.toLowerCase().trim();
|
||||
if (aName.length >= 4 && addrLower.includes(aName)) {
|
||||
areaId = a.area_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!areaId) {
|
||||
areaId = areas[0].area_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// Area is optional in Pathao
|
||||
}
|
||||
return { cityId, zoneId, areaId };
|
||||
}
|
||||
async createParcel(req) {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error("Pathao credentials are not configured.");
|
||||
}
|
||||
const client = await this.getClient();
|
||||
const resolvedStoreId = await this.resolveStoreId();
|
||||
const { cityId, zoneId, areaId } = await this.resolveLocation(req.recipient_address, req);
|
||||
const cleanPhone = req.recipient_phone.replace(/\D/g, "");
|
||||
const formattedPhone = cleanPhone.length > 11 ? cleanPhone.slice(-11) : cleanPhone;
|
||||
const payload = {
|
||||
store_id: Number(this.storeId) || 1,
|
||||
store_id: resolvedStoreId,
|
||||
merchant_order_id: req.invoice,
|
||||
recipient_name: req.recipient_name,
|
||||
recipient_phone: req.recipient_phone,
|
||||
recipient_address: req.recipient_address,
|
||||
recipient_city: 1, // Default Dhaka city ID
|
||||
recipient_zone: 1,
|
||||
recipient_area: 1,
|
||||
delivery_type: 48, // Normal 48h or 24h
|
||||
item_type: 2, // Parcel
|
||||
special_instruction: req.note || "Aura AI automated dispatch",
|
||||
recipient_name: (req.recipient_name || "Customer").trim().slice(0, 100),
|
||||
recipient_phone: formattedPhone,
|
||||
recipient_address: (req.recipient_address || "").trim().slice(0, 220),
|
||||
delivery_type: 48, // 48 for Normal Delivery, 12 for On Demand Delivery
|
||||
item_type: 2, // 1 for Document, 2 for Parcel
|
||||
special_instruction: (req.note || "Aura AI automated dispatch").slice(0, 250),
|
||||
item_quantity: 1,
|
||||
item_weight: req.item_weight || 0.5,
|
||||
amount_to_collect: req.cod_amount,
|
||||
item_description: req.item_type || "Standard parcel",
|
||||
item_weight: Math.max(0.5, Math.min(10, Number(req.item_weight || 0.5))),
|
||||
amount_to_collect: Math.round(Number(req.cod_amount || 0)),
|
||||
item_description: (req.item_type || "Standard parcel").slice(0, 250),
|
||||
};
|
||||
// Pathao docs: "do not send a null value. If not included in the request payload,
|
||||
// then our system will populate it automatically based on recipient_address"
|
||||
if (cityId && zoneId && zoneId > 1) {
|
||||
payload.recipient_city = Number(cityId);
|
||||
payload.recipient_zone = Number(zoneId);
|
||||
if (areaId && areaId > 1) {
|
||||
payload.recipient_area = Number(areaId);
|
||||
}
|
||||
}
|
||||
const response = await client.post("/aladdin/api/v1/orders", payload);
|
||||
const data = response.data.data;
|
||||
return {
|
||||
@@ -105,7 +245,7 @@ export class PathaoAdapter {
|
||||
success: true,
|
||||
courier: "pathao",
|
||||
current_balance: 0,
|
||||
raw_response: { message: "Pathao payout balance fetched via portal" },
|
||||
raw_response: { message: "Pathao payout balance managed via merchant portal" },
|
||||
};
|
||||
}
|
||||
async getLocations(cityName) {
|
||||
|
||||
Reference in New Issue
Block a user