Looking to hire Laravel developers? Try LaraJobs

laravel-booking-com maintained by flairuk

Description
A Laravel client for the Booking.com Demand API: search, availability, property content, orders and car rentals.
Author
Last update
2026/10/05 13:03 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

Laravel Booking.com — A Laravel 12 and 13 client for the Booking.com Demand API, for affiliates and travel sites that search, show and book Booking.com stays and car rentals.

  • Every stable endpoint. Covers accommodation search, availability, content, reviews and changes; locations; orders (preview, create, modify, cancel); car rentals; and messaging.
  • Pagination built in. Any paginated result can be walked with ->lazy(), which fetches the next page only when you reach it.
  • Rate-limit friendly. Reference data is cached. A 429 raises a RateLimitException that tells you how long to wait.
  • Safe retries. Reads are retried on connection errors and 5xx responses. Order creation, changes and cancellations never are, so nothing is booked or cancelled twice.
  • Real errors. Failures throw exceptions carrying Booking.com's error ids and the request_id to quote to their support.

This is an unofficial package. It is not affiliated with or endorsed by Booking.com. You need your own Booking.com affiliate partner account and API key.

📦 Installation

composer require flairuk/laravel-booking-com
php artisan booking-com:install

booking-com:install publishes config/booking-com.php and adds these keys to .env and .env.example:

BOOKING_COM_TOKEN=            # API key from the Affiliate Partner Centre
BOOKING_COM_AFFILIATE_ID=     # affiliate ID of the API user
BOOKING_COM_SANDBOX=true      # use the sandbox until you go live
BOOKING_COM_BOOKER_COUNTRY=   # optional default booker country, e.g. gb
BOOKING_COM_CURRENCY=         # optional default currency, e.g. GBP

Then check the connection:

php artisan booking-com:status

The sandbox uses your normal credentials, with test properties such as 10507360 (Demand API Sandbox Hotel Orion, Amsterdam). Car rentals aren't available in the sandbox.

🚀 Usage

use FLAIRUK\BookingCom\Facades\BookingCom;

You can also type-hint FLAIRUK\BookingCom\BookingCom to have it injected.

Request bodies are plain arrays in the shape the API reference documents. Dates can be given as DateTimeInterface objects. Every call returns a Response:

$response->data();         // the "data" array
$response->metadata();     // the "metadata" array
$response->requestId();    // quote this to Booking.com support
$response->total();        // metadata.total_results
$response->toArray();      // the whole body

foreach ($response as $item) { /* items in data */ }
count($response);
$response[0]['id'];

Searching for stays

$results = BookingCom::accommodations()->search([
    'city' => -2140479,                       // Amsterdam: see Locations below
    'checkin' => now()->addMonth(),
    'checkout' => now()->addMonth()->addDays(2),
    'guests' => ['number_of_adults' => 2, 'number_of_rooms' => 1],
    'booker' => ['country' => 'gb', 'platform' => 'desktop'],
    'currency' => 'GBP',
    'extras' => ['extra_charges', 'products'],
    'rows' => 20,
]);

$results->total();                           // e.g. 122

foreach ($results->lazy() as $property) {    // walks every page on demand
    // ...
}

booker and currency fall back to BOOKING_COM_BOOKER_COUNTRY, BOOKING_COM_BOOKER_PLATFORM and BOOKING_COM_CURRENCY. The booker's country decides the prices and how taxes are shown, so pass each visitor's real country when you know it.

You can also search by country, region, district, landmark, airport, coordinates (latitude, longitude and radius) or a list of accommodations.

Availability, content and reviews

BookingCom::accommodations()->availability([
    'accommodations' => [10507360],
    'checkin' => '2026-11-01',
    'checkout' => '2026-11-03',
    'guests' => ['number_of_adults' => 2, 'number_of_rooms' => 1],
]);

BookingCom::accommodations()->find(10507360, extras: ['description', 'facilities', 'photos', 'policies', 'rooms']);
BookingCom::accommodations()->details(['city' => -2140479, 'rows' => 100])->lazy();

BookingCom::accommodations()->reviews(['accommodations' => [10507360], 'languages' => ['en-gb']]);
BookingCom::accommodations()->reviewScores(['accommodations' => [10507360]]);

BookingCom::accommodations()->constants();   // facility, room type, meal plan ids… (cached)
BookingCom::accommodations()->chains();      // hotel chains and brands (cached)

Keeping stored content up to date

Store property content and refresh only what changed:

foreach (BookingCom::accommodations()->changesSince($lastSync, ['countries' => ['gb']]) as $batch) {
    $batch['changed'];   // ids to re-fetch with find()
    $batch['opened'];    // new properties
    $batch['closed'];    // properties to hide
    $lastSync = $batch['next'] ?? $batch['from'];
}

Locations

Accommodation searches use Booking.com's own location ids:

BookingCom::locations()->cities(['country' => 'nl', 'languages' => ['en-gb']])->lazy();
BookingCom::locations()->countries();
BookingCom::locations()->regions(['country' => 'gb']);
BookingCom::locations()->districts(['city' => -2140479]);
BookingCom::locations()->landmarks(['city' => -2140479]);
BookingCom::locations()->airports(['country' => 'gb']);

BookingCom::common()->languages();    // cached
BookingCom::common()->currencies();   // cached
BookingCom::common()->cards();        // cached

Car rentals

BookingCom::cars()->search([
    'route' => [
        'pickup' => ['datetime' => '2026-11-01T10:00:00', 'location' => ['airport' => 'AMS']],
        'dropoff' => ['datetime' => '2026-11-03T10:00:00', 'location' => ['airport' => 'AMS']],
    ],
    'driver' => ['age' => 35],
    'currency' => 'EUR',
]);

BookingCom::cars()->details();
BookingCom::cars()->depots();
BookingCom::cars()->suppliers();   // cached
BookingCom::cars()->constants();   // cached

Anything else

For endpoints without a wrapper, such as Beta endpoints, call them directly. Authentication and error handling still apply:

BookingCom::post('attractions/search', ['city' => -2140479]);
BookingCom::post('orders/modify/preview', $body, retry: false);   // pass retry: false for anything that changes state

🛏️ Booking flow

Preview the order to get the final price, policies and an order_token. Then create the order with that token:

$preview = BookingCom::orders()->preview([
    'accommodation' => [
        'id' => 10507360,
        'checkin' => '2026-11-01',
        'checkout' => '2026-11-03',
        'products' => [['id' => $productId, 'allocation' => ['number_of_adults' => 2]]],
    ],
    'currency' => 'GBP',
]);

$order = BookingCom::orders()->create([
    'order_token' => $preview->data()['order_token'],
    'booker' => [
        'name' => ['first_name' => 'Jane', 'last_name' => 'Doe'],
        'email' => 'jane@example.com',
        'telephone' => '+441234567890',
        'address' => ['address_line' => '1 High Street', 'city' => 'London', 'country' => 'gb', 'post_code' => 'SW1A 1AA'],
    ],
    'payment' => ['timing' => 'pay_at_the_property'],
]);

After booking:

BookingCom::orders()->find($orderId);
BookingCom::orders()->details(['created' => ['from' => '2026-10-01', 'to' => '2026-10-07']])->lazy();   // max 7-day range
BookingCom::orders()->accommodations(['orders' => [$orderId]]);
BookingCom::orders()->modify(['order' => $orderId, 'modification' => [/* ... */]]);
BookingCom::orders()->cancelAccommodation($reservationId, 'Guest changed plans');

create(), modify() and cancel() are never retried automatically. If one of them times out, check with orders()->find() before trying again.

Card data: if your integration takes card payments, card details pass straight through to Booking.com in the create() payload. Make sure your own logging, error reporting and queues never record that payload, and check your PCI DSS obligations.

Messaging

Guest–property messaging is available in API version 3.1, and in 3.2 for partners with Beta access:

$messages = BookingCom::version('3.1')->messages();

$messages->latest();
$messages->confirm(['message-id']);
$messages->conversation(['accommodation' => 10507360, 'reservation' => $reservationId]);
$messages->send(['accommodation' => 10507360, 'conversation' => $conversationId, 'content' => 'What time is check-in?']);

⚠️ Errors and rate limits

use FLAIRUK\BookingCom\Exceptions\AuthenticationException;
use FLAIRUK\BookingCom\Exceptions\BookingComException;
use FLAIRUK\BookingCom\Exceptions\RateLimitException;

try {
    BookingCom::accommodations()->search($query);
} catch (RateLimitException $e) {
    // per-minute quota exceeded: wait $e->retryAfter() seconds (usually 60)
} catch (AuthenticationException $e) {
    // missing or rejected API key / affiliate ID
} catch (BookingComException $e) {
    $e->errorIds();        // e.g. ['invalid_request']
    $e->errors;            // [['id' => ..., 'message' => ...]]
    $e->requestId;         // quote this to Booking.com support
    $e->response?->json(); // the full response
}

In queued jobs, release the job on a rate limit:

catch (RateLimitException $e) {
    $this->release($e->retryAfter());
}

Your account's rate limit is set by Booking.com; your Account Manager can tell you what it is.

⚙️ Configuration

Key Env Default
token BOOKING_COM_TOKEN
affiliate_id BOOKING_COM_AFFILIATE_ID
sandbox BOOKING_COM_SANDBOX false
version BOOKING_COM_API_VERSION 3.2
base_url BOOKING_COM_BASE_URL built from sandbox and version
defaults.booker.country BOOKING_COM_BOOKER_COUNTRY
defaults.booker.platform BOOKING_COM_BOOKER_PLATFORM desktop
defaults.currency BOOKING_COM_CURRENCY
timeout BOOKING_COM_TIMEOUT 30 seconds
retry 2 retries, 500 ms apart (reads only)
cache.store BOOKING_COM_CACHE_STORE the default cache store
cache.ttl 24 hours; null turns caching off

Scope a single call differently without changing the defaults:

BookingCom::sandbox()->accommodations()->search($query);
BookingCom::version('3.1')->accommodations()->bulkAvailability($query);
BookingCom::forAffiliate($otherAffiliateId, $otherToken)->orders()->find($orderId);

🧪 Testing

composer test

The client uses Laravel's HTTP client, so Http::fake() works in your own tests:

Http::fake([
    'demandapi*.booking.com/*/accommodations/search' => Http::response(['request_id' => 'test', 'data' => []]),
]);

📄 License

The MIT License (MIT). See LICENSE for details.

Booking.com is a trademark of Booking.com B.V. This package is not affiliated with or endorsed by Booking.com.