pralhadstha/nepal-can-php-sdk
PHP SDK for Nepal Can Move (NCM) shipping API. Create and manage shipments, track orders, calculate delivery rates, list branches, handle COD and returns/exchanges, manage tickets/staff, and process webhooks with typed resources. PHP 8.1+ with Guzzle.
Installation:
composer require pralhadstha/nepal-can-php-sdk
Initialize Client (use sandbox for testing):
use OmniCargo\NepalCan\Client;
$client = new Client(config('services.nepalcan.api_token'));
First Use Case: Fetch branch details for a specific location:
$branch = $client->branches->list()->first(fn($b) => $b->name === 'TINKUNE');
shipments->create(), shipments->find()tracking->track(), tracking->getStatusHistory()rates->calculate()webhooks->parse() (for Laravel, use the dedicated package)// Create order with COD
$order = $client->shipments->create([
'name' => 'Customer Name',
'phone' => '98XXXXXXXX',
'cod_charge' => '1500',
'address' => 'Customer Address',
'fbranch' => 'TINKUNE', // From branch
'branch' => 'KATHMANDU', // To branch
'package' => 'Product Description',
'vref_id' => 'INV-123', // Your internal order ID
'delivery_type' => RateService::TYPE_PICKUP_COLLECT, // Door2Door
]);
// Store order ID in your DB with $order->orderId
// Poll for status updates (cron job)
$statuses = $client->tracking->getStatusHistory($orderId);
$lastStatus = end($statuses)->status;
// Update your DB
Order::where('ncm_order_id', $orderId)->update(['status' => $lastStatus]);
// routes/web.php
Route::post('/nepalcan-webhook', function (Request $request) {
$webhook = $client->webhooks->parse($request->getContent());
$dispatcher->dispatch($webhook);
});
// Calculate rate during cart checkout
$rate = $client->rates->calculate(
'TINKUNE', // From branch
'KATHMANDU', // To branch
RateService::TYPE_PICKUP_COLLECT,
weight: 1.5
);
$deliveryFee = $rate->charge;
Service Provider Binding:
// app/Providers/NepalCanServiceProvider.php
public function register()
{
$this->app->singleton(Client::class, function ($app) {
return new Client(config('services.nepalcan.api_token'));
});
}
Event-Driven Webhooks:
// app/Providers/EventServiceProvider.php
protected $listen = [
WebhookEvent::DELIVERY_COMPLETED => [
\App\Listeners\UpdateOrderStatus::class,
],
];
Command for Bulk Status Checks:
// app/Console/Commands/CheckNepalCanOrders.php
public function handle()
{
$pendingOrders = Order::whereNull('last_ncm_status')->get();
foreach ($pendingOrders as $order) {
$status = $client->tracking->track($order->ncm_tracking_id);
$order->update(['status' => $status->lastDeliveryStatus]);
}
}
API Token Management:
.env:
NEPALCAN_SANDBOX_TOKEN=your_sandbox_token
NEPALCAN_PRODUCTION_TOKEN=your_prod_token
Idempotency in Webhooks:
$key = $webhook->orderId . ':' . $webhook->event;
if (ProcessedWebhook::where('key', $key)->exists()) return;
Rate Calculation Edge Cases:
"1.5").RateService::TYPE_PICKUP_COLLECT).Error Handling Quirks:
ValidationException contains a getErrors() method with field-specific validation messages.NotFoundException is thrown for invalid order IDs (404).Enable Guzzle Debugging:
$client = new Client($token, [], [
'debug' => true,
'handler' => HandlerStack::create([
new \GuzzleHttp\Middleware::tap(function ($request) {
\Log::debug('NCM Request:', $request->getBody());
}),
]),
]);
Webhook Validation:
User-Agent header matches NCM's expected value:
if (!$client->webhooks->isValidUserAgent($request->header('User-Agent'))) {
abort(403);
}
Rate Limiting:
$rate = Cache::remember("ncm_rate_{$from}_{$to}", now()->addHours(1), function () use ($client, $from, $to) {
return $client->rates->calculate($from, $to, $type);
});
Custom Webhook Handlers:
WebhookHandlerInterface for domain-specific logic:
class CodReceivedHandler implements WebhookHandlerInterface
{
public function handle(Webhook $webhook): void
{
if ($webhook->event === WebhookEvent::DELIVERY_COMPLETED) {
$this->processCodPayment($webhook->orderId);
}
}
}
Decorate the Client:
$client = new class($originalClient) extends Client {
public function __call($method, $args) {
\Log::info("Calling NCM {$method} with args: " . json_encode($args));
return parent::__call($method, $args);
}
};
Override Resources:
class ExtendedOrder extends Order
{
public function getDeliveryETA(): ?Carbon
{
$status = $this->statusHistory->last();
return $status ? Carbon::parse($status->addedTime)->addHours(24) : null;
}
}
Config::BASE_URL_PRODUCTION explicitly for live:
$client = new Client($token, Config::BASE_URL_PRODUCTION);
$client = new Client($token, [], ['timeout' => 10]);
$mockClient = Mockery::mock(Client::class);
$mockClient->shouldReceive('shipments->create')
->once()
->andReturn(new Order(['orderId' => 12345]));
$payload = file_get_contents('tests/fixtures/webhook_delivery_completed.json');
$webhook = $client->webhooks->parse($payload);
$this->assertEquals(WebhookEvent::DELIVERY_COMPLETED, $webhook->event);
How can I help you explore Laravel packages today?