Looking to hire Laravel developers? Try LaraJobs

laravel-billingo maintained by szabrob

Description
Laravel integration for the Billingo API - create, send and manage invoices, partners and products.
Last update
2026/11/10 09:56 (dev-main)
License
Downloads
18

Comments
comments powered by Disqus

Laravel integration for the Billingo API - create, send and manage invoices, partners and products.

Requirements

  • PHP 8.3+
  • Laravel 12 or 13

Installation

You can install the package via Composer:

composer require szabrob/laravel-billingo

Add your Billingo API v3 key to your .env file:

BILLINGO_API_KEY=your-api-key

Optionally, publish the configuration file:

php artisan vendor:publish --tag="laravel-billingo-config"
Key Env variable Default
api_key BILLINGO_API_KEY null
base_url BILLINGO_BASE_URL https://api.billingo.hu/v3
timeout BILLINGO_TIMEOUT 30 (seconds)
rate_limit.retries BILLINGO_RATE_LIMIT_RETRIES 2
rate_limit.max_delay BILLINGO_RATE_LIMIT_MAX_DELAY 10 (seconds)

Checking the connection

Verify that the API key works and see the account details:

php artisan billingo:check

# Check another API key without changing the configuration
php artisan billingo:check --key=another-api-key

The command prints the organization's tax code, subscription expiry, enabled features and NAV connection status, and exits with a non-zero code when the request fails.

Usage

Every Billingo API v3 resource is available through the Billingo facade (or by injecting Szabrob\LaravelBillingo\Billingo). Requests and responses use spatie/laravel-data objects with camelCase properties, PHP enums and CarbonImmutable dates.

Creating an invoice

use Carbon\CarbonImmutable;
use Szabrob\LaravelBillingo\Data\Documents\CreateDocumentData;
use Szabrob\LaravelBillingo\Data\Documents\CustomItemData;
use Szabrob\LaravelBillingo\Data\Documents\ProductItemData;
use Szabrob\LaravelBillingo\Enums\Currency;
use Szabrob\LaravelBillingo\Enums\DocumentInsertType;
use Szabrob\LaravelBillingo\Enums\DocumentLanguage;
use Szabrob\LaravelBillingo\Enums\PaymentMethod;
use Szabrob\LaravelBillingo\Enums\UnitPriceType;
use Szabrob\LaravelBillingo\Enums\Vat;
use Szabrob\LaravelBillingo\Facades\Billingo;

$invoice = Billingo::documents()->create(new CreateDocumentData(
    partnerId: 42,
    blockId: 7,
    type: DocumentInsertType::Invoice,
    fulfillmentDate: CarbonImmutable::today(),
    dueDate: CarbonImmutable::today()->addDays(8),
    paymentMethod: PaymentMethod::WireTransfer,
    language: DocumentLanguage::Hu,
    currency: Currency::HUF,
    electronic: true,
    items: [
        // A product saved in Billingo
        new ProductItemData(productId: 9, quantity: 2),
        // An ad-hoc line item
        new CustomItemData(
            name: 'Consulting',
            unitPrice: 10000,
            unitPriceType: UnitPriceType::Net,
            quantity: 1,
            unit: 'hour',
            vat: Vat::Percent27,
        ),
    ],
));

$invoice->id;            // 1001
$invoice->invoiceNumber; // "INV-2026-1"
$invoice->grossTotal;    // 12700.0

Billingo::documents()->send($invoice->id);              // to the partner's e-mail addresses
$pdf = Billingo::documents()->download($invoice->id);   // raw PDF contents

Optional fields default to null and are left out of the request payload.

Partners and products

use Szabrob\LaravelBillingo\Data\Partners\PartnerData;
use Szabrob\LaravelBillingo\Data\Shared\AddressData;
use Szabrob\LaravelBillingo\Enums\Country;
use Szabrob\LaravelBillingo\Enums\PartnerTaxType;

$partner = Billingo::partners()->create(new PartnerData(
    name: 'Example Ltd.',
    address: new AddressData(countryCode: Country::HU, postCode: '1111', city: 'Budapest', address: 'Fő utca 1.'),
    emails: ['billing@example.com'],
    taxcode: '12345678-2-42',
    taxType: PartnerTaxType::HasTaxNumber,
));

Billingo::partners()->get($partner->id);
Billingo::partners()->update($partner->id, $partner);
Billingo::partners()->delete($partner->id);

Listing and pagination

List methods return a single page as Szabrob\LaravelBillingo\Support\PaginatedResponse. Use lazy() to iterate over every page as a LazyCollection:

use Szabrob\LaravelBillingo\Data\Documents\DocumentFilterData;
use Szabrob\LaravelBillingo\Enums\PaymentStatus;

$page = Billingo::partners()->list(page: 1, perPage: 50, query: 'Example');

$page->data;            // Collection<int, PartnerData>
$page->total;
$page->hasMorePages();

Billingo::documents()
    ->lazy(new DocumentFilterData(paymentStatus: PaymentStatus::Outstanding))
    ->each(fn ($document) => /* ... */);

Available resources

Resource Methods
bankAccounts() list, lazy, get, create, update, delete
currencies() conversionRate
documentBlocks() list, lazy, create
documentExports() create, poll, download
documents() list, lazy, get, findByVendorId, create, createFromDraft, createReceipt, createReceiptFromDraft, createFromProforma, createModification, cancel, copy, delete, archive, send, download, posPrint, publicUrl, onlineSzamlaStatus, reminders, payments, updatePayments, deletePayments
inventory() productQuantity
organization() get
partners() list, lazy, get, create, update, delete, guess
products() list, lazy, get, create, update, delete
spendings() list, lazy, get, create, update, delete
utils() checkTaxNumber, convertLegacyId, serverTime

Using another API key

To work with multiple Billingo accounts, switch the key at runtime. The configured default client is not modified:

Billingo::withApiKey($company->billingo_api_key)->documents()->list();

Error handling

Failed requests throw a Szabrob\LaravelBillingo\Exceptions\BillingoException (the HTTP response is available as $exception->response). More specific subclasses are thrown for some cases:

Exception When
ValidationException HTTP 422; errors() returns the messages keyed by field
RateLimitException HTTP 429; retryAfter() returns the seconds to wait
DocumentNotReadyException The document PDF is not generated yet (HTTP 202 on download)
use Szabrob\LaravelBillingo\Exceptions\RateLimitException;
use Szabrob\LaravelBillingo\Exceptions\ValidationException;

try {
    Billingo::partners()->create($partner);
} catch (ValidationException $exception) {
    $exception->errors(); // ['name' => ['The name field is required.']]
} catch (RateLimitException $exception) {
    $this->release($exception->retryAfter() ?? 60);
}

Rate limiting

When Billingo responds with 429 Too Many Requests, the request is retried automatically (twice by default) after waiting the number of seconds sent in the Retry-After header. If Billingo asks to wait longer than rate_limit.max_delay seconds, the request is not retried, so web requests are not blocked for long; a RateLimitException is thrown instead. Set BILLINGO_RATE_LIMIT_RETRIES=0 to disable retries. Other failed responses are never retried.

Testing

The package uses Laravel's HTTP client, so you can fake Billingo responses with Http::fake():

Http::fake([
    'api.billingo.hu/v3/partners/*' => Http::response(['id' => 42, 'name' => 'Example Ltd.']),
]);

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Thank you for considering contributing to Laravel Billingo! Please review our contributing guide to get started.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

Laravel Billingo is open-sourced software licensed under the MIT license.