PHPackages                             charlielangridge/lunar-xero - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. charlielangridge/lunar-xero

ActiveLibrary

charlielangridge/lunar-xero
===========================

Xero integration for Lunar with Filament admin tooling, OAuth connection management, and queued customer, invoice, and payment sync.

v0.1.0(1mo ago)019↑531.6%MITPHPPHP ^8.4

Since Apr 7Pushed 4d agoCompare

[ Source](https://github.com/charlielangridge/lunar-xero)[ Packagist](https://packagist.org/packages/charlielangridge/lunar-xero)[ Docs](https://github.com/charlielangridge/lunar-xero)[ RSS](/packages/charlielangridge-lunar-xero/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (20)Versions (2)Used By (0)

Lunar Xero
==========

[](#lunar-xero)

`lunar-xero` connects a [Lunar](https://lunarphp.io/) 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
------------

[](#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
- Lets admins opt individual customers into including Lunar order line notes on Xero invoice lines
- Syncs orders to Xero invoices
- Can email authorised Xero invoices to customers while avoiding duplicate sends
- 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
------------

[](#requirements)

- PHP 8.4+
- Laravel 11, 12 or 13
- Filament 4
- Lunar 1.x
- A running queue worker
- A Xero app with OAuth enabled

Installation
------------

[](#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
-------------

[](#configuration)

The package config lives at `config/lunarpanel-xero.php`.

The main settings are:

- `defaults.invoice_status`: default invoice status sent to Xero, either `DRAFT` or `AUTHORISED`
- `defaults.sync_queue`: queue name used for contact, invoice, and payment jobs
- `oauth.read_only`: blocks write calls to Xero when enabled
- `events.order_created`: event class used to dispatch invoice syncs
- `events.payment_completed`: event class used to dispatch payment syncs
- `models.*`: model classes the package should use
- `tables.*`: table names for the package tables and patched Lunar tables
- `routes.prefix` and `routes.middleware`: override the OAuth route group when needed
- `charity.*`: configures where charity VAT relief metadata is read from on the order for Xero invoice traceability lines

### Environment

[](#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
------

[](#routes)

The package registers these routes:

- `GET /connect`
- `GET /callback`
- `POST /disconnect`
- `POST /tenants/refresh`
- `POST /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
-----------

[](#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, and can opt into Xero invoice line notes
- orders show the current invoice sync state, Xero invoice ID, last error, and a manual sync action

Sync behaviour
--------------

[](#sync-behaviour)

### Contacts

[](#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_id` values 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

[](#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:

1. creates a sync log entry
2. resolves or creates a Xero contact
3. builds invoice lines from the order
4. resolves account codes from variant, product, then package default settings
5. resolves or creates Xero item codes where possible
6. creates or updates the Xero invoice
7. stores the returned Xero invoice ID, invoice number, invoice status, and customer online invoice URL on the order
8. backfills payments and refunds where appropriate

For account or pay-later order flows, the package can also sync and email an invoice in one queued operation. That flow always pushes the latest Lunar order details to Xero first, creates or updates the invoice as `AUTHORISED`, fetches the invoice from Xero, and only calls Xero's email endpoint when `sent_to_contact` is still false. If the invoice has already been sent from Xero or by a previous package run, the email step is skipped and logged as successful.

Synced orders can contain these Xero invoice fields:

- `xero_invoice_id`: the Xero invoice UUID
- `xero_invoice_number`: the human-readable Xero invoice number, such as `INV-1234`
- `xero_invoice_status`: the latest status returned by Xero, such as `DRAFT`, `AUTHORISED`, or `PAID`
- `xero_online_invoice_url`: Xero's customer-safe online invoice URL, fetched only for customer-visible statuses

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.

Customers can be opted into order line notes from the Lunar customer edit page with the `Include order line notes on Xero invoices` toggle. The underlying `xero_include_order_line_notes` customer field defaults to `false`. When it is enabled and an order line has non-blank `notes`, the package appends the notes underneath the existing Xero invoice line description:

```
Existing description
Notes: Order line notes

```

If the order contains charity VAT relief metadata, the package also appends zero-value traceability lines for the charity name, charity number, declaration name, and declared-at timestamp. By default these values are read from `order.meta.charity_vat_relief.*`, matching `ganda-webstore`, and the taxable product lines still keep their VAT mapping from Lunar tax data.

You can override those defaults in `config/lunarpanel-xero.php`:

```
'charity' => [
    'enabled' => true,
    'name_path' => 'meta.charity_vat_relief.charity_name',
    'number_path' => 'meta.charity_vat_relief.charity_number',
    'declaration_name_path' => 'meta.charity_vat_relief.declaration_name',
    'declared_at_path' => 'meta.charity_vat_relief.declared_at',
],
```

### Customer invoice links

[](#customer-invoice-links)

For storefront customer order pages, use the stored `xero_online_invoice_url` after your app has already checked that the signed-in customer owns the order. Do not expose the internal `go.xero.com` invoice URL used by the Lunar admin panel; that link is intended for staff with Xero access.

Draft invoices are not customer-visible by default. The helper only returns a URL for `AUTHORISED` and `PAID` invoices:

```
use CharlieLangridge\LunarXero\Support\XeroUrlFactory;

$invoiceUrl = XeroUrlFactory::customerInvoiceUrl(
    $order->xero_online_invoice_url,
    $order->xero_invoice_status,
);
```

A `ganda-webstore` style order show view can then render the link conditionally:

```
@php
    $xeroInvoiceUrl = \CharlieLangridge\LunarXero\Support\XeroUrlFactory::customerInvoiceUrl(
        $order->xero_online_invoice_url,
        $order->xero_invoice_status,
    );
@endphp

@if ($xeroInvoiceUrl)

        View Xero invoice

@endif
```

For an Inertia and Vue storefront, resolve the customer-safe URL in your Laravel controller or page response and pass only that final value to the component:

```
use CharlieLangridge\LunarXero\Support\XeroUrlFactory;
use Inertia\Inertia;

return Inertia::render('Account/Orders/Show', [
    'order' => [
        'id' => $order->id,
        'reference' => $order->reference,
        'xeroInvoiceUrl' => XeroUrlFactory::customerInvoiceUrl(
            $order->xero_online_invoice_url,
            $order->xero_invoice_status,
        ),
    ],
]);
```

Then render it in the Vue page only when the prop is present:

```

defineProps({
    order: {
        type: Object,
        required: true,
    },
})

        View Xero invoice

```

### Payments and refunds

[](#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
--------

[](#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
-------

[](#logging)

Each sync attempt writes to `xero_sync_logs`, including:

- operation type, such as `invoice`, `invoice_email`, `payment`, or `credit_note`
- 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
-----------

[](#development)

Run the test suite:

```
composer test
```

Run static analysis:

```
composer analyse
```

Run formatting:

```
composer format
```

License
-------

[](#license)

MIT. See [LICENSE.md](LICENSE.md).

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance96

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

35d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4c3198a248b9580414af6bd9a09b66c8307b409de14b5bbec32140974323474?d=identicon)[charlielangridge](/maintainers/charlielangridge)

---

Top Contributors

[![charlielangridge](https://avatars.githubusercontent.com/u/8578083?v=4)](https://github.com/charlielangridge "charlielangridge (7 commits)")

---

Tags

laravelAccountingxerofilamentlunar

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/charlielangridge-lunar-xero/health.svg)

```
[![Health](https://phpackages.com/badges/charlielangridge-lunar-xero/health.svg)](https://phpackages.com/packages/charlielangridge-lunar-xero)
```

###  Alternatives

[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[spatie/laravel-health

Monitor the health of a Laravel application

85810.0M83](/packages/spatie-laravel-health)[laravel/cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

264778.4k3](/packages/laravel-cashier-paddle)[flat3/lodata

OData v4.01 Producer for Laravel

96320.9k](/packages/flat3-lodata)[simplestats-io/laravel-client

Client for SimpleStats!

4515.5k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
