nikba / laravel-bussystem-api
Laravel package providing seamless integration with BusSystem transportation services
Package info
github.com/Nikba-Creative-Studio/Laravel-Bussystem-Api
pkg:composer/nikba/laravel-bussystem-api
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.5|^8.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^2.9|^3.0
- laravel/pint: ^1.18
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpstan/phpstan: ^1.11|^2.0
- phpunit/phpunit: ^10.5|^11.0|^12.0
- rector/rector: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
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,AirSortType:Time,PriceResponseFormat:Json,XmlOrderStatus:Reserve,ReserveOk,Buy,CancelTicketStatus: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.