A comprehensive Laravel package for seamless integration with the Lazada Open Platform API. This package provides a clean, intuitive interface for managing Lazada sellers, handling authentication, processing orders, managing products, and receiving webhooks.
- π Complete Authentication Flow - Automatic token management with refresh capabilities
- πͺ Multi-Seller Support - Manage multiple Lazada sellers within a single application
- π¦ Product Management - Retrieve products and update sellable stock quantities
- π Order Processing - Comprehensive order management and document retrieval
- π° Finance & Transactions - Query payout status, account transactions, and finance details
- π‘ Webhook Integration - Real-time push event handling with built-in endpoints
- ποΈ Database Logging - Automatic request/response logging for debugging and monitoring
- π Auto Token Refresh - Scheduled token refresh to maintain API connectivity
- PHP 8.2 and above.
- Laravel 10 and above.
You can install the package via composer:
composer require laraditz/lazadaBefore using this package, you need a Lazada Open Platform app:
- Visit Lazada Open Platform
- Create a new app or use an existing one
- Note your
App KeyandApp Secret - Configure the App Callback URL:
https://your-app-url.com/lazada/seller/authorized
Add your Lazada credentials to your .env file:
LAZADA_APP_KEY=your_app_key_here
LAZADA_APP_SECRET=your_app_secret_here
LAZADA_SELLER_SHORT_CODE=MYXXXXXXXX # Optional: default seller short code (also called Seller ID in the console)php artisan vendor:publish --provider="Laraditz\Lazada\LazadaServiceProvider" --tag="migrations"php artisan migratephp artisan vendor:publish --provider="Laraditz\Lazada\LazadaServiceProvider" --tag="config"To authorize a seller:
- Generate and share the authorization URL with the seller:
$url = Lazada::auth()->authorizationUrl();
// Redirect the seller to this URL- The seller logs in and authorizes your app
- Lazada redirects to
https://your-app-url.com/lazada/seller/authorized - The package automatically handles the token exchange and storage
- The seller is now ready for API calls
Note: Lazada rejects a url-encoded
redirect_uri, soauthorizationUrl()passes the callback URL as-is. Because of this, the callback URL (LAZADA_APP_CALLBACK_URLor the default/lazada/seller/authorizedroute) must not contain a query string. For example,https://your-app-url.com/callback?foo=1&bar=2would break the authorization URL, because&bar=2gets read as a separate parameter of the Lazada URL. Use a plain path instead, and make sure it exactly matches the App Callback URL configured in the Lazada Open Platform console.
Here's the full list of methods available in this package. Parameters follow the Lazada Open Platform Documentation exactly. Common parameters like app_key, sign, timestamp, and access_token are added automatically.
| Method | Description |
|---|---|
authorizationUrl() |
Get the authorization URL to share with the seller. |
accessToken() |
Exchange an authorization code for an access token. |
refreshToken() |
Refresh an access token before it expires. |
accessTokenWithOpenId() |
Generate an access token using an OpenID. |
| Method | Description |
|---|---|
get() |
Fetch seller information from Lazada API and sync to DB. |
info() |
Return the resolved LazadaSeller model for the current context. |
pickUpStoreList() |
Return the list of pick-up store information for the seller. |
| Method | Description |
|---|---|
list() |
Get an order list from a specified date range. |
get() |
Get single order detail by order ID. |
items() |
Get the item information of an order. |
document() |
Retrieve order-related documents (invoices, shipping labels). |
| Method | Description |
|---|---|
payoutStatus() |
Get transaction statements created after the provided date. |
accountTransactions() |
Query account transactions. |
logisticsFeeDetail() |
Query logistics fee details. |
transactionDetail() |
Query seller transaction details within a specific date range. |
| Method | Description |
|---|---|
get() |
Get the product list for the seller. |
item() |
Get a single product item by item ID. |
updateSellableQuantity() |
Update sellable stock quantity for a SKU. |
use Laraditz\Lazada\Facades\Lazada;
// Using facade (recommended)
Lazada::order()->list(created_after: '2023-11-17T00:00:00+08:00');
// Using service container
app('lazada')->order()->list(created_after: '2023-11-17T00:00:00+08:00');use Laraditz\Lazada\Facades\Lazada;
use Laraditz\Lazada\Exceptions\LazadaAPIError;
// Step 1: Get the authorization URL and redirect the seller to it
$url = Lazada::auth()->authorizationUrl();
// Step 2: After the seller authorizes, exchange the code for a token
// (The package handles this automatically via the built-in callback route)
// If using a custom callback, call this manually:
try {
$token = Lazada::auth()->accessToken(code: '0_123456_XxxXXXXxxXXxxXXXXxxxxxxXXXXxx');
} catch (LazadaAPIError $e) {
logger()->error('Lazada token error', [
'message' => $e->getMessage(),
'code' => $e->getMessageCode(),
'request' => $e->getRequestId(),
]);
}// Get orders created after a date
$orders = Lazada::order()->list(created_after: '2023-11-17T00:00:00+08:00');
// Get a specific order
$order = Lazada::order()->get(order_id: '16090');
// Get items in an order
$items = Lazada::order()->items(order_id: '16090');
// Get order documents (invoice, shipping label)
$document = Lazada::order()->document(
order_item_ids: [1234567, 1234568],
doc_type: 'invoice',
);// Get product list
$products = Lazada::product()->get(
filter: 'live',
offset: 0,
limit: 50,
);
// Get a specific product item
$item = Lazada::product()->item(item_id: 123456789);
// Update sellable stock quantity for a SKU
Lazada::product()->updateSellableQuantity(
payload: json_encode([
'skuId' => 'your-sku-id',
'sellableQuantity' => 100,
])
);// Get payout status
$payout = Lazada::finance()->payoutStatus(
created_after: '2023-01-01T00:00:00+08:00',
);
// Query account transactions
$transactions = Lazada::finance()->accountTransactions(
start_time: '2023-11-01T00:00:00+08:00',
end_time: '2023-11-30T23:59:59+08:00',
);
// Query transaction details
$details = Lazada::finance()->transactionDetail(
trans_type: 3,
start_time: '2023-11-01T00:00:00+08:00',
end_time: '2023-11-30T23:59:59+08:00',
);// Sync seller info from Lazada API to DB
Lazada::seller()->get();
// Get the resolved seller model (no API call)
$seller = Lazada::seller()->info();
echo $seller->name;
echo $seller->short_code;
// Get pick-up store list
$stores = Lazada::seller()->pickUpStoreList();By default the package uses LAZADA_SELLER_SHORT_CODE from your .env. For multi-seller applications, specify the seller per request:
// Method 1: Using make() β reuse the same instance for multiple calls
$lazada = Lazada::make(seller_id: 'MYXXXXXXXX');
$orders = $lazada->order()->list(created_after: '2023-11-17T00:00:00+08:00');
$products = $lazada->product()->get(filter: 'live', offset: 0, limit: 50);
// Method 2: Pass seller_id on any service call to switch sellers inline
Lazada::order(seller_id: 'MYXXXXXXXX')->list(created_after: '2023-11-17T00:00:00+08:00');use Laraditz\Lazada\Facades\Lazada;
use Laraditz\Lazada\Exceptions\LazadaAPIError;
try {
$orders = Lazada::order()->list(created_after: '2023-11-17T00:00:00+08:00');
} catch (LazadaAPIError $e) {
// Handle Lazada API errors
logger()->error('Lazada API Error', [
'message' => $e->getMessage(),
'code' => $e->getMessageCode(),
'request_id' => $e->getRequestId(),
'result' => $e->getResult(), // raw response
]);
} catch (\Throwable $th) {
throw $th;
}Configure this URL in your Lazada Open Platform App Management under the Push Mechanism section so Lazada pushes content updates to your app:
https://your-app-url.com/lazada/webhooks
| Event | Description |
|---|---|
Laraditz\Lazada\Events\WebPushReceived |
Triggered when Lazada sends a web push. |
// app/Listeners/LazadaWebPushListener.php
namespace App\Listeners;
use Laraditz\Lazada\Events\WebPushReceived;
class LazadaWebPushListener
{
public function handle(WebPushReceived $event): void
{
$pushType = $event->pushType;
$data = $event->data;
// Handle the push content based on type
}
}Register the listener in your EventServiceProvider (Laravel 10 and below):
// app/Providers/EventServiceProvider.php
protected $listen = [
\Laraditz\Lazada\Events\WebPushReceived::class => [
\App\Listeners\LazadaWebPushListener::class,
],
];Read more about the Lazada Push Mechanism (LPM) in the official documentation.
# Refresh existing access tokens before they expire
php artisan lazada:refresh-token
# Remove expired access tokens from the database
php artisan lazada:flush-expired-tokenSet up scheduled token refresh in your app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
// Refresh tokens daily (live access tokens expire in 30 days)
$schedule->command('lazada:refresh-token')
->daily()
->withoutOverlapping();
// Clean up expired tokens weekly
$schedule->command('lazada:flush-expired-token')
->weekly();
}| Environment | Token Type | Duration |
|---|---|---|
| Live | Access Token | 30 days |
| Live | Refresh Token | 180 days |
| Testing | Access Token | 7 days |
| Testing | Refresh Token | 30 days |
Important: If both tokens expire, the seller must re-authorize your app to generate a new token pair.
composer testPlease see CHANGELOG for more information about recent changes.
Please see CONTRIBUTING for details.
If you discover any security vulnerabilities, please email raditzfarhan@gmail.com instead of using the issue tracker.
Lazada::make()->auth()->authorizationUrl(?string $redirectUri = null, ?string $state = null): string$redirectUri replaces the configured callback URL for this call (it is not url-encoded, as Lazada rejects an encoded redirect_uri). A non-null $state is appended as &state= followed by the url-encoded value. With no arguments the URL is unchanged.
Lazada's support for
stateis to be confirmed by Ortiro's sandbox test (Plan 4).
Lazada::verifyPushSignature(string $rawBody, string $authorization): boolCompares the push Authorization header (case-insensitive, trimmed) with the expected signature of the raw body, using a timing-safe comparison. Returns false for empty input.
Laraditz\Lazada\Events\SellerAuthorized carries the public readonly LazadaSeller $seller and LazadaAccessToken $accessToken. It is dispatched after an access token exchange has stored the seller and token, before the seller-info fetch. Listening to it is optional.
// config/lazada.php
'encrypt_tokens' => env('LAZADA_ENCRYPT_TOKENS', false),Default false: tokens are stored as plaintext, exactly as before. When true, access_token and refresh_token are encrypted with Laravel's Crypt (so APP_KEY must stay stable) when written through the LazadaAccessToken model. Reads work for plaintext rows, encrypted rows, and after the flag is switched off again. A published config file without this key behaves as false.
To encrypt rows that already exist, enable the flag and run:
php artisan lazada:encrypt-tokensIt prints Encrypted N value(s). and is safe to run again (the second run reports 0). With the flag off it prints Enable lazada.encrypt_tokens first. and exits with code 1. Always write tokens through the Eloquent model; query-builder updates skip the cast.
Encrypted values are longer than the original token, so a new migration (change_tokens_to_text_in_lazada_access_tokens_table) widens access_token and refresh_token from varchar(255) to text. Publish and run it before enabling encryption (php artisan vendor:publish --tag=migrations; already-published migrations are skipped by name).
- The
2026_10_29migration uses->change(), which needsdoctrine/dbalon Laravel 10. Rolling it back narrows the columns to 255 characters and fails or truncates if encrypted values exist. - If you rotate
APP_KEY, keep the old key inAPP_PREVIOUS_KEYSso existing encrypted values still decrypt, and do not runlazada:encrypt-tokensuntil then: it treats any value it cannot decrypt with the current key as plaintext and would encrypt it a second time.
Every API call is logged to lazada_messages. These options are all opt-in; the defaults keep today's behaviour. They are separate from log_message, which controls webhook logging.
'logging' => [
'requests' => env('LAZADA_LOG_REQUESTS', true),
'redact_secrets' => env('LAZADA_LOG_REDACT_SECRETS', false),
'prune_after_days' => env('LAZADA_LOG_PRUNE_AFTER_DAYS'),
],requests(defaulttrue): set tofalseto stop writinglazada_messagesrows. API calls and after-hooks (for example storing the token afterauth()->accessToken()) work as before; only the log row is skipped.redact_secrets(defaultfalse): whentrue, the saved log replacesaccess_token,refresh_token,codeandsignwith[REDACTED]in the request (at any depth) and the URL query string, andaccess_tokenandrefresh_tokenin the response. The HTTP call and the response returned to you are unchanged, andlazada_access_tokensstill stores the real values.prune_after_days(defaultnull): a positive integer letsLazadaMessagerows older than that many days be pruned. Empty or0prunes nothing.
Laravel's model:prune only discovers models under app/Models, so package models are pruned only if your app schedules it explicitly, for example:
Schedule::command('model:prune', ['--model' => [\Laraditz\Lazada\Models\LazadaMessage::class]])->daily();A published config file without the logging key behaves as the defaults above.
- Raditz Farhan - Creator and maintainer
- All Contributors - Thank you for your contributions!
The MIT License (MIT). Please see License File for more information.
