Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Kiriminaja Php Laravel Package

kiriminaja/kiriminaja-php

PHP SDK for KiriminAja, the Indonesian multi-courier shipping aggregator. Compare rates across couriers, create orders, schedule pickups, print labels, and track shipments from one simple API wrapper that works with Laravel or any plain PHP app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin the version. The package is a thin SDK with a small user base (13 stars), so lock the version and treat upgrades as intentional:

composer require kiriminaja/kiriminaja-php

Where to look first, in order:

  1. README.md — the examples are the actual API surface; method names are the contract.
  2. src/ — confirm the constructor signature (usually API key + sandbox/production flag) and the shape of returned responses.
  3. The response envelope — nearly every call returns something like status, message, data. This matters more than the method list, because you'll write all your error handling against it.

Configure credentials per environment. Keep the sandbox flag and key out of code:

// config/services.php
'kiriminaja' => [
    'key'     => env('KIRIMINAJA_API_KEY'),
    'sandbox' => env('KIRIMINAJA_SANDBOX', true),
],
KIRIMINAJA_API_KEY=
KIRIMINAJA_SANDBOX=true

Bind it once. Don't new KiriminAja(...) inside controllers. Register a singleton (the package likely has no Laravel auto-discovery, so add this manually):

// app/Providers/AppServiceProvider.php
$this->app->singleton(KiriminAja::class, fn () => new KiriminAja(
    config('services.kiriminaja.key'),
    config('services.kiriminaja.sandbox'),
));

Better still, bind it to your own ShippingGateway interface so the package is swappable.

First use case: rate check (cek ongkir). This is the lowest-risk entry point — it's read-only, easy to verify against the sandbox, and it forces you to solve the destination-ID problem early:

  1. Pull provinces, then cities/districts/subdistricts for the destination.
  2. Call the shipping-price endpoint with origin, destination IDs, weight, and courier list.
  3. Print the results before writing any UI.

Once rates work, pickup creation and tracking follow the same shape.

Implementation Patterns

Wrap the SDK behind your own service. The vendor class is a detail. Create app/Services/Shipping/KiriminAjaGateway.php that translates your domain objects (Order, Address, Shipment) into SDK calls and back into DTOs. When the package is abandoned or you move to direct HTTP calls, only this file changes.

Cache master data aggressively. Provinces/cities/districts/subdistricts are near-static and are the slowest calls. Cache them (Redis or a regions table) and expose a search endpoint for your checkout dropdown:

$provinces = Cache::remember('kiriminaja.provinces', now()->addDay(), fn () =>
    $gateway->provinces()
);

Store region IDs as strings — they are codes, not integers.

Normalize the rate response into your own structure. The vendor returns per-courier, per-service rows with inconsistent field names and price types. Collapse to something your UI and pricing logic can reason about:

final class RateOption {
    public function __construct(
        public string $courier,   // 'jne'
        public string $service,   // 'REG'
        public int $price,        // integer rupiah, no formatting
        public string $etd,       // '2-3 hari'
    ) {}
}

Convert to integer rupiah at the boundary. Never let formatted strings like "Rp 12.000" reach your database or price comparison.

Create pickups through a queued job, keyed by your order reference. Pickup creation is a write operation that allocates an AWB; it must not run in a web request. Persist your order first, then dispatch:

final class RequestPickup implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public int $tries = 3;
    public array $backoff = [30, 120];

    public function __construct(public int $orderId) {}

    public function handle(KiriminAjaGateway $gateway): void
    {
        $order = Order::with('items', 'destination')->findOrFail($this->orderId);

        if ($order->awb) {
            return; // already created — idempotent by state
        }

        $result = $gateway->requestPickup($order);

        $order->update([
            'awb'          => $result->awb,
            'pickup_code'  => $result->pickupCode,
            'shipping_state' => 'pickup_requested',
        ]);
    }
}

Guard on your own persisted state (awb, pickup_code) rather than relying on the retry to be harmless. Blind retries are the number-one source of duplicate pickups.

Track with webhooks as the source of truth, polling as reconciliation. Add a webhook controller that returns 200 immediately, writes the raw payload to a shipment_events table, and dispatches a processing job:

public function __invoke(Request $request)
{
    ShipmentEvent::create([
        'awb'      => $request->input('awb'),
        'status'   => $request->input('status'),
        'payload'  => $request->all(),
        'event_id' => $request->input('id'), // dedupe key if provided
    ]);

    ProcessShipmentEvent::dispatch();
    return response()->noContent();
}

Make processing idempotent on (awb, status, occurred_at) — couriers re-send events freely. Then run a nightly SyncShipmentStatus job that calls tracking for anything not updated in 24h, so dropped webhooks don't leave orders stuck.

Model shipment status as a state machine, not a boolean. Map the vendor's status strings to your own enum early; the raw values vary per courier and change without notice:

enum ShippingState: string {
    case PickupRequested = 'pickup_requested';
    case PickedUp        = 'picked_up';
    case InTransit       = 'in_transit';
    case Delivered       = 'delivered';
    case Returned        = 'returned';
    case Cancelled       = 'cancelled';
}

Cancel pickups through the same job pattern, and only while the state is PickupRequested. After pickup, cancellation is a return, not a cancel — decide that in your own domain layer, not the vendor's.

Make it testable. Bind the interface in tests to a fake, and use sandbox mode plus Http::fake() for a small contract test suite. Assert on your DTOs, not on vendor array keys, so package updates don't break 40 tests.

Gotchas and Tips

HTTP 200 does not mean success. The API commonly returns 200 OK with status: false and an Indonesian message. Check the envelope's status field explicitly and map failures to your own exception type. If you only check $response->status(), failed pickups will silently look like successes.

Never let sandbox mode reach production. The flag is a config value; a copy-pasted .env will happily create fake AWBs in prod. Add a boot-time assertion in a service provider:

abort_if(app()->isProduction() && config('services.kiriminaja.sandbox'), 500, 'KiriminAja sandbox enabled in production');

Also log the mode on every gateway call during rollout, and use a distinctly different reference prefix for sandbox orders.

Weight is where the money leaks. Couriers bill volumetric weight: max(actual_kg, L×W×H / 6000). Pass the chargeable weight, not the scale weight, or you underpay rates and get surcharged — or overpay on every shipment. Compute it once in a ChargeableWeight value object used by both rate checks and pickup payloads, so quotes match invoices.

Destination IDs are the friction point. You need district/subdistrict IDs, not city names, and some couriers require a deeper level than others. Resolve IDs at checkout and store them on the address; don't re-resolve at pickup time, when the customer is gone and the mapping has drifted.

Don't retry pickup creation on timeout. A network timeout is ambiguous: the pickup may exist. On ConnectionException, query your order state and the tracking endpoint before re-dispatching. Retrying with backoff ([30, 120] and $tries = 3) without this check will create duplicate pickups and duplicate billing.

Batch and queue rate checks. A product page that calls shipping price per variant will get throttled. Cache quotes per (origin, destination, weight, courier) for 5–15 minutes — enough for a checkout session, short enough to absorb price changes.

Redact logs. The payload contains customer names, phone numbers, and addresses, and the API key lives in the client. Log the order reference, AWB, and status — not the body. If you need bodies for debugging, gate them behind a config flag off by default.

Watch timezones and pickup windows. Pickup scheduling is in Asia/Jakarta. Store timestamps in UTC, convert at the boundary, and validate that the requested window is in the future before calling the API — otherwise you get a cryptic vendor error late in the job.

Budget for package risk. At 13 stars and a thin wrapper, expect gaps: missing methods, undocumented endpoints, no typed responses. Two mitigations: (1) keep the gateway interface narrow so replacing the SDK with direct Http::withToken(...) calls is a one-file change, and (2) check the package's PHP/Laravel version constraints before upgrading either.

Extension points are limited, so extend around it, not through it. There's no event system or middleware to hook. Put cross-cutting concerns — logging, metrics, circuit breaking, retries — in your gateway wrapper or in Laravel's HTTP client if you drop to direct calls.

Map error messages to user-facing copy. Vendor validation messages arrive in Indonesian and are often terse. Keep a small translation table (invalid destination, weight exceeded, courier unavailable) and fall back to a generic "shipping is temporarily unavailable" so raw API strings never hit your checkout UI.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify