Search by

nikba / laravel-bussystem-api

nikba

Laravel package providing seamless integration with BusSystem transportation services

v2.0.0 2026-09-03 09:33 UTC

This package is auto-updated.

Last update: 2026-09-03 09:35:29 UTC


README

Latest Version on Packagist Total Downloads Tests PHP Version License

A typed Laravel client for the BusSystem passenger transportation API. Search bus, train, and air routes; build bookings; manage orders and tickets; and optionally persist API data with the included Eloquent models.

Highlights

  • Laravel-native service container binding and facade
  • Fluent, typed builders for route searches and bookings
  • Bus, train, and air transport support
  • Seat plans, discounts, baggage, reservations, payments, and cancellations
  • Automatic caching for points, routes, and seat plans
  • Configurable retries for transient API failures
  • JSON and XML response parsing
  • Optional order and ticket migrations and Eloquent models
  • Dedicated exception hierarchy for predictable error handling

Requirements

  • PHP 8.2+ (PHP 8.3+ with Laravel 13)
  • Laravel 10, 11, 12, or 13
  • Guzzle 7.5 or 8.x

Laravel 10 and 11 are supported for compatibility with existing applications but are end-of-life upstream. Prefer a currently maintained Laravel release for production deployments.

Installation

Install the package with Composer:

composer require nikba/laravel-bussystem-api

Laravel discovers the service provider and facade automatically.

Publish the configuration file:

php artisan vendor:publish --tag=bussystem-config

If you want to store orders and tickets locally, publish and run the included migrations:

php artisan vendor:publish --tag=bussystem-migrations
php artisan migrate

Configuration

Add your BusSystem credentials to .env:

BUSSYSTEM_API_URL=https://test-api.bussystem.eu/server
BUSSYSTEM_LOGIN=your-login
BUSSYSTEM_PASSWORD=your-password
BUSSYSTEM_PARTNER_ID=

BUSSYSTEM_DEFAULT_CURRENCY=EUR
BUSSYSTEM_DEFAULT_LANGUAGE=en
BUSSYSTEM_DEFAULT_API_VERSION=1.1

The package also supports these optional settings:

BUSSYSTEM_TIMEOUT=120
BUSSYSTEM_RETRY_ATTEMPTS=3
BUSSYSTEM_RETRY_DELAY=1000
BUSSYSTEM_RESPONSE_FORMAT=json

BUSSYSTEM_CACHE_ENABLED=true
BUSSYSTEM_CACHE_PREFIX=bussystem
BUSSYSTEM_CACHE_POINTS_TTL=3600
BUSSYSTEM_CACHE_ROUTES_TTL=300
BUSSYSTEM_CACHE_PLANS_TTL=86400

BUSSYSTEM_LOGGING_ENABLED=true
BUSSYSTEM_LOG_CHANNEL=daily
BUSSYSTEM_LOG_LEVEL=info

BUSSYSTEM_RETRY_DELAY and cache TTL values are expressed in milliseconds and seconds, respectively. Keep credentials out of source control and use separate credentials for test and production environments.

Quick start

1. Search for routes

use Nikba\LaravelBussystemApi\Data\SearchCriteria;
use Nikba\LaravelBussystemApi\Enums\SortType;
use Nikba\LaravelBussystemApi\Facades\BusSystem;

$criteria = SearchCriteria::create()
    ->date('2026-09-15')
    ->from(3)
    ->to(7)
    ->bus()
    ->directOnly()
    ->sortBy(SortType::Price)
    ->currency('EUR')
    ->language('en');

$routes = BusSystem::getRoutes($criteria);

City and station identifiers are provided by BusSystem. You can retrieve available points before searching:

$points = BusSystem::getPoints(['lang' => 'en']);

2. Check seats and booking options

Use the interval_id returned by the route search:

$intervalId = (string) $selectedRoute['interval_id'];

$seats = BusSystem::getFreeSeats($intervalId);
$discounts = BusSystem::getDiscounts($intervalId);
$baggage = BusSystem::getBaggage($intervalId);

3. Create an order

Route and passenger indexes are zero-based when assigning seats, discounts, baggage, or wagons.

use Nikba\LaravelBussystemApi\Data\BookingData;
use Nikba\LaravelBussystemApi\Exceptions\BusSystemValidationException;

$booking = BookingData::create('EUR', 'en')
    ->addRoute('2026-09-15', $intervalId)
    ->addPassenger(
        firstName: 'John',
        lastName: 'Doe',
        birthDate: '1990-01-15',
        docType: 1,
        docNumber: 'AB123456',
        gender: 'M',
    )
    ->addSeat(routeIndex: 0, seat: '12')
    ->setContactInfo('+37360000000', 'john@example.com');

$validationErrors = $booking->validate();

if ($validationErrors !== []) {
    throw new BusSystemValidationException(
        implode('; ', $validationErrors),
        context: ['errors' => $validationErrors],
    );
}

$order = BusSystem::createOrder($booking);

4. Buy or reserve tickets

$orderId = (int) $order['order_id'];

$tickets = BusSystem::buyTickets($orderId, 'en');

$reservation = BusSystem::reserveTickets($orderId, [
    'phone' => '+37360000000',
    'email' => 'john@example.com',
    'lang' => 'en',
]);

Treat response arrays as external API data: validate the keys required by your application before reading or persisting them.

Dependency injection

For application services and controllers, dependency injection is usually preferable to the facade:

use Nikba\LaravelBussystemApi\Contracts\BusSystemClientInterface;
use Nikba\LaravelBussystemApi\Data\SearchCriteria;

final class SearchRoutes
{
    public function __construct(
        private readonly BusSystemClientInterface $busSystem,
    ) {}

    public function handle(int $fromCityId, int $toCityId, string $date): array
    {
        return $this->busSystem->getRoutes(
            SearchCriteria::create()
                ->date($date)
                ->from($fromCityId)
                ->to($toCityId),
        );
    }
}

The client is registered as a singleton under BusSystemClientInterface and the bussystem container alias.

Route searches

SearchCriteria supports the following fluent methods:

Purpose Methods
Travel date date(), period()
City route from(), to()
Train stations trainFrom(), trainTo()
Airport route airportFrom(), airportTo()
Specific stations stationFrom(), stationTo()
Transport transport(), bus(), train(), air()
Transfers allowTransfers(), directOnly()
Result options sortBy(), sortByTime(), sortByPrice(), includeSoldOut()
Locale currency(), language()
Air travel airPassengers(), airServiceClass(), airDirect(), airBaggage()
Custom API data addParam()

For air travel, use IATA codes and the dedicated passenger options:

$flights = BusSystem::getRoutes(
    SearchCriteria::create()
        ->date('2026-10-10')
        ->airportFrom('KIV')
        ->airportTo('VIE')
        ->air()
        ->airPassengers(adults: 2, children: 1)
        ->airServiceClass('E')
        ->airDirect(false),
);

Booking builder

BookingData provides methods for composing single-leg or multi-leg bookings:

Purpose Methods
Routes addRoute()
Passengers addPassenger()
Seats addSeat(), addSeats()
Discounts and baggage addDiscount(), addBaggage()
Train wagon addWagon()
Contact details setContactInfo()
Additional details setAdditionalInfo(), setPromocode()
Locale setCurrency(), setLanguage()
Inspection toArray(), validate(), getPassengerCount(), getRouteCount()

For a multi-leg journey, call addRoute() once per leg and use its zero-based index for related selections:

$booking = BookingData::create()
    ->addRoute('2026-09-15', 'outbound-interval')
    ->addRoute('2026-09-22', 'return-interval')
    ->addPassenger('John', 'Doe', '1990-01-15', 1, 'AB123456')
    ->addSeats(0, ['12'])
    ->addSeats(1, ['8'])
    ->setContactInfo('+37360000000', 'john@example.com');

Available operations

All methods are available through either BusSystemClientInterface or the BusSystem facade.

For signatures, parameters, builders, models, and configuration details, see the complete API reference.

Method Description
getPoints() Retrieve cities, stations, and airports
getRoutes() Search for available routes
getAllRoutes() Retrieve detailed timetable routes
getFreeSeats() Retrieve available seats or train wagons
getSeatPlan() Retrieve a seat-plan layout
getDiscounts() Retrieve discounts for a route
getBaggage() Retrieve baggage options for a route
createOrder() Create an order from BookingData
buyTickets() Purchase tickets for an order
reserveTickets() Reserve tickets for payment on boarding
validateReservation() Check whether a phone number can reserve
validateSms() Send or verify an SMS validation code
getOrder() Retrieve order information
getTicket() Retrieve ticket information
cancelTickets() Cancel an order or selected tickets
ping() Check API availability
$order = BusSystem::getOrder($orderId, $securityCode, 'en');

$ticket = BusSystem::getTicket([
    'ticket_id' => $ticketId,
    'security' => $ticketSecurityCode,
    'lang' => 'en',
]);

$cancellation = BusSystem::cancelTickets([
    'order_id' => $orderId,
    'security' => $securityCode,
    'lang' => 'en',
]);

Enums

Native PHP enums remove common magic strings while preserving string-based compatibility:

use Nikba\LaravelBussystemApi\Enums\SortType;
use Nikba\LaravelBussystemApi\Enums\TransportType;

$criteria
    ->transport(TransportType::Train)
    ->sortBy(SortType::Time);
  • TransportType: All, Bus, Train, Air
  • SortType: Time, Price
  • ResponseFormat: Json, Xml
  • OrderStatus: Reserve, ReserveOk, Buy, Cancel
  • TicketStatus: Reserve, ReserveOk, Buy, Cancel

Unknown transport values fall back to TransportType::All; unknown sort values fall back to SortType::Time. Unknown model status values resolve to null.

Error handling

Every package exception extends BusSystemException and can carry structured context:

use Illuminate\Support\Facades\Log;
use Nikba\LaravelBussystemApi\Exceptions\BusSystemApiException;
use Nikba\LaravelBussystemApi\Exceptions\BusSystemAuthenticationException;
use Nikba\LaravelBussystemApi\Exceptions\BusSystemException;
use Nikba\LaravelBussystemApi\Exceptions\BusSystemValidationException;

try {
    $routes = BusSystem::getRoutes($criteria);
} catch (BusSystemAuthenticationException $exception) {
    report($exception);
    abort(502, 'BusSystem authentication failed.');
} catch (BusSystemValidationException $exception) {
    return response()->json([
        'message' => $exception->getMessage(),
        'context' => $exception->getContext(),
    ], 422);
} catch (BusSystemApiException $exception) {
    Log::warning('BusSystem request failed', [
        'message' => $exception->getMessage(),
    ]);
    abort(503, 'The transport service is temporarily unavailable.');
} catch (BusSystemException $exception) {
    report($exception);
    abort(500);
}

The exception namespace also includes booking, payment, and cancellation exception types for application-level workflows.

Caching, retries, and logging

Caching applies automatically to points, route searches, and seat plans when enabled. Cache keys include the request parameters, and each resource has its own configurable TTL.

The default HTTP client retries connection failures and HTTP 5xx responses using exponential backoff. BUSSYSTEM_RETRY_ATTEMPTS controls the maximum retry count and BUSSYSTEM_RETRY_DELAY sets the base delay in milliseconds.

Request logging is configurable through BUSSYSTEM_LOGGING_ENABLED, BUSSYSTEM_LOG_CHANNEL, and BUSSYSTEM_LOG_LEVEL. Authentication parameters are removed from request log context by the package. Review your logging retention and access policies because other booking data may contain personal information.

Optional Eloquent models

The package includes Order and Ticket models backed by bussystem_orders and bussystem_tickets. API responses are not persisted automatically; your application decides what and when to store.

use Nikba\LaravelBussystemApi\Enums\OrderStatus;
use Nikba\LaravelBussystemApi\Models\Order;

$order = Order::create([
    'order_id' => $response['order_id'],
    'security_code' => $response['security'],
    'status' => $response['status'],
    'currency' => 'EUR',
    'api_response' => $response,
]);

$activeOrders = Order::query()->active()->get();
$paidOrders = Order::query()->paid()->get();

if ($order->status() === OrderStatus::Buy) {
    // The order is paid.
}

Useful scopes include:

  • Order: active(), reserved(), paid(), expired(), forUser()
  • Ticket: active(), reserved(), paid(), forPassenger(), forRoute(), departingAfter(), departingBefore()

Both models use soft deletes and retain the original API payload in the nullable api_response JSON column.

Testing

The injectable HTTP client in BusSystemClient makes isolated tests straightforward:

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Nikba\LaravelBussystemApi\Services\BusSystemClient;

$handler = new MockHandler([
    new Response(200, [], json_encode(['status' => 'ok'], JSON_THROW_ON_ERROR)),
]);

$client = new BusSystemClient(
    apiUrl: 'https://example.test',
    login: 'test-login',
    password: 'test-password',
    httpClient: new Client(['handler' => HandlerStack::create($handler)]),
);

$response = $client->ping();

Run the project checks locally:

composer check
composer refactor-test

Individual commands are also available:

composer test          # PHPUnit
composer test-coverage # PHPUnit with HTML coverage
composer lint-test     # Pint, check only
composer lint          # Pint, apply formatting
composer analyse       # PHPStan / Larastan
composer refactor-test # Rector, dry run
composer refactor      # Rector, apply changes

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request and include tests for behavioral changes.

Security

Do not report security vulnerabilities through public issues. Follow SECURITY.md to disclose them privately.

Changelog

See CHANGELOG.md for release history and upgrade notes.

Credits

License

Laravel BusSystem API is open-source software licensed under the MIT License.