Directory-submission readiness: add MCP tool annotations (title + readOnlyHint/destructiveHint), /privacy policy route + README Privacy Policy section, bump to 2.3.2
This commit is contained in:
Vendored
+62
-45
@@ -1,94 +1,111 @@
|
||||
import axios from "axios";
|
||||
export class RedXAdapter {
|
||||
export class RedxAdapter {
|
||||
courierName = "redx";
|
||||
client;
|
||||
apiToken;
|
||||
enabled;
|
||||
constructor(apiToken, baseUrl = "https://openapi.redx.com.bd/v1.0.0-beta") {
|
||||
this.apiToken = apiToken;
|
||||
pickupStoreId;
|
||||
constructor(apiToken, baseUrl, pickupStoreId = "") {
|
||||
this.enabled = Boolean(apiToken);
|
||||
this.pickupStoreId = pickupStoreId;
|
||||
this.client = axios.create({
|
||||
baseURL: baseUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"API-ACCESS-TOKEN": `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 10000,
|
||||
timeout: 15000,
|
||||
});
|
||||
}
|
||||
isConfigured() {
|
||||
return this.enabled;
|
||||
}
|
||||
// RedX's create endpoint needs a numeric delivery_area_id. If the caller did
|
||||
// not pass one, best-effort resolve it from the address via GET /areas.
|
||||
async resolveDeliveryArea(req) {
|
||||
if (req.delivery_area_id) {
|
||||
return { id: Number(req.delivery_area_id), name: req.delivery_area || "" };
|
||||
}
|
||||
const res = await this.client.get("/areas");
|
||||
const areas = res.data?.areas || [];
|
||||
const addr = (req.recipient_address || "").toLowerCase();
|
||||
let best = null;
|
||||
for (const a of areas) {
|
||||
const base = String(a.name).split("(")[0].trim().toLowerCase();
|
||||
if (base && addr.includes(base)) {
|
||||
const bestLen = best ? String(best.name).split("(")[0].trim().length : 0;
|
||||
if (base.length > bestLen)
|
||||
best = a;
|
||||
}
|
||||
}
|
||||
if (!best) {
|
||||
throw new Error("RedX: could not match a delivery area from the address. Pass 'delivery_area_id' explicitly (look it up via RedX /areas).");
|
||||
}
|
||||
return { id: best.id, name: best.name };
|
||||
}
|
||||
async createParcel(req) {
|
||||
if (!this.enabled) {
|
||||
throw new Error("RedX Courier credentials (API Token) are not configured.");
|
||||
throw new Error("RedX credentials (API access token) are not configured.");
|
||||
}
|
||||
const area = await this.resolveDeliveryArea(req);
|
||||
const weightGrams = Math.max(1, Math.round((req.item_weight ?? 0.5) * 1000));
|
||||
const declaredValue = String(req.value ?? req.cod_amount ?? 0);
|
||||
const payload = {
|
||||
customer_name: req.recipient_name,
|
||||
customer_phone: req.recipient_phone,
|
||||
delivery_area: req.recipient_address,
|
||||
delivery_area_id: 1, // Default auto-resolved area
|
||||
delivery_area: area.name || req.delivery_area || "",
|
||||
delivery_area_id: area.id,
|
||||
customer_address: req.recipient_address,
|
||||
merchant_invoice_id: req.invoice,
|
||||
cash_collection_amount: req.cod_amount,
|
||||
parcel_weight: (req.item_weight || 0.5) * 1000, // RedX uses grams
|
||||
cash_collection_amount: String(req.cod_amount ?? 0),
|
||||
parcel_weight: weightGrams,
|
||||
instruction: req.note || "Aura AI automated dispatch",
|
||||
value: req.cod_amount || 500,
|
||||
value: declaredValue,
|
||||
};
|
||||
const response = await this.client.post("/parcels", payload);
|
||||
const trackingId = response.data.tracking_id || response.data.parcel_id;
|
||||
const storeId = req.pickup_store_id || this.pickupStoreId;
|
||||
if (storeId)
|
||||
payload.pickup_store_id = Number(storeId);
|
||||
if (req.item_type) {
|
||||
payload.parcel_details_json = [
|
||||
{ name: req.item_type, category: req.item_category || "general", value: Number(req.value ?? req.cod_amount ?? 0) },
|
||||
];
|
||||
}
|
||||
const response = await this.client.post("/parcel", payload);
|
||||
const data = response.data;
|
||||
const trackingId = data?.tracking_id;
|
||||
if (!trackingId) {
|
||||
throw new Error(`RedX API error: ${JSON.stringify(data?.message || data)}`);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
courier: "redx",
|
||||
tracking_code: trackingId,
|
||||
consignment_id: trackingId,
|
||||
invoice: req.invoice,
|
||||
status: "ready_for_pickup",
|
||||
status: "pickup-pending",
|
||||
cod_amount: req.cod_amount,
|
||||
delivery_fee: response.data.delivery_fee || 60,
|
||||
created_at: new Date().toISOString(),
|
||||
raw_response: response.data,
|
||||
raw_response: data,
|
||||
};
|
||||
}
|
||||
async trackParcel(trackingCode) {
|
||||
if (!this.enabled) {
|
||||
throw new Error("RedX Courier credentials are not configured.");
|
||||
throw new Error("RedX credentials are not configured.");
|
||||
}
|
||||
const response = await this.client.get(`/parcels/track/${encodeURIComponent(trackingCode)}`);
|
||||
const response = await this.client.get(`/parcel/track/${encodeURIComponent(trackingCode)}`);
|
||||
const data = response.data;
|
||||
const events = data?.tracking || [];
|
||||
const latest = events[events.length - 1];
|
||||
return {
|
||||
success: true,
|
||||
courier: "redx",
|
||||
tracking_code: trackingCode,
|
||||
status: data.tracking?.status || "in_transit",
|
||||
timeline: (data.tracking?.history || []).map((h) => ({
|
||||
status: h.status,
|
||||
time: h.created_at,
|
||||
note: h.message,
|
||||
})),
|
||||
status: latest?.message_en || "unknown",
|
||||
updated_at: latest?.time || new Date().toISOString(),
|
||||
timeline: events.map((e) => ({ status: e.message_en || "", time: e.time || "", note: e.message_bn })),
|
||||
raw_response: data,
|
||||
};
|
||||
}
|
||||
async getBalance() {
|
||||
return {
|
||||
success: true,
|
||||
courier: "redx",
|
||||
current_balance: 0,
|
||||
raw_response: { message: "RedX payouts are disbursed directly to connected merchant bank accounts." },
|
||||
};
|
||||
}
|
||||
async getLocations(cityName) {
|
||||
const response = await this.client.get("/areas");
|
||||
const areas = response.data.areas || [];
|
||||
return {
|
||||
success: true,
|
||||
courier: "redx",
|
||||
locations: areas.map((a) => ({
|
||||
city_id: a.id,
|
||||
city_name: a.name,
|
||||
zone_name: a.zone_name,
|
||||
area_name: a.name,
|
||||
})),
|
||||
};
|
||||
throw new Error("RedX does not expose a merchant balance endpoint via its public API — check your balance in the RedX merchant panel.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user