EasyParcel
The fulfillment provider, and what is still needed to switch it on.
apps/backend/src/modules/easyparcel/ — registered as a provider of the
fulfillment module. Provider identifier: easyparcel.
Connecting it
- Sign in to the backend admin in your browser.
- Open Shipping → Connect or renew EasyParcel.
- Sign in to EasyParcel, approve, and return using the same browser session.
Both GET /easyparcel/oauth/authorize and GET /easyparcel/oauth/callback
require an authenticated user session. Bearer-only callers cannot initiate
this browser flow. State expires after ten minutes and can be used once, only
by its initiating admin session. Restart from Shipping if it expires.
The daily refresh job probes quotations and warns about a failing or expiring grant. Token expiry/rotation is coordinated across clients using a database row lock in the existing token store. Check current status in Shipping; do not infer live connectivity from the presence of credentials alone.
The APIs
The primary integration uses the Open API with OAuth: quotations at
/shipment/quotations and booking at /submit-orders. The legacy Individual
API client remains a fallback. Its service ids differ, so verify the courier
before using a legacy booking for an Open API quote.
The shipping option owns the courier service_id. Caller-supplied method data
cannot override it. Quote cache keys include route, weight and parcel value.
Credit, not invoicing
EasyParcel works from pre-funded credit. EPPayOrderBulk fails when the
balance is short, and it fails at booking time — after the customer has already
paid. Worth monitoring before busy periods.
Warehouse
Origin is Jitra, Kedah, set up by
apps/backend/src/scripts/setup-shipping-my.ts, with a service zone covering
all of Malaysia.
Shipping options return an empty list — even with a valid cart and address —
unless the sales channel is linked to the stock location via
linkSalesChannelsToStockLocationWorkflow. This is the single most confusing
failure in the shipping setup, because nothing errors; the options are just
absent. The setup script is step-wise idempotent because the first version
bailed early and left the configuration half-built.
Weights
Rates are priced by weight, so the catalogue's weight data is part of this integration working:
- 4,404 variants have weights imported from Shopify
- 41 have none
- 6 look wrong by a decimal — an alternator recorded at 290 kg is the clearest case
Fix both before switching on live rates, or those products will quote absurd delivery prices.
Switching it on
- Confirm which EasyParcel API the account has.
- Set
EASYPARCEL_API_KEYandEASYPARCEL_ENVon the Railway service. - Redeploy.
- Confirm the Gateway mode widget shows Shipping as
LIVErather thanOFF. - Place a test order to a real address and check the quoted rate is sane.