PHPackages                             dinas/shipping-sdk-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. dinas/shipping-sdk-laravel

ActiveLibrary[API Development](/categories/api)

dinas/shipping-sdk-laravel
==========================

Laravel client for Dinas Shipping API

v1.2.0(2mo ago)03[2 PRs](https://github.com/DinasJp/shipping-sdk-laravel/pulls)MITPHPPHP ^8.2CI passing

Since Jan 9Pushed 1mo agoCompare

[ Source](https://github.com/DinasJp/shipping-sdk-laravel)[ Packagist](https://packagist.org/packages/dinas/shipping-sdk-laravel)[ Docs](https://github.com/dinasjp/shipping-sdk-laravel)[ RSS](/packages/dinas-shipping-sdk-laravel/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (15)Versions (6)Used By (0)

Laravel client for Dinas Shipping API
=====================================

[](#laravel-client-for-dinas-shipping-api)

[![Latest Version on Packagist](https://camo.githubusercontent.com/f7637a998b3e3b758e7f5a051d58511e83fa4c54771ff558522cc04df95e6357/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f64696e61732f7368697070696e672d73646b2d6c61726176656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/dinas/shipping-sdk-laravel)[![GitHub Tests Action Status](https://camo.githubusercontent.com/3a7e5b9939da050ac0156a6cdf332362ae938921421c6bd05c5a81e8953522c8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f64696e61732f7368697070696e672d73646b2d6c61726176656c2f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/dinasjp/shipping-sdk-laravel/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/340afb98c0420b97e4efcfd28233cd8afd3b2f448d0b97a3988e4fec3c4a0e36/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f64696e61732f7368697070696e672d73646b2d6c61726176656c2f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/dinasjp/shipping-sdk-laravel/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/a1bf53c0c47598311a0935cd9c546048d12641c7d2a30ed84461288825a642f3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f64696e61732f7368697070696e672d73646b2d6c61726176656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/dinas/shipping-sdk-laravel)

This package is two-way bridge between Laravel applications and the Dinas Shipping API. You can send data to REST endpoints and handle incoming webhooks with ease.

Incoming events are automatically verified, logged, and dispatched as Laravel jobs, enabling smooth asynchronous updates such as shipment status changes or document availability.

You can find the full documentation [here](https://shipping.dinas.jp/api/documentation).

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require dinas/shipping-sdk-laravel
```

The service provider will automatically register itself.

Add the following environment variables to your `.env` file:

```
DINAS_SHIPPING_TOKEN=your-api-token-here
DINAS_SHIPPING_SECRET=your-random-webhook-secret-here
```

You can obtain your API token from the dashboard .

### Webhook Setup

[](#webhook-setup)

First, add the webhook route to your `api` or `web` route file:

```
Route::dinasShippingWebhooks('dinas-shipping/webhook');
```

You can customize the endpoint path as needed, it will be automatically discovered on setup command.

Behind the scenes, by default this will register a POST route to a controller provided by this package. Because the app that sends webhooks to you has no way of getting a csrf-token for `web`, you must exclude the route from csrf token validation.

Here is how you can do that in recent versions of Laravel.

```
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
       // ...
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->validateCsrfTokens(except: [
            'dinas-shipping/webhook'
        ]);
    })->create();
```

Next, publish the webhook migration and run it:

```
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations"
php artisan vendor:publish --tag="shipping-sdk-laravel-migrations"
php artisan migrate
```

Now, register the webhook with Dinas Shipping API by running:

```
php artisan webhook:dinas-shipping -i
```

You can check status of all webhooks with:

```
php artisan webhook:dinas-shipping
```

To remove the webhook registration, run:

```
php artisan webhook:dinas-shipping -r
```

More information is accessible with:

```
php artisan webhook:dinas-shipping --help
```

You can publish the config files with:

```
php artisan vendor:publish --tag="shipping-sdk-laravel-config"
```

Usage
-----

[](#usage)

### Cars API

[](#cars-api)

```
use Dinas\Shipping\Facades\Shipping;

// Get cars with filters
$cars = Shipping::getCars([
    'status' => \Dinas\ShippingSdk\Model\StockStatus::PENDING,
    'search' => 'Toyota',
    'port_code' => 'OSA',
    'voyage' => 'VES0001PORT',
    'vehicle_state' => 'dis', // whole, unknown, dmg
    'vehicle_type' => \Dinas\ShippingSdk\Model\VehicleType::TRACTOR,
    'photos' => true,  // Only cars with photos
    'docs' => true,    // Only cars with documents
    'on_yard' => true, // Only cars on yard
    'price_terms' => \Dinas\ShippingSdk\Model\PriceTerms::FOB,
    'sort' => '-id',
    'per_page' => 100,
    'page' => 1,
]);

// Sync cars (create or update)
$result = Shipping::syncCars([
    [
        'chassis' => 'ABC123',
        'make' => 'Toyota',
        'model' => 'Camry',
        'year' => 2020,
        'color' => 'White',
        // ... other car fields
    ],
    [
        'chassis' => 'DEF456',
        'make' => 'Honda',
        'model' => 'Civic',
        // ... other car fields
    ],
]);

// Hold cars from shipping
Shipping::holdCars(['ABC123', 'DEF456'], [
    'date' => '2026-03-15',
    'after' => true, // true = after date, false = before date
]);

// Hold without date limit
Shipping::holdCars(['ABC123', 'DEF456']);

// Release cars for shipping
Shipping::releaseCars(['ABC123', 'DEF456']);

// Withhold cars upon arrival
Shipping::withholdCars(['ABC123'], 'Payment pending');

// Withhold without reason
Shipping::withholdCars(['ABC123']);

// Grant cars (clear withhold status)
Shipping::grantCars(['ABC123']);

// Set yard ETA for cars
Shipping::setYardEta([
    ['chassis' => 'ABC123', 'eta' => '2026-02-15'],
    ['chassis' => 'DEF456', 'eta' => '2026-02-16'],
]);
```

### Photos API

[](#photos-api)

```
use Dinas\Shipping\Facades\Shipping;

// Get car photos with filters
$photos = Shipping::getCarPhotos([
    'chassis' => 'ABC123',
    'voyage' => 'VES0001PORT',
    'status' => 'pending',
    'photos' => true, // false - for list without photo uploaded yet
    'per_page' => 100,
]);

// Store car photos from URLs
// Data is automatically chunked (default: 50 items per chunk, configurable).
// Returns a StoreResult with structured errors and job IDs.
$result = Shipping::storeCarPhotos([
    [
        'chassis' => 'ABC123',
        'album' => \Dinas\ShippingSdk\Model\AlbumType::YARD_CARGO,
        'urls' => [
            'https://example.com/photo1.jpg',
            'https://example.com/photo2.jpg',
        ],
    ],
    [
        'chassis' => 'DEF456',
        'album' => 'auction',
        'urls' => [
            'https://example.com/photo3.jpg',
        ],
    ],
]);

// Check results
if (!$result->ok) {
    // Validation errors (422) as structured arrays
    foreach ($result->validationErrors as $field => $messages) {
        // e.g. ['0.chassis' => ['The chassis field is required.']]
    }
}

// API-level errors (e.g. "Car not found") as arrays
foreach ($result->errors as $error) {
    echo $error['chassis'] . ': ' . $error['error'];
}

// All error messages as a flat array of strings
$messages = $result->allErrorMessages();

// Job IDs returned from the API
$jobIds = $result->jobIds;

// Store car photos from files
$result = Shipping::storeCarPhotoFiles([
    [
        'chassis' => 'ABC123',
        'album' => \Dinas\ShippingSdk\Model\AlbumType::YARD_NOTE,
        'files' => [
            // File objects or paths
        ],
    ],
]);
```

### Documents API

[](#documents-api)

```
use Dinas\Shipping\Facades\Shipping;

// Store car documents from URLs
$result = Shipping::storeCarDocuments([
    [
        'chassis' => 'ABC123',
        'type' => \Dinas\ShippingSdk\Model\DocumentType::EXPORT_CERTIFICATE,
        'url' => 'https://example.com/cert-tohon.pdf',
        'valid_until' => '2027-01-30', // optional, but recommended for better tracking of document validity
    ],
    [
        'chassis' => 'ABC123',
        'type' => \Dinas\ShippingSdk\Model\DocumentType::VEHICLE_INVOICE,
        'url' => 'https://example.com/invoice.pdf',
    ],
    [
        'chassis' => 'DEF456',
        'type' => \Dinas\ShippingSdk\Model\DocumentType::EXPORT_CERTIFICATE,
        'url' => 'https://example.com/title.pdf',
    ],
]);

// Store car documents from files
$result = Shipping::storeCarDocumentFiles([
    [
        'chassis' => 'ABC123',
        'type' => \Dinas\ShippingSdk\Model\DocumentType::EXPORT_CERTIFICATE,
        'file' => $uploadedFile,
        'valid_until' => '2027-01-30',
    ],
]);
```

### Async Job Callbacks (`onResolve`)

[](#async-job-callbacks-onresolve)

When the API processes your photos/documents asynchronously, it returns a `jobId` and later sends a webhook (`api.job` event) when the job is complete. You can register a callback that will be automatically executed when that webhook arrives:

```
use Dinas\Shipping\Facades\Shipping;
use Dinas\Shipping\DTOs\WebhookJobContext;

$result = Shipping::storeCarDocuments($documents, onResolve: function (WebhookJobContext $context) {
    // This runs automatically when the API job finishes and the webhook is received.
    // $context->jobId    — API job ID
    // $context->userId   — User who initiated the request
    // $context->method   — 'storeCarDocuments'
    // $context->status   — 'finished' or 'failed'
    // $context->message  — Optional message from API (can be null)
    // $context->errors   — Errors from the initial API response

    if ($context->isFailed()) {
        Log::warning("Job {$context->jobId} failed: {$context->message}");
    }

    // Notify user, update records, etc.
});

// You can also use any callable:
$result = Shipping::storeCarPhotos($photos, onResolve: [MyService::class, 'handleResult']);
$result = Shipping::storeCarPhotos($photos, onResolve: 'my_handler_function');
```

The callback is serialized and stored in the `webhook_jobs` table. When the webhook arrives, the callback is deserialized and executed exactly once (duplicate webhooks are ignored via status tracking). The `user_id` of the authenticated user at the time of the call is captured and passed to the callback context.

### Broadcasting (Pusher)

[](#broadcasting-pusher)

When a webhook resolves a job, a `ShippingJobResolved` event will be broadcast on a private user's channel to notify the frontend in real-time. This is opt-out.

Disable in your `.env`:

```
DINAS_SHIPPING_BROADCASTING=false
```

Or in `config/dinas-shipping-sdk.php`:

```
'broadcasting' => [
    'enabled' => true,
],
```

Listen on the frontend (e.g. Laravel Echo + Pusher):

```
Echo.private(`App.Models.User.${userId}`)
    .listen('.shipping.job.resolved', (e) => {
        console.log(e.jobId, e.method, e.status, e.message, e.errors);
    });
```

The broadcast payload contains: `jobId`, `method`, `status`, `message`, `errors`.

### Deleting models

[](#deleting-models)

Whenever you call async method, this package will store callback as a `WebhookJob` model. After a while, you might want to delete old models.

The `WebhookJob` model has [Laravel's MassPrunable trait](https://laravel.com/docs/master/eloquent#pruning-models)applied on it. You can customize the cutoff date in the config file.

In this example all models will be deleted when older than 30 days.

```
return [
    'webhook_jobs' => [
        'delete_after_days' => 30,
    ],
];
```

After configuring the model, you should schedule the `model:prune` Artisan command in your application's `routes/console.php`. Don't forget to explicitly mention the `WebhookJob` class. You are free to choose the appropriate interval at which this command should be run:

```
use Illuminate\Support\Facades\Schedule;
use Dinas\Shipping\Models\WebhookJob;
use Spatie\WebhookClient\Models\WebhookCall;

Schedule::command('model:prune', [
    '--model' => [WebhookJob::class, WebhookCall::class],
])->daily();

// This will not work, as models in a package are not used by default
// Schedule::command('model:prune')->daily();
```

### Voyages API

[](#voyages-api)

```
use Dinas\Shipping\Facades\Shipping;

// Get all voyages
$voyages = Shipping::getVoyages([
    'per_page' => 25,
    'page' => 1,
]);

// Get a specific voyage
$voyage = Shipping::getVoyage(123);
```

### Direct API Access

[](#direct-api-access)

For full control, you can access the underlying API instances directly:

```
use Dinas\Shipping\Facades\Shipping;

// Access Cars API directly
$carsApi = Shipping::cars();
$result = $carsApi->getCars(status: 'pending', perPage: 100);

// Access Car Photos API directly
$photosApi = Shipping::carPhotos();
$photos = $photosApi->getCarPhotos(chassis: 'ABC123');

// Access Car Documents API directly
$docsApi = Shipping::carDocuments();
$docsApi->storeCarDocumentUrls($documentData);

// Access Voyages API directly
$voyagesApi = Shipping::voyages();
$voyages = $voyagesApi->getVoyages();

// Access Webhooks API directly
$webhooksApi = Shipping::webhooks();
$webhooks = $webhooksApi->getWebhooks();

// Get the SDK configuration
$config = Shipping::getConfiguration();

// Set a custom HTTP client
Shipping::setHttpClient($customClient);
```

### Dependency Injection

[](#dependency-injection)

You can also inject the `Shipping` class directly:

```
use Dinas\Shipping\Shipping;

class CarController extends Controller
{
    public function __construct(
        protected Shipping $shipping
    ) {}

    public function index()
    {
        return $this->shipping->getCars(['status' => 'pending']);
    }
}
```

### Handling webhook requests using jobs

[](#handling-webhook-requests-using-jobs)

If you want to do something when a specific event type comes in you can define a job that does the work. Here's an example of such a job:

```
