dinas/shipping-sdk-laravel

Laravel client for Dinas Shipping API

Maintainers

Package info

github.com/DinasJp/shipping-sdk-laravel

Homepage

pkg:composer/dinas/shipping-sdk-laravel

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.0 2026-02-27 08:17 UTC

This package is auto-updated.

Last update: 2026-02-27 10:19:06 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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.

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 https://shipping.dinas.jp/settings/tokens.

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

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

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

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)

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)

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

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 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

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

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

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

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:

<?php

namespace App\Jobs\DinasWebhooks;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\WebhookClient\Models\WebhookCall;

class TestJob implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public WebhookCall $webhookCall)
    {
    }

    public function handle(): void
    {
        $payload = $this->webhookCall->payload;
        
        // do your work here
    }
}

We highly recommend that you make this job queueable, because this will minimize the response time of the webhook requests. This allows you to handle more webhook requests and avoid timeouts.

After having created your job you must register it at the jobs array in the dinas-shipping-sdk.php config file.

// config/dinas-shipping-sdk.php

'jobs' => [
    'car.updated' => \App\Jobs\DinasWebhooks\HandleCarUpdated::class,
],

You can find the full list of events types in the documentation.

In case you want to configure one job as default to process all undefined event, you may set the job at default_job in the dinas-shipping-sdk.php config file. The value should be the fully qualified classname.

Advanced usage

Retry handling a webhook

All incoming webhook requests are written to the database. This is incredibly valuable when something goes wrong while handling a webhook call. You can easily retry processing the webhook call, after you've investigated and fixed the cause of failure, like this:

use Spatie\WebhookClient\Models\WebhookCall;
use Spatie\WebhookClient\Jobs\ProcessWebhookJob;

dispatch(new ProcessWebhookJob(WebhookCall::find($id)));

More Information

For more information on how to use the underlying SDK, please refer to the spatie webhook client

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.