charlielangridge / lunar-xero
Xero integration for Lunar with Filament admin tooling, OAuth connection management, and queued customer, invoice, and payment sync.
Requires
- php: ^8.4
- filament/filament: ^4.0
- guzzlehttp/guzzle: ^7.9
- illuminate/database: ^11.0||^12.0
- illuminate/http: ^11.0||^12.0
- illuminate/routing: ^11.0||^12.0
- illuminate/support: ^11.0||^12.0
- lunarphp/lunar: ^1.0
- spatie/laravel-package-tools: ^1.16
- xeroapi/xero-php-oauth2: ^7.4
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
README
lunar-xero connects a Lunar store to Xero. It adds a Xero settings page to the Lunar admin, stores the OAuth connection locally, and syncs contacts, invoices, captured payments, and refunds through queued jobs.
The package is built around Lunar's existing models and events, so it fits into a normal Lunar install instead of replacing the order flow.
What it does
- Connects to Xero with OAuth 2.0 and PKCE
- Stores and manages the active Xero tenant
- Adds a Xero settings page in the Lunar admin
- Adds Xero account code and item code fields to products and variants
- Lets admins link or unlink customers to existing Xero contacts
- Syncs orders to Xero invoices
- Syncs captured payments to Xero payments
- Syncs refunds to Xero credit notes and payout allocations
- Logs each sync attempt so failures and skips are inspectable
- Falls back to billing or shipping address data when it needs to create a contact for a guest order
Requirements
- PHP 8.4+
- Laravel 11 or 12
- Filament 4
- Lunar 1.x
- A running queue worker
- A Xero app with OAuth enabled
Installation
Install the package:
composer require charlielangridge/lunar-xero
Publish the config file if you want to override the defaults:
php artisan vendor:publish --tag=lunarpanel-xero-config
Publish the views only if you need to customise them:
php artisan vendor:publish --tag=lunarpanel-xero-views
Run your migrations:
php artisan migrate
The service provider is auto-discovered. In a standard Lunar panel setup the plugin page is registered automatically as well.
Configuration
The package config lives at config/lunarpanel-xero.php.
The main settings are:
defaults.invoice_status: default invoice status sent to Xero, eitherDRAFTorAUTHORISEDdefaults.sync_queue: queue name used for contact, invoice, and payment jobsoauth.read_only: blocks write calls to Xero when enabledevents.order_created: event class used to dispatch invoice syncsevents.payment_completed: event class used to dispatch payment syncsmodels.*: model classes the package should usetables.*: table names for the package tables and patched Lunar tablesroutes.prefixandroutes.middleware: override the OAuth route group when needed
Environment
Add these values to .env:
XERO_CLIENT_ID= XERO_CLIENT_SECRET= XERO_REDIRECT_URI= XERO_READ_ONLY=false LUNARPANEL_XERO_ROUTE_PREFIX=
If XERO_REDIRECT_URI is left empty, the package falls back to the generated lunarpanel-xero.callback route.
Routes
The package registers these routes:
GET /connectGET /callbackPOST /disconnectPOST /tenants/refreshPOST /tenants/select
By default they sit under the Lunar panel path with /xero appended. If the panel path cannot be resolved, the fallback prefix is lunar/xero.
Admin areas
The Xero settings page lets you:
- start or disconnect the OAuth connection
- refresh tenants and choose the active tenant
- choose whether invoices are created as draft or authorised
- set a default revenue account code
- map Lunar payment types to Xero account codes
The package also extends existing Lunar admin screens:
- products get a dedicated Xero page
- variants get a dedicated Xero page
- customers can be linked to or unlinked from a Xero contact
- orders show the current invoice sync state, Xero invoice ID, last error, and a manual sync action
Sync behaviour
Contacts
Customer sync is observer-driven.
- New customers queue a contact sync job
- Unlinked customers queue another sync when they are updated
- Existing
xero_contact_idvalues are respected - Matching Xero contacts can be found by email before a new contact is created
- Guest orders can create a contact from billing details, with shipping used as a fallback
Invoices
Invoice sync runs from the configured order-created event and can also be queued manually from the order page.
During invoice sync the package:
- creates a sync log entry
- resolves or creates a Xero contact
- builds invoice lines from the order
- resolves account codes from variant, product, then package default settings
- resolves or creates Xero item codes where possible
- creates or updates the Xero invoice
- stores the returned Xero invoice ID on the order
- backfills payments and refunds where appropriate
If the order has a customer_reference, that value is used as the invoice reference and is also added as a zero-value purchase-order line.
Payments and refunds
Payment sync runs from the configured payment event and from the transaction observer.
- Captured payments are posted against the matching Xero invoice
- Refund transactions are turned into Xero credit notes
- Refund credit notes are allocated back to the invoice and can be paid out through the mapped account
- Duplicate payments and refunds are checked before new records are created
Queueing
All sync jobs implement ShouldQueue and default to the xero queue unless you override defaults.sync_queue.
Example worker command:
php artisan queue:work --queue=xero,default
Logging
Each sync attempt writes to xero_sync_logs, including:
- operation type
- resource type and ID
- payload snapshot
- external reference
- status
- response payload
- error message and exception class when something fails
That table is the first place to look when a sync has been skipped or has failed.
Development
Run the test suite:
composer test
Run static analysis:
composer analyse
Run formatting:
composer format
License
MIT. See LICENSE.md.