Skip to main content
Taxation in Lunar is driver-based, so a custom driver can replace the shipped system driver, for example to delegate tax calculation to a third-party service.

Overview

By default, Lunar calculates tax using Lunar\Core\Drivers\SystemTaxDriver, which relies on Lunar’s own tax zones, tax classes, and tax rates. Day-to-day configuration of those models is covered in the Taxation reference and is not repeated here. This page covers writing a custom tax driver, useful when tax calculation needs to be delegated to an external service instead of Lunar’s own rules. The active driver is set in config/lunar/taxes.php:

The TaxDriver contract

A custom driver must implement Lunar\Core\Drivers\TaxDriver:
Lunar calls the setter methods to populate the driver’s context, then calls getBreakdown(), which must return a Lunar\Core\ValueObjects\Cart\TaxBreakdown.
setTaxZone() lets a caller hand the driver a resolved Lunar\Core\Models\TaxZone directly, bypassing address-based resolution. This is useful for taxation determined by something other than the shipping address, for example an IP address lookup. When a tax zone is set this way, a custom driver should prefer it over deriving a zone from the shipping address.

Building the tax breakdown

getBreakdown() must return a Lunar\Core\ValueObjects\Cart\TaxBreakdown. Its constructor accepts an optional Illuminate\Support\Collection of amounts, and defaults to an empty collection when omitted:
Amounts are added one at a time with addAmount(), which accepts a Lunar\Core\ValueObjects\Cart\TaxBreakdownAmount:
Each TaxBreakdownAmount represents a single tax line (for example, “VAT” or “State Tax”) and is constructed with:
A PriceValue wraps an integer amount (in the currency’s minor unit) together with the Lunar\Core\Models\Currency it belongs to:

Registering a custom driver

Register the driver by extending the tax manager, typically from a service provider’s boot() method:
Then set the driver name in config/lunar/taxes.php:

Full example

The following example wraps a fictional third-party tax API:
AcmeTaxClient in this example stands in for whatever SDK or HTTP client wraps the third-party tax API. It can be injected through the constructor like any other collaborator, since the driver is resolved through the container.
getBreakdown() may be called once per cart line, and often once per candidate tax zone during checkout. Avoid making an external API call inside a tight loop without caching; SystemTaxDriver uses Spatie\Blink\Blink to memoize tax rate lookups within a request for this reason.

Reference implementation

Lunar\Core\Drivers\SystemTaxDriver is the shipped default driver and the best reference for how a driver fits together, including how it resolves a TaxZone when setTaxZone() has not been called, and how it distributes rounding remainders across multiple tax rates. Its constructor collaborators are resolved through the container and are not part of the public contract, so a custom driver is free to depend on whatever it needs. For configuring tax zones, tax classes, and tax rates that SystemTaxDriver uses out of the box, see the Taxation reference.