dcblogdev/laravel-xero
Laravel package for integrating the Xero accounting API into your app. Handles OAuth 2.0 authentication, token refresh, and provides a clean interface for invoices, contacts, payments, and other Xero endpoints, so you can sync accounting data without writing boilerplate API code.
composer require dcblogdev/laravel-xerophp artisan vendor:publish --provider="Dcblogdev\Xero\XeroServiceProvider" (config lands at config/xero.php, migration creates the xero_tokens table). Run php artisan migrate..env: XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_REDIRECT_URI. The redirect URI must match the Xero app config byte-for-byte, including trailing slash.Xero::connect() (redirects to Xero) and one that hits Xero::callback() (stores tokens). A simple Route::get('/xero/connect', ...) / Route::get('/xero/callback', ...) pair is enough to start.config/xero.php — that's where scopes, the redirect path, and the token model live. Then read the Xero facade class; every API call funnels through it.First use case: connect a tenant, then Xero::get('Contacts') (or Xero::get('Organisation')) in a tinker session to prove the token round-trip works before writing any domain code.
Note for this release: accounting.transactions is no longer in the default scopes array. If your first use case involves invoices, credit notes, payments, bank transactions, or manual journals, add it back to config/xero.php before you run the OAuth flow — otherwise you'll get a valid token that can't touch the endpoints you care about.
Xero::get/post/put/delete calls inside small repository or service classes (XeroInvoiceRepository) rather than scattering them across controllers. The facade methods return decoded arrays, so wrapping them in DTOs pays off quickly.xero_tokens. Access tokens expire in ~30 minutes; the package refreshes on demand. Don't cache the access token yourself — cache the result of the call instead, and let the package own the refresh cycle.Xero::getTenants() gives you the connected organisations. Store the chosen tenantId on your User/Team model and call Xero::setTenantId($id) (or pass it per request) before each operation. If you only ever have one tenant, still read it from the token row rather than hardcoding.$backoff and $tries on the job; Xero's per-tenant limit is tight enough that a bulk sync will hit it.InvoiceNumber/ContactNumber before POSTing. Xero will happily create duplicates.x-xero-signature HMAC against the raw request body before any middleware mutates it, and return 200 fast — enqueue the actual processing.config('xero.scopes') in a service provider. That way a package default change (like this release) can't silently break a feature.accounting.transactions from the default scopes. This is the headline change and it is effectively a breaking change for any app that relies on the default scope list. Symptoms are 401 Unauthorized / 403 Forbidden on invoice, payment, bank-transaction, credit-note, and manual-journal endpoints after a fresh connect, while contacts and settings keep working. Fix: add 'accounting.transactions' to scopes in config/xero.php and complete the OAuth flow again.offline_access. Refresh tokens only come back if that scope is present — it's in the defaults, but if you prune the scopes array aggressively you'll silently lose refresh and get hourly reconnects./xero/connect, don't just log and drop the job.usleep/RateLimiter::attempt — a naive foreach over 500 contacts will 429.state parameter is used for CSRF on the OAuth round-trip. If you're behind a load balancer with sticky-session issues, the state check can fail intermittently — verify session persistence before assuming it's a package bug.Team instead of a global table, which is what most multi-tenant apps end up needing.^1.1.18 (or lock it) so a future scope tweak doesn't ship itself into production overnight.How can I help you explore Laravel packages today?