From d2696d8ad1c01a03ef75c1eccbd94a10a6e3effc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 01:34:16 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Paperfly=20courier=20adapter=20(create?= =?UTF-8?q?=20+=20track);=20v2.3.0=20=E2=80=94=20all=204=20couriers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- src/adapters/paperfly.ts | 117 ++++++++++++++++++++------------------- src/config.ts | 16 ++++++ src/http.ts | 6 +- src/registry.ts | 10 ++++ src/server.ts | 6 +- 6 files changed, 97 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 56b7f91..7ef4450 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aura-courier-mcp", - "version": "2.2.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.", + "version": "2.3.0", + "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", "type": "module", "bin": { @@ -28,6 +28,7 @@ "steadfast", "pathao", "redx", + "paperfly", "bangladesh", "antigravity", "claude", diff --git a/src/adapters/paperfly.ts b/src/adapters/paperfly.ts index eb353ec..a705054 100644 --- a/src/adapters/paperfly.ts +++ b/src/adapters/paperfly.ts @@ -2,7 +2,6 @@ import axios, { AxiosInstance } from "axios"; import { CourierAdapter } from "./base.js"; import { BalanceResponse, - LocationResolutionResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, @@ -11,32 +10,23 @@ import { export class PaperflyAdapter implements CourierAdapter { courierName: SupportedCourier = "paperfly"; + private apiKey: string; + private username: string; + private password: string; + private storeName: string; private client: AxiosInstance; - private user: string; - private pass: string; - private key: string; private enabled: boolean; - constructor( - user: string, - pass: string, - key: string, - baseUrl: string = "https://api.paperfly.com.bd" - ) { - this.user = user; - this.pass = pass; - this.key = key; - this.enabled = Boolean(user && pass && key); - - const token = Buffer.from(`${user}:${pass}`).toString("base64"); + constructor(apiKey: string, username: string, password: string, storeName: string, baseUrl: string) { + this.apiKey = apiKey; + this.username = username; + this.password = password; + this.storeName = storeName; + this.enabled = Boolean(apiKey && storeName); this.client = axios.create({ baseURL: baseUrl, - headers: { - Authorization: `Basic ${token}`, - pap_key: key, - "Content-Type": "application/json", - }, - timeout: 10000, + timeout: 15000, + headers: { "Content-Type": "application/json" }, }); } @@ -46,66 +36,79 @@ export class PaperflyAdapter implements CourierAdapter { async createParcel(req: ParcelCreateRequest): Promise { 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 = { - merOrderRef: req.invoice, - pickMerchantName: "Aura AI Commerce", - pickMerchantAddress: "Dhaka, Bangladesh", - custName: req.recipient_name, - custPhone: req.recipient_phone, - custAddr: req.recipient_address, - customerDistrict: "Dhaka", - customerThana: "Gulshan", - packagePrice: req.cod_amount, - max_weight: req.item_weight || 0.5, - productBrief: req.item_type || "General Ecommerce item", - deliveryOption: "regular", + merchantOrderReference: req.invoice, + storeName: this.storeName, + productBrief: req.item_type || "Product", + packagePrice: String(req.value ?? req.cod_amount ?? 0), + max_weight: String(req.item_weight ?? 0.5), + customerName: req.recipient_name, + customerAddress: req.recipient_address, + customerPhone: req.recipient_phone, }; - - const response = await this.client.post("/OrderPlacement", payload); - const trackingCode = response.data?.tracking_number || req.invoice; - + const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, { + headers: { paperflykey: this.apiKey }, + }); + 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 { success: true, courier: "paperfly", - tracking_code: trackingCode, - consignment_id: trackingCode, + tracking_code: ok.tracking_number, + consignment_id: ok.tracking_barcode || ok.tracking_number, invoice: req.invoice, - status: "order_placed", + status: ok.message || "created", cod_amount: req.cod_amount, created_at: new Date().toISOString(), - raw_response: response.data, + raw_response: data, }; } async trackParcel(trackingCode: string): Promise { if (!this.enabled) { - throw new Error("Paperfly Courier credentials are not configured."); + throw new Error("Paperfly credentials are not configured."); } - - const response = await this.client.post("/Tracking", { - ReferenceNumber: trackingCode, - }); + if (!this.username || !this.password) { + throw new Error( + "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 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 { success: true, courier: "paperfly", tracking_code: trackingCode, - status: data?.Status || "in_progress", + status, updated_at: new Date().toISOString(), raw_response: data, }; } async getBalance(): Promise { - return { - success: true, - courier: "paperfly", - current_balance: 0, - raw_response: { message: "Paperfly merchant settlements processed weekly via Wingman portal." }, - }; + throw new Error( + "Paperfly does not expose a merchant balance endpoint via its public API — check the Paperfly merchant panel." + ); } } diff --git a/src/config.ts b/src/config.ts index a4792ee..e2b84ac 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,6 +24,14 @@ export interface CourierConfig { pickupStoreId: string; enabled: boolean; }; + paperfly: { + apiKey: string; + username: string; + password: string; + storeName: string; + baseUrl: string; + enabled: boolean; + }; } export type EnvSource = Record; @@ -62,5 +70,13 @@ export function loadConfig(src: EnvSource = process.env): CourierConfig { pickupStoreId: src.REDX_PICKUP_STORE_ID || "", 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), + }, }; } diff --git a/src/http.ts b/src/http.ts index 6e2006b..e82c1c7 100644 --- a/src/http.ts +++ b/src/http.ts @@ -35,6 +35,10 @@ function keysFromRequest(req: Request): EnvSource { REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"), 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"), + 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-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-Expose-Headers", "mcp-session-id"); diff --git a/src/registry.ts b/src/registry.ts index 97c7bef..07aad08 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -3,6 +3,7 @@ import { SteadfastAdapter } from "./adapters/steadfast.js"; import { PathaoAdapter } from "./adapters/pathao.js"; import { FraudRiskEngine } from "./adapters/fraud_engine.js"; import { RedxAdapter } from "./adapters/redx.js"; +import { PaperflyAdapter } from "./adapters/paperfly.js"; import { loadConfig, CourierConfig } from "./config.js"; import { BalanceResponse, @@ -36,6 +37,15 @@ export class CourierRegistry { const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId); 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() { diff --git a/src/server.ts b/src/server.ts index 96c6963..c7680f4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,11 +16,11 @@ export const TOOLS: Tool[] = [ }, { 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: { type: "object", 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)" }, recipient_name: { type: "string", description: "Customer full name" }, recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" }, @@ -43,7 +43,7 @@ export const TOOLS: Tool[] = [ type: "object", properties: { 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"], },