paperfly_courier

Official Dart/Flutter client for the Paperfly (Bangladesh) merchant courier API. Create parcels and exchange orders, track shipments, cancel orders, and parse webhook events — all with typed requests, typed responses, and typed errors.

Works in any Dart or Flutter app (mobile, web, desktop, server/CLI); it has no Flutter SDK dependency, so it's just as usable from a Dart backend.

Features

  • createOrder — create a parcel, or an exchange order via PaperflyOrderRequest.exchange.
  • trackOrder — look up delivery status, with a ready-made chronological timeline.
  • cancelOrder — cancel a parcel.
  • PaperflyWebhookEvent — parse inbound webhook payloads for all 14 documented event types.
  • PaperflyWebhookVerifier — check a webhook's shared secret.
  • Typed exceptions (PaperflyAuthException, PaperflyApiException, PaperflyNetworkException, PaperflyDecodeException) instead of raw HTTP responses to parse yourself.

Getting started

Add the package:

flutter pub add paperfly_courier

or, for a plain Dart project:

dart pub add paperfly_courier

You'll need three values from your Paperfly merchant dashboard (go.paperfly.com.bd/merchant/dashboardDeveloper & PluginsAPI Docs):

  • Your merchant panel User Name
  • Your merchant panel Password
  • The paperflykey header value shown on that page

Security note: never hard-code these in a public client app, and never embed them in an app you distribute. Treat them like any other API secret — load them from secure configuration and, where possible, make Paperfly calls from your own backend rather than directly from a mobile or web client.

Usage

Create a parcel

import 'package:paperfly_courier/paperfly_courier.dart';

final client = PaperflyClient(
  merchantUsername: 'YOUR_MERCHANT_USERNAME',
  merchantPassword: 'YOUR_MERCHANT_PASSWORD',
  paperflyKey: 'YOUR_PAPERFLY_KEY',
);

try {
  final order = await client.createOrder(
    PaperflyOrderRequest(
      merchantOrderReference: 'ORDER-1001', // must be unique per order
      storeName: 'My Shop',
      productBrief: 'Blue T-Shirt (L)',
      packagePrice: 590,
      maxWeight: 0.5,
      customerName: 'Jane Doe',
      customerAddress: 'House 12, Road 5, Banani, Dhaka',
      customerPhone: '01700000000',
    ),
  );

  print(order.trackingNumber); // e.g. "Z-051125-63821-A3-A1"
  print(order.trackingBarcode);
} on PaperflyAuthException catch (e) {
  // Bad username/password/paperflykey.
} on PaperflyApiException catch (e) {
  // Paperfly rejected the request — see e.message / e.responseBody.
} on PaperflyNetworkException catch (e) {
  // Couldn't reach Paperfly at all (offline, timeout, etc).
}

Create an exchange order

Paperfly uses the same endpoint for exchange orders — just set exchange:

final order = await client.createOrder(
  PaperflyOrderRequest(
    merchantOrderReference: 'ORDER-1002',
    storeName: 'My Shop',
    productBrief: 'Blue T-Shirt (L)',
    packagePrice: 590,
    maxWeight: 0.5,
    customerName: 'Jane Doe',
    customerAddress: 'House 12, Road 5, Banani, Dhaka',
    customerPhone: '01700000000',
    exchange: PaperflyExchangeDetails(
      description: 'Red T-Shirt (M) — exchanged for blue L',
      price: 590,
      weight: 0.5,
    ),
  ),
);

Track a parcel

final tracking = await client.trackOrder('ORDER-1001');

for (final milestone in tracking.latest?.timeline ?? const []) {
  print('${milestone.stage}: ${milestone.time}');
}

print(tracking.latest?.isDelivered); // true / false

Cancel a parcel

final cancelled = await client.cancelOrder('ORDER-1001');
print(cancelled.message);

Handling webhooks

Configure a webhook URL and secret under Developer & Plugins → Webhooks in the dashboard, then parse incoming requests on your server with PaperflyWebhookEvent:

import 'dart:convert';
import 'package:paperfly_courier/paperfly_courier.dart';

// Inside your webhook handler, given the raw request body:
void handleWebhook(String requestBody) {
  final event = PaperflyWebhookEvent.fromJson(jsonDecode(requestBody));

  switch (event.type) {
    case PaperflyWebhookEventType.parcelDelivered:
      // Mark the order delivered in your own system.
      break;
    case PaperflyWebhookEventType.parcelCancelled:
      // ...
      break;
    default:
      // See PaperflyWebhookEventType for the full list of 14 events.
      break;
  }

  print(event.data.orderNumber);
  print(event.data.merchantOrderReference);
  print(event.data.recipient.name);
}

Paperfly's dashboard states that the secret you configure "will be sent in the header for verification", without documenting the exact header name or signing scheme. Compare whatever header your server receives against your configured secret with PaperflyWebhookVerifier.isValid:

final isGenuine = PaperflyWebhookVerifier.isValid(
  request.headers['x-paperfly-secret'], // adjust to the header you observe
  configuredSecret,
);

Webhooks arrive on your server, not inside the Flutter app itself — wire this up in whatever backend (Cloud Function, Node/PHP/Dart server, etc.) receives the webhook, not in client-side app code.

Full example

See example/lib/main.dart for a small Flutter app that creates, tracks, and cancels a parcel, and example/paperfly_courier_example.dart for a plain-Dart version.

API coverage

This package covers every endpoint documented on the merchant dashboard's Developer & Plugins → API Docs page as of writing:

Operation Endpoint
Create order POST /merchant/api/service/new_order_v2.php
Create exchange POST /merchant/api/service/new_order_v2.php (with exchange fields)
Track order POST /API-Order-Tracking
Cancel order POST /api/v1/cancel-order

All requests are authenticated with HTTP Basic Auth (your merchant panel username/password) plus a paperflykey header.

Error handling

Every failure surfaces as a PaperflyException subtype instead of a raw http.Response:

  • PaperflyAuthException — HTTP 401/403, or bad credentials.
  • PaperflyApiException — any other API-reported failure (non-2xx status, or a 200 whose body reports failure). Carries statusCode, responseCode, and the raw responseBody.
  • PaperflyNetworkException — the request couldn't be sent at all (offline, DNS failure, timeout, etc). Carries the original cause.
  • PaperflyDecodeException — Paperfly's response wasn't valid JSON.

Contributing

Issues and pull requests are welcome. Please run dart format ., dart analyze, and dart test before submitting.

License

MIT — see LICENSE.

Libraries

paperfly_courier
The official Dart/Flutter client for the Paperfly (Bangladesh) merchant courier API.