Delivery
Parcel
Parcel delivery provider — OAuth2 authentication, multi-region support, and real-time tracking.
The @commercejs/delivery-parcel package implements the DeliveryProvider interface for Parcel, a multi-region last-mile delivery service with OAuth2 authentication.
Installation
pnpm add @commercejs/delivery-parcel
Quick Start
import { ParcelDeliveryProvider } from '@commercejs/delivery-parcel'
const parcel = new ParcelDeliveryProvider({
clientId: process.env.PARCEL_CLIENT_ID!,
clientSecret: process.env.PARCEL_CLIENT_SECRET!,
region: 'SA-riyadh',
})
// Estimate delivery fee
const estimate = await parcel.estimate({
origin: {
contactName: 'Store',
contactPhone: '+966500000000',
firstLine: 'Olaya St, Riyadh',
latitude: 24.7136,
longitude: 46.6753,
},
destination: {
contactName: 'Customer',
contactPhone: '+966512345678',
firstLine: 'King Fahd Rd, Riyadh',
latitude: 24.7743,
longitude: 46.7386,
},
})
Configuration
| Option | Type | Required | Description |
|---|---|---|---|
clientId | string | ✅ | OAuth2 client ID |
clientSecret | string | ✅ | OAuth2 client secret |
webhookSecret | string | — | Secret for webhook payload verification |
region | string | — | Region header (e.g., SA-riyadh, BH-manama) |
baseUrl | string | — | API base URL (default: https://api.tryparcel.com/api) |
fetchFn | typeof fetch | — | Custom fetch function for testing |
Authentication
Parcel uses OAuth2 client_credentials flow. The provider handles this automatically:
- Acquires an access token on the first API call
- Caches the token and reuses it for subsequent calls
- Refreshes 60 seconds before expiry
- Auto-retries with a fresh token on 401 responses
You never need to manage tokens manually — the provider handles the full OAuth2 lifecycle.
Methods
estimate
Estimates the delivery fee between two points:
const estimate = await parcel.estimate({
origin: { latitude: 24.7136, longitude: 46.6753 },
destination: { latitude: 24.7743, longitude: 46.7386 },
})
console.log(estimate.fee) // 12.5
console.log(estimate.currency) // 'SAR'
console.log(estimate.estimatedDuration) // 20 (minutes)
console.log(estimate.estimatedDistance) // 7000 (meters)
createDelivery
Creates a new delivery task:
const delivery = await parcel.createDelivery({
origin: {
contactName: 'Shop Manager',
contactPhone: '+966500000000',
firstLine: 'King Abdullah Road',
latitude: 24.7136,
longitude: 46.6753,
},
destination: {
contactName: 'Ahmed',
contactPhone: '+966512345678',
firstLine: 'Al Malqa District',
latitude: 24.8021,
longitude: 46.6271,
instructions: 'Leave at reception',
},
orderId: 'order_123',
payment: { amount: 50, type: 'cash' }, // COD support
})
Set
payment.type to 'cash' for cash-on-delivery (COD) orders. Any other value maps to 'prepaid'.getDelivery
Retrieves the current state of a delivery:
const delivery = await parcel.getDelivery('TR-001')
console.log(delivery.status) // 'in_transit'
console.log(delivery.trackingUrl) // 'https://track.tryparcel.com/TR-001'
console.log(delivery.driver) // { name: 'Mohammed', phone: '...', latitude: ..., longitude: ... }
cancelDelivery
Cancels an active delivery:
const cancelled = await parcel.cancelDelivery('TR-001')
console.log(cancelled.status) // 'cancelled'
Status Mapping
Parcel task statuses are normalized to the DeliveryStatus union:
| Parcel Status | DeliveryStatus |
|---|---|
Unassigned | pending |
Acquiring Location | pending |
Assigned | assigned |
In Progress | in_transit |
Completed | delivered |
Successful | delivered |
Canceled | cancelled |
Location Inquiry Expired | failed |
Webhook Verification
Parcel embeds a WebhookSecret in the payload body. Configure the secret in the provider to enable verification:
const parcel = new ParcelDeliveryProvider({
clientId: process.env.PARCEL_CLIENT_ID!,
clientSecret: process.env.PARCEL_CLIENT_SECRET!,
webhookSecret: process.env.PARCEL_WEBHOOK_SECRET!,
})
const event = await parcel.verifyWebhook(request.body, '')
console.log(event.type) // 'delivery.updated' | 'delivery.location'
console.log(event.deliveryId) // 'TR-001'
console.log(event.status) // 'delivered'
console.log(event.location) // { latitude: 24.73, longitude: 46.69 }
Hook Types
| Hook Type | Event Type |
|---|---|
taskUpdate | delivery.updated |
driverLocation | delivery.location |
Multi-Region Support
Parcel operates across multiple regions. Set the region option to target a specific city:
| Region | Code |
|---|---|
| Riyadh | SA-riyadh |
| Jeddah | SA-jeddah |
| Manama | BH-manama |
The region is sent as a custom HTTP header with every API request.
Using with the Orchestrator
import { createCommerce } from '@commercejs/core'
import { ParcelDeliveryProvider } from '@commercejs/delivery-parcel'
const commerce = createCommerce({
adapter,
delivery: {
parcel: new ParcelDeliveryProvider({
clientId: process.env.PARCEL_CLIENT_ID!,
clientSecret: process.env.PARCEL_CLIENT_SECRET!,
region: 'SA-riyadh',
}),
},
defaultDelivery: 'parcel',
})