feat: Paperfly courier adapter (create + track); v2.3.0 — all 4 couriers
This commit is contained in:
+60
-57
@@ -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<ParcelResponse> {
|
||||
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<TrackingResponse> {
|
||||
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<BalanceResponse> {
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user