aiarmada / jnt
J&T Express Malaysia API Integration Package
v1.4.7
2025-11-06 11:02 UTC
Requires
- php: ^8.4
- aiarmada/commerce-support: v1.4.7
This package is auto-updated.
Last update: 2026-03-22 00:58:08 UTC
README
Laravel 12 integration for J&T Express Malaysia Open API – orders, tracking, waybills, and real-time webhooks.
Why this package?
- Complete API coverage – create orders, track parcels, print waybills, cancel orders, batch operations.
- Clean API naming – use
orderIdinstead oftxlogisticId,trackingNumberinstead ofbillCode. - Type-safe enums –
ExpressType::DOMESTICinstead of magic strings like'EZ'. - First-class Laravel DX – facades, fluent builders, data objects, events, Artisan commands.
- Production ready – PHP 8.4 / Laravel 12, PHPStan level 6, Pest test suite.
- Webhooks included – automatic signature verification and event dispatching.
Installation
composer require aiarmada/jnt
Publish configuration and migrations:
php artisan vendor:publish --tag="jnt-config" php artisan vendor:publish --tag="jnt-migrations" php artisan migrate
Environment Variables
JNT_ENVIRONMENT=testing # or 'production' JNT_CUSTOMER_CODE=your_customer_code JNT_PASSWORD=your_password # Production only (testing uses J&T's public sandbox credentials): JNT_API_ACCOUNT=your_api_account JNT_PRIVATE_KEY=your_private_key # Optional JNT_LOGGING_ENABLED=true JNT_WEBHOOKS_ENABLED=true
Note: When
JNT_ENVIRONMENT=testing, the package automatically uses J&T's official sandbox credentials. You only needJNT_CUSTOMER_CODEandJNT_PASSWORD.
Usage
Create an Order
use AIArmada\Jnt\Facades\JntExpress; use AIArmada\Jnt\Data\{AddressData, ItemData, PackageInfoData}; use AIArmada\Jnt\Enums\{ExpressType, ServiceType, PaymentType, GoodsType}; $sender = new AddressData( name: 'John Sender', phone: '60123456789', address: 'No 32, Jalan Kempas 4', postCode: '81930', countryCode: 'MYS', state: 'Johor', city: 'Johor Bahru', ); $receiver = new AddressData( name: 'Jane Receiver', phone: '60987654321', address: '4678, Laluan Sentang 35', postCode: '31000', countryCode: 'MYS', state: 'Perak', city: 'Batu Gajah', ); $item = new ItemData( itemName: 'Basketball', quantity: 2, weight: 10, unitPrice: 50.00, ); $packageInfo = new PackageInfoData( quantity: 1, weight: 10, declaredValue: 50, goodsType: GoodsType::PACKAGE, ); $order = JntExpress::createOrderBuilder() ->orderId('ORDER-' . time()) ->expressType(ExpressType::DOMESTIC) ->serviceType(ServiceType::DOOR_TO_DOOR) ->paymentType(PaymentType::PREPAID_POSTPAID) ->sender($sender) ->receiver($receiver) ->addItem($item) ->packageInfo($packageInfo) ->build(); $result = JntExpress::createOrderFromArray($order); echo "Tracking: " . $result->trackingNumber;
Track a Parcel
// By your order ID $tracking = JntExpress::trackParcel(orderId: 'ORDER-123'); // Or by J&T tracking number $tracking = JntExpress::trackParcel(trackingNumber: 'JT630002864925'); foreach ($tracking->details as $detail) { echo "{$detail->scanTime}: {$detail->description}\n"; }
Cancel an Order
JntExpress::cancelOrder( orderId: 'ORDER-123', reason: 'Customer requested cancellation', trackingNumber: 'JT630002864925', );
Print Waybill
$label = JntExpress::printOrder( orderId: 'ORDER-123', trackingNumber: 'JT630002864925', ); $pdfUrl = $label['urlContent'];
Enums
Type-safe enums prevent invalid values:
// Express Type ExpressType::DOMESTIC // Standard delivery ExpressType::NEXT_DAY // Express next day ExpressType::FRESH // Cold chain delivery // Service Type ServiceType::DOOR_TO_DOOR // Pickup from sender ServiceType::WALK_IN // Drop-off at counter // Payment Type PaymentType::PREPAID_POSTPAID // Prepaid by merchant PaymentType::COLLECT_CASH // Cash on delivery // Goods Type GoodsType::DOCUMENT // Documents GoodsType::PACKAGE // Parcels
Webhooks
Receive real-time tracking updates from J&T.
Setup
-
Enable webhooks in
.env:JNT_WEBHOOKS_ENABLED=true
-
Create a listener:
namespace App\Listeners; use AIArmada\Jnt\Events\TrackingStatusReceived; class UpdateOrderTracking { public function handle(TrackingStatusReceived $event): void { $order = Order::where('tracking_number', $event->trackingNumber)->first(); $order?->update([ 'tracking_status' => $event->lastStatus, 'tracking_time' => $event->scanTime, ]); } }
-
Register the listener:
// EventServiceProvider.php protected $listen = [ \AIArmada\Jnt\Events\TrackingStatusReceived::class => [ \App\Listeners\UpdateOrderTracking::class, ], ];
-
Configure J&T Dashboard with your webhook URL:
https://yourdomain.com/webhooks/jnt/status
Artisan Commands
# Check configuration php artisan jnt:config:check # Health check php artisan jnt:health # Track parcel php artisan jnt:order:track --order-id=ORDER-123 # Cancel order php artisan jnt:order:cancel --order-id=ORDER-123 --reason="Out of stock" # Print waybill php artisan jnt:order:print --order-id=ORDER-123 --tracking-number=JT123456
Documentation
- API Reference – Complete method reference
- Batch Operations – Process multiple orders efficiently
- Webhooks – Webhook integration guide
- Testing Credentials – Auto-configuration for sandbox
Property Name Mapping
The package translates clean names to J&T's API format automatically:
| Your Code | J&T API | Description |
|---|---|---|
orderId |
txlogisticId |
Your order reference |
trackingNumber |
billCode |
J&T tracking number |
state |
prov |
State/province |
quantity |
number |
Item quantity |
chargeableWeight |
packageChargeWeight |
Billable weight |
Testing
vendor/bin/pest tests/src/Jnt
Contributing
- Fork & clone the repository
- Install dependencies:
composer install - Run tests:
vendor/bin/pest - Format code:
vendor/bin/pint
License
Released under the MIT License. See LICENSE for details.