When displaying prices on a storefront, it is important to show the correct format relative to the currency the customer is purchasing in.Every storefront is different. Lunar provides a default price formatter that suits most use cases, while also making it straightforward to swap in a custom implementation for stores with specific formatting requirements.
The Lunar\Models\Price model provides methods for retrieving prices with or without tax applied, based on the lunar.pricing.stored_inclusive_of_tax configuration value.
use Lunar\Models\Price;$price = Price::find(1);$price->priceExTax(); // Lunar\DataTypes\Price$price->priceIncTax(); // Lunar\DataTypes\Price$price->comparePriceIncTax(); // Lunar\DataTypes\Price
Each method accepts an optional Lunar\Models\TaxZone to override the zone used when computing the tax rate. When no zone is passed, the store’s default tax zone is used.
use Lunar\Models\Price;use Lunar\Models\TaxZone;$price = Price::find(1);$zone = TaxZone::where('name', 'EU')->first();$price->priceIncTax($zone);$price->priceExTax($zone);$price->comparePriceIncTax($zone);
This is useful when displaying product prices for a known visitor region before the cart is built. Once items are in the cart, the cart’s own tax_zone_id (see Setting the Tax Zone) drives tax-inclusive line totals.
The Lunar\DataTypes\Price class is used throughout Lunar whenever a price value needs formatting. It is not limited to the Lunar\Models\Price model. The following models also have attributes that return Lunar\DataTypes\Price instances:
The Lunar\Pricing\DefaultPriceFormatter ships with Lunar and handles most use cases for formatting a price.To demonstrate, start by creating a standard price model:
use Lunar\Models\Price;$priceModel = Price::create([ // ... 'price' => 1000, // Stored as an integer in the smallest currency unit 'min_quantity' => 1,]);// Lunar\DataTypes\Price$priceDataType = $priceModel->price;
Return the decimal representation of the price. The decimal value accounts for the number of decimal places configured on the currency. For example, if the currency has 2 decimal places:
These two values are identical in this example. The unitDecimal method factors in the unit_quantity of the purchasable model. Consider the following:
use Lunar\Models\ProductVariant;$productVariant = ProductVariant::create([ // ... 'unit_quantity' => 10,]);
By setting unit_quantity to 10, Lunar is told that 10 individual units make up this product at this price point. This is useful for items where a single unit would cost less than the smallest currency denomination (e.g. 0.001 EUR).
The formatted price uses the native PHP NumberFormatter. A locale and formatting style can be specified:
$priceDataType->formatted('fr'); // 10,00 £GB$priceDataType->formatted('en-gb', \NumberFormatter::SPELLOUT); // ten point zero zero$priceDataType->unitFormatted('en-gb'); // £10.00
A custom formatter must implement Lunar\Pricing\PriceFormatterInterface and accept $value, $currency, and $unitQty as constructor parameters.
<?phpnamespace App\Pricing;use Illuminate\Support\Facades\App;use Lunar\Models\Contracts\Currency;use Lunar\Pricing\PriceFormatterInterface;use NumberFormatter;class CustomPriceFormatter implements PriceFormatterInterface{ public function __construct( public int $value, public ?Currency $currency = null, public int $unitQty = 1 ) { if (! $this->currency) { $this->currency = \Lunar\Models\Currency::getDefault(); } } public function decimal(): float { // ... } public function unitDecimal(): float { // ... } public function formatted(): mixed { // ... } public function unitFormatted(): mixed { // ... }}
The methods can accept any number of arguments beyond those defined in the interface. The formatter is not bound to the same parameter signatures as DefaultPriceFormatter.Once implemented, register the custom formatter in config/lunar/pricing.php:
The Lunar\Base\Casts\Price cast resolves the currency from the model’s currency relationship. If no currency relationship exists, it falls back to the default currency.