redx_courier
A Dart/Flutter client for the RedX courier OpenAPI
(https://openapi.redx.com.bd/v1.0.0-beta): create parcels, resolve a
delivery area from a free-text customer address, and list your pickup
stores.
Companion package to paperfly_courier.
Install
dependencies:
redx_courier: ^0.0.1
Getting your access token
Log in to your RedX merchant dashboard and grab your API access token from the Developer/API section. It's sent on every request as:
API-ACCESS-TOKEN: Bearer <your token>
Finding your pickup store ID (read this first)
This is the single most common integration mistake with RedX, and it's why this package puts it front and center.
RedX has two different IDs that look interchangeable but aren't:
- Your shop ID - the number in your merchant dashboard URL, and also
the
shop_idfield baked into your access token's own JWT payload. - Your pickup store ID - a separate ID for each physical pickup
location registered under your shop, used for the
pickup_store_idfield when creating a parcel.
It's natural to assume they're the same thing, or to grab the shop ID out
of your JWT and use that as pickup_store_id - RedX's own docs don't call
this out. If you do, createParcel() will fail with:
{"message": "Cannot read property 'PHONE' of undefined"}
That error is RedX's backend trying to read a phone number off a pickup
store that doesn't exist under the ID you sent (because it was actually
your shop ID). Same symptom no matter what else you change in the
request - a different cash_collection_amount, phone formatting, or the
type field will not fix it.
The fix: always look up your real pickup store ID at runtime instead of hardcoding a number you found in a URL or a JWT:
final client = RedxClient(accessToken: 'YOUR_REDX_ACCESS_TOKEN');
final stores = await client.getPickupStores();
for (final store in stores) {
print('${store.name} -> id: ${store.id}, phone: ${store.phone}');
}
final pickupStoreId = stores.first.id; // use this, not your shop ID
You can also sanity-check a specific ID with getPickupStoreInfo() - it
returns null (instead of throwing) when the ID you passed turns out to be
a shop ID rather than a pickup store ID:
final store = await client.getPickupStoreInfo(1040634);
if (store == null) {
print('That ID is not a pickup store - probably your shop ID instead.');
}
Usage
import 'package:redx_courier/redx_courier.dart';
Future<void> main() async {
final client = RedxClient(accessToken: 'YOUR_REDX_ACCESS_TOKEN');
final stores = await client.getPickupStores();
final pickupStoreId = stores.first.id;
// Resolves a free-text address to the right RedX delivery area, e.g.
// matching "House 5, Road 12, Gulshan 2" to an area named "Gulshan-2".
final area = await client.resolveAreaForAddress(
districtName: 'Dhaka',
customerAddress: 'House 5, Road 12, Gulshan 2, Dhaka',
);
if (area == null) {
// No confident match - fall back to client.getAreasByDistrict('Dhaka')
// and let the customer pick their area explicitly.
return;
}
final result = await client.createParcel(
RedxParcelRequest(
customerName: 'Test Customer',
customerPhone: normalizeBdPhoneForRedx('+8801712345678') ?? '',
customerAddress: 'House 5, Road 12, Gulshan 2, Dhaka',
areaId: area.id,
areaName: area.name,
merchantInvoiceId: 'INV-0001',
cashCollectionAmount: 500, // 0 for a prepaid order
parcelWeight: 1,
value: 500,
itemName: 'Sample Item',
pickupStoreId: pickupStoreId,
),
);
print('success: ${result.success}, tracking: ${result.trackingId}');
client.close();
}
See example/example.dart for the full runnable
version.
API
RedxClient.getPickupStores()- list pickup stores under your shop.RedxClient.getPickupStoreInfo(int pickupStoreId)- look up one store;nullif the ID isn't a real pickup store.RedxClient.getAreasByDistrict(String districtName)- raw area lookup, with automatic retry under known alternate district spellings (Barisal/ Barishal, Bogra/Bogura, Chattogram/Chittagong, Comilla/Cumilla, Jashore/ Jessore).RedxClient.resolveAreaForAddress(...)- best-match area for a district- free-text address;
nullif nothing matches confidently.
- free-text address;
RedxClient.createParcel(RedxParcelRequest request)- create a parcel.RedxClient.trackParcel(String trackingId)- track a parcel.normalizeBdPhoneForRedx(String? phone)- normalizes a phone number to the local01XXXXXXXXXformat RedX expects (a+880/880-prefixed number sent as-is has also been observed to trigger the same "Cannot read property 'PHONE' of undefined" crash as the wrong pickup store ID, so normalize phone numbers too).
License
MIT, see LICENSE.
Libraries
- redx_courier
- A Dart/Flutter client for the RedX (redx.com.bd) courier OpenAPI.