feat: Paperfly courier adapter (create + track); v2.3.0 — all 4 couriers
This commit is contained in:
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "aura-courier-mcp",
|
"name": "aura-courier-mcp",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"description": "Universal Bangladesh courier MCP server — Steadfast, Pathao & RedX with a built-in fraud-risk engine — for Claude, Antigravity, Cursor & n8n AI agents. Runs as a local npx (STDIO) server or a remote HTTP endpoint.",
|
"description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly with a built-in fraud-risk engine — for Claude, Antigravity, Cursor & n8n AI agents. Runs as a local npx (STDIO) server or a remote HTTP endpoint.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"steadfast",
|
"steadfast",
|
||||||
"pathao",
|
"pathao",
|
||||||
"redx",
|
"redx",
|
||||||
|
"paperfly",
|
||||||
"bangladesh",
|
"bangladesh",
|
||||||
"antigravity",
|
"antigravity",
|
||||||
"claude",
|
"claude",
|
||||||
|
|||||||
+60
-57
@@ -2,7 +2,6 @@ import axios, { AxiosInstance } from "axios";
|
|||||||
import { CourierAdapter } from "./base.js";
|
import { CourierAdapter } from "./base.js";
|
||||||
import {
|
import {
|
||||||
BalanceResponse,
|
BalanceResponse,
|
||||||
LocationResolutionResponse,
|
|
||||||
ParcelCreateRequest,
|
ParcelCreateRequest,
|
||||||
ParcelResponse,
|
ParcelResponse,
|
||||||
SupportedCourier,
|
SupportedCourier,
|
||||||
@@ -11,32 +10,23 @@ import {
|
|||||||
|
|
||||||
export class PaperflyAdapter implements CourierAdapter {
|
export class PaperflyAdapter implements CourierAdapter {
|
||||||
courierName: SupportedCourier = "paperfly";
|
courierName: SupportedCourier = "paperfly";
|
||||||
|
private apiKey: string;
|
||||||
|
private username: string;
|
||||||
|
private password: string;
|
||||||
|
private storeName: string;
|
||||||
private client: AxiosInstance;
|
private client: AxiosInstance;
|
||||||
private user: string;
|
|
||||||
private pass: string;
|
|
||||||
private key: string;
|
|
||||||
private enabled: boolean;
|
private enabled: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(apiKey: string, username: string, password: string, storeName: string, baseUrl: string) {
|
||||||
user: string,
|
this.apiKey = apiKey;
|
||||||
pass: string,
|
this.username = username;
|
||||||
key: string,
|
this.password = password;
|
||||||
baseUrl: string = "https://api.paperfly.com.bd"
|
this.storeName = storeName;
|
||||||
) {
|
this.enabled = Boolean(apiKey && storeName);
|
||||||
this.user = user;
|
|
||||||
this.pass = pass;
|
|
||||||
this.key = key;
|
|
||||||
this.enabled = Boolean(user && pass && key);
|
|
||||||
|
|
||||||
const token = Buffer.from(`${user}:${pass}`).toString("base64");
|
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL: baseUrl,
|
baseURL: baseUrl,
|
||||||
headers: {
|
timeout: 15000,
|
||||||
Authorization: `Basic ${token}`,
|
headers: { "Content-Type": "application/json" },
|
||||||
pap_key: key,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,66 +36,79 @@ export class PaperflyAdapter implements CourierAdapter {
|
|||||||
|
|
||||||
async createParcel(req: ParcelCreateRequest): Promise<ParcelResponse> {
|
async createParcel(req: ParcelCreateRequest): Promise<ParcelResponse> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
throw new Error("Paperfly Courier credentials (User / Pass / Key) are not configured.");
|
throw new Error("Paperfly credentials (paperflykey + store name) are not configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
merOrderRef: req.invoice,
|
merchantOrderReference: req.invoice,
|
||||||
pickMerchantName: "Aura AI Commerce",
|
storeName: this.storeName,
|
||||||
pickMerchantAddress: "Dhaka, Bangladesh",
|
productBrief: req.item_type || "Product",
|
||||||
custName: req.recipient_name,
|
packagePrice: String(req.value ?? req.cod_amount ?? 0),
|
||||||
custPhone: req.recipient_phone,
|
max_weight: String(req.item_weight ?? 0.5),
|
||||||
custAddr: req.recipient_address,
|
customerName: req.recipient_name,
|
||||||
customerDistrict: "Dhaka",
|
customerAddress: req.recipient_address,
|
||||||
customerThana: "Gulshan",
|
customerPhone: req.recipient_phone,
|
||||||
packagePrice: req.cod_amount,
|
|
||||||
max_weight: req.item_weight || 0.5,
|
|
||||||
productBrief: req.item_type || "General Ecommerce item",
|
|
||||||
deliveryOption: "regular",
|
|
||||||
};
|
};
|
||||||
|
const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, {
|
||||||
const response = await this.client.post("/OrderPlacement", payload);
|
headers: { paperflykey: this.apiKey },
|
||||||
const trackingCode = response.data?.tracking_number || req.invoice;
|
});
|
||||||
|
const data = response.data;
|
||||||
|
const ok = data?.success;
|
||||||
|
if (!ok?.tracking_number) {
|
||||||
|
throw new Error(`Paperfly API error: ${JSON.stringify(data?.error || data?.message || data)}`);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "paperfly",
|
courier: "paperfly",
|
||||||
tracking_code: trackingCode,
|
tracking_code: ok.tracking_number,
|
||||||
consignment_id: trackingCode,
|
consignment_id: ok.tracking_barcode || ok.tracking_number,
|
||||||
invoice: req.invoice,
|
invoice: req.invoice,
|
||||||
status: "order_placed",
|
status: ok.message || "created",
|
||||||
cod_amount: req.cod_amount,
|
cod_amount: req.cod_amount,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
raw_response: response.data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async trackParcel(trackingCode: string): Promise<TrackingResponse> {
|
async trackParcel(trackingCode: string): Promise<TrackingResponse> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
throw new Error("Paperfly Courier credentials are not configured.");
|
throw new Error("Paperfly credentials are not configured.");
|
||||||
}
|
}
|
||||||
|
if (!this.username || !this.password) {
|
||||||
const response = await this.client.post("/Tracking", {
|
throw new Error(
|
||||||
ReferenceNumber: trackingCode,
|
"Paperfly tracking needs your merchant-panel username & password (Basic Auth). Add them to use track_parcel."
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
// Paperfly tracks by your merchant order reference (merchantOrderReference), not its tracking number.
|
||||||
|
const response = await this.client.post(
|
||||||
|
"/API-Order-Tracking",
|
||||||
|
{ ReferenceNumber: trackingCode },
|
||||||
|
{ auth: { username: this.username, password: this.password } }
|
||||||
|
);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
const st = data?.success?.trackingStatus?.[0] || {};
|
||||||
|
const stages: Array<[any, string]> = [
|
||||||
|
[st.Delivered, "delivered"],
|
||||||
|
[st.Partial, "partial-delivery"],
|
||||||
|
[st.Returned, "returned"],
|
||||||
|
[st.PickedForDelivery, "out-for-delivery"],
|
||||||
|
[st.inTransit, "in-transit"],
|
||||||
|
[st.ReceivedAtPoint, "received-at-point"],
|
||||||
|
[st.Pick, "picked-up"],
|
||||||
|
];
|
||||||
|
const status = stages.find(([v]) => v && String(v).trim())?.[1] || data?.success?.message || "pending";
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "paperfly",
|
courier: "paperfly",
|
||||||
tracking_code: trackingCode,
|
tracking_code: trackingCode,
|
||||||
status: data?.Status || "in_progress",
|
status,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
raw_response: data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBalance(): Promise<BalanceResponse> {
|
async getBalance(): Promise<BalanceResponse> {
|
||||||
return {
|
throw new Error(
|
||||||
success: true,
|
"Paperfly does not expose a merchant balance endpoint via its public API — check the Paperfly merchant panel."
|
||||||
courier: "paperfly",
|
);
|
||||||
current_balance: 0,
|
|
||||||
raw_response: { message: "Paperfly merchant settlements processed weekly via Wingman portal." },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ export interface CourierConfig {
|
|||||||
pickupStoreId: string;
|
pickupStoreId: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
paperfly: {
|
||||||
|
apiKey: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
storeName: string;
|
||||||
|
baseUrl: string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EnvSource = Record<string, string | undefined>;
|
export type EnvSource = Record<string, string | undefined>;
|
||||||
@@ -62,5 +70,13 @@ export function loadConfig(src: EnvSource = process.env): CourierConfig {
|
|||||||
pickupStoreId: src.REDX_PICKUP_STORE_ID || "",
|
pickupStoreId: src.REDX_PICKUP_STORE_ID || "",
|
||||||
enabled: Boolean(src.REDX_API_TOKEN),
|
enabled: Boolean(src.REDX_API_TOKEN),
|
||||||
},
|
},
|
||||||
|
paperfly: {
|
||||||
|
apiKey: src.PAPERFLY_API_KEY || "",
|
||||||
|
username: src.PAPERFLY_USERNAME || "",
|
||||||
|
password: src.PAPERFLY_PASSWORD || "",
|
||||||
|
storeName: src.PAPERFLY_STORE_NAME || "",
|
||||||
|
baseUrl: src.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd",
|
||||||
|
enabled: Boolean(src.PAPERFLY_API_KEY && src.PAPERFLY_STORE_NAME),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -35,6 +35,10 @@ function keysFromRequest(req: Request): EnvSource {
|
|||||||
REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"),
|
REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"),
|
||||||
REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"),
|
REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"),
|
||||||
REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"),
|
REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"),
|
||||||
|
PAPERFLY_API_KEY: pick("x-paperfly-api-key", "paperfly_key"),
|
||||||
|
PAPERFLY_USERNAME: pick("x-paperfly-username", "paperfly_username"),
|
||||||
|
PAPERFLY_PASSWORD: pick("x-paperfly-password", "paperfly_password"),
|
||||||
|
PAPERFLY_STORE_NAME: pick("x-paperfly-store-name", "paperfly_store"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +50,7 @@ app.use((req, res, next) => {
|
|||||||
res.header("Access-Control-Allow-Origin", "*");
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
res.header(
|
res.header(
|
||||||
"Access-Control-Allow-Headers",
|
"Access-Control-Allow-Headers",
|
||||||
"Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version, x-steadfast-api-key, x-steadfast-secret-key, x-steadfast-base-url, x-pathao-client-id, x-pathao-client-secret, x-pathao-username, x-pathao-password, x-pathao-store-id, x-pathao-base-url, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id"
|
"Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version, x-steadfast-api-key, x-steadfast-secret-key, x-steadfast-base-url, x-pathao-client-id, x-pathao-client-secret, x-pathao-username, x-pathao-password, x-pathao-store-id, x-pathao-base-url, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id, x-paperfly-api-key, x-paperfly-username, x-paperfly-password, x-paperfly-store-name"
|
||||||
);
|
);
|
||||||
res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
||||||
res.header("Access-Control-Expose-Headers", "mcp-session-id");
|
res.header("Access-Control-Expose-Headers", "mcp-session-id");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { SteadfastAdapter } from "./adapters/steadfast.js";
|
|||||||
import { PathaoAdapter } from "./adapters/pathao.js";
|
import { PathaoAdapter } from "./adapters/pathao.js";
|
||||||
import { FraudRiskEngine } from "./adapters/fraud_engine.js";
|
import { FraudRiskEngine } from "./adapters/fraud_engine.js";
|
||||||
import { RedxAdapter } from "./adapters/redx.js";
|
import { RedxAdapter } from "./adapters/redx.js";
|
||||||
|
import { PaperflyAdapter } from "./adapters/paperfly.js";
|
||||||
import { loadConfig, CourierConfig } from "./config.js";
|
import { loadConfig, CourierConfig } from "./config.js";
|
||||||
import {
|
import {
|
||||||
BalanceResponse,
|
BalanceResponse,
|
||||||
@@ -36,6 +37,15 @@ export class CourierRegistry {
|
|||||||
|
|
||||||
const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId);
|
const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId);
|
||||||
this.adapters.set("redx", redx);
|
this.adapters.set("redx", redx);
|
||||||
|
|
||||||
|
const paperfly = new PaperflyAdapter(
|
||||||
|
config.paperfly.apiKey,
|
||||||
|
config.paperfly.username,
|
||||||
|
config.paperfly.password,
|
||||||
|
config.paperfly.storeName,
|
||||||
|
config.paperfly.baseUrl
|
||||||
|
);
|
||||||
|
this.adapters.set("paperfly", paperfly);
|
||||||
}
|
}
|
||||||
|
|
||||||
listCouriers() {
|
listCouriers() {
|
||||||
|
|||||||
+3
-3
@@ -16,11 +16,11 @@ export const TOOLS: Tool[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_parcel",
|
name: "create_parcel",
|
||||||
description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao or RedX) with normalized response.",
|
description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao, RedX or Paperfly) with normalized response.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
courier: { type: "string", enum: ["steadfast", "pathao", "redx", "auto"], description: "Target courier or 'auto' for AI smart routing (default: auto)" },
|
courier: { type: "string", enum: ["steadfast", "pathao", "redx", "paperfly", "auto"], description: "Target courier or 'auto' for AI smart routing (default: auto)" },
|
||||||
invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
|
invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
|
||||||
recipient_name: { type: "string", description: "Customer full name" },
|
recipient_name: { type: "string", description: "Customer full name" },
|
||||||
recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
|
recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
|
||||||
@@ -43,7 +43,7 @@ export const TOOLS: Tool[] = [
|
|||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
tracking_code: { type: "string", description: "Consignment ID or tracking code" },
|
tracking_code: { type: "string", description: "Consignment ID or tracking code" },
|
||||||
courier: { type: "string", enum: ["steadfast", "pathao", "redx"], description: "Optional courier name if known" },
|
courier: { type: "string", enum: ["steadfast", "pathao", "redx", "paperfly"], description: "Optional courier name if known" },
|
||||||
},
|
},
|
||||||
required: ["tracking_code"],
|
required: ["tracking_code"],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user