# Money A simple package for working with money. Main features: - Simple API - Livewire integration - Custom currency support - Highly customizable formatting - Rounding logic for compliant accounting This package is our implementation of the [Money pattern](https://martinfowler.com/eaaCatalog/money.html). You can read more about why we built it and how it works on our forum: [New package: archtechx/money](https://forum.archte.ch/archtech/t/new-package-archtechxmoney). ## Installation Require the package via composer: ```sh composer require archtechx/money ``` # Usage The package has two main classes: - `Money` which represents monetary values - `Currency` which is extended by the currencies that you're using This document uses the terms [decimal value](#decimal-value), [base value](#base-value), [default currency](#default-currency), [current currency](#current-currency), [rounding](#rounding), [math decimals](#math-decimals), [display decimals](#display-decimals), and a few others. Refer to the [Terminology](#terminology) section for definitions. ## Money **Important**: As an implementation of the [Money pattern](https://martinfowler.com/eaaCatalog/money.html), the `Money` object creates a new instance after each operation. Meaning, **all `Money` instances are immutable**. To modify the value of a variable, re-initialize it with a new value: ```php // Incorrect $money = money(1500); $money->times(3); // ❌ $money->value(); // 1500 // Correct $money = money(1500); $money = $money->times(3); // ✅ $money->value(); // 4500 ``` ### Creating `Money` instances ```php // Using cents $money = money(1500); // $15.00; default currency $money = money(1500, 'EUR'); // 15.00 € $money = money(2000, new USD); // $20.00 $money = money(3000, CZK::class); // 20 Kč // Using decimals $money = Money::fromDecimal(15.00, 'EUR'); // 15.00 € $money = Money::fromDecimal(20.00, new USD); // $20.00 $money = Money::fromDecimal(30.00, CZK::class); // 20 Kč ``` ### Arithmetics ```php // Addition $money = money(1000); $money = $money->add(500); $money->value(); // 1500 // Subtraction $money = money(1000); $money = $money->subtract(500); $money->value(); // 500 // Multiplication $money = money(1000); $money = $money->multiplyBy(2); // alias: ->times() $money->value(); // 2000 // Division $money = money(1000); $money = $money->divideBy(2); $money->value(); // 500 ``` ### Converting money to a different currency ```php $money = money(2200); $money->convertTo(CZK::class); ``` ### Comparing money instances **Equality of monetary value** ```php // Assuming CZK is 25:1 USD // ✅ true money(100, USD::class)->equals(money(100, USD::class)); // ❌ false money(100, USD::class)->equals(money(200, USD::class)); // ✅ true money(100, USD::class)->equals(money(2500, CZK::class)); // ❌ false money(100, USD::class)->equals(money(200, CZK::class)); ``` **Equality of monetary value AND currency** ```php // Assuming CZK is 25:1 USD // ✅ true money(100, USD::class)->equals(money(100, USD::class)); // ❌ false: different monetary value money(100, USD::class)->equals(money(200, USD::class)); // ❌ false: different currency money(100, USD::class)->equals(money(2500, CZK::class)); // ❌ false: different currency AND monetary value money(100, USD::class)->equals(money(200, CZK::class)); ``` ### Adding fees You can use the `addFee()` or `addTax()` methods to add a % fee to the money: ```php $money = money(1000); $money = $money->addTax(20.0); // 20% $money->value(); // 1200 ``` ### Accessing the decimal value ```php $money = Money::fromDecimal(100.0, new USD); $money->value(); // 10000 $money->decimals(); // 100.0 ``` ### Formatting money You can format money using the `->formatted()` method: ```php $money = Money::fromDecimal(40.25, USD::class); $money->formatted(); // $40.25 ``` The method optionally accepts overrides for the [currency specification](#currency-logic): ```php $money = Money::fromDecimal(40.25, USD::class); // $ 40.25 USD $money->formatted(decimalSeparator: ',', prefix: '$ ', suffix: ' USD'); ``` The overrides can also be passed as an array: ```php $money = Money::fromDecimal(40.25, USD::class); // $ 40.25 USD $money->formatted(['decimalSeparator' => ',', 'prefix' => '$ ', 'suffix' => ' USD']); ``` ### Rounding money Some currencies, such as the Czech Crown (CZK), generally display final prices in full crowns, but use cents for the intermediate math operations. For example: ```php $money = Money::fromDecimal(3.30, CZK::class); $money->value(); // 330 $money->formatted(); // 3 Kč $money = $money->times(3); $money->value(); // 990 $money->formatted(); // 10 Kč ``` If the customer purchases a single `3.30` item, he pays `3 CZK`, but if he purchases three `3.30` items, he pays `10 CZK`. This rounding (to full crowns) is standard and legal per the accounting legislation, since it makes payments easier. However, the law requires you to keep track of the rounding difference for tax purposes. #### Getting the used rounding For that use case, our package lets you get the rounding difference using a simple method call: ```php $money = Money::fromDecimal(9.90, CZK::class); $money->decimals(); // 9.90 $money->formatted(); // 10 Kč $money->rounding(); // +0.10 Kč = 10 $money = Money::fromDecimal(3.30, CZK::class); $money->decimals(); // 3.30 $money->formatted(); // 3 Kč $money->rounding(); // -0.30 Kč = -30 ``` #### Applying rounding to money ```php // Using the currency rounding $money = Money::fromDecimal(9.90, CZK::class); $money->decimals(); // 9.90 $money = $money->rounded(); // currency rounding $money->decimals(); // 10.0 // Using custom rounding $money = Money::fromDecimal(2.22, USD::class); $money->decimals(); // 2.22 $money = $money->rounded(1); // custom rounding: 1 decimal $money->decimals(); // 2.20 ``` ## Currencies To work with the registered currencies, use the bound `CurrencyManager` instance, accessible using the `currencies()` helper. ### Creating a currency You can create a currency using one of the multiple supported syntaxes. ```php // anonymous Currency object $currency = new Currency( code: 'FOO', name: 'Foo currency', rate: 1.8, prefix: '# ', suffix: ' FOO', ); // array $currency = [ code: 'FOO', name: 'Foo currency', rate: 1.8, prefix: '# ', suffix: ' FOO', ]; // class class FOO extends Currency { protected string $code = 'FOO'; protected string $name = 'Foo currency'; protected float $rate = 1.8; protected string $prefix = '# '; protected string $suffix = ' FOO'; } ``` See the [Currency logic](#currency-logic) section for a list of available properties to configure. Note that when registering a currency, two values **must** be specified: 1. The code of the currency (e.g. `USD`) 2. The name of the currency (e.g. `United States Dollar`) ### Adding a currency Register a new currency: ```php currencies()->add(new USD); currencies()->add(USD::class); currencies()->add($currency); // object or array ``` ### Removing a specific currency To remove a specific currency, you can use the `remove()` method: ```php currencies()->remove('USD'); currencies()->remove(USD::class); ``` ### Removing all currencies To remove all currencies, you can use the `clear()` method: ```php currencies()->clear(); ``` ### Resetting currencies Can be useful in tests. This reverts all your changes and makes the `CurrencyManager` use `USD` as the default currency. ```php currencies()->reset(); ``` ### Currency logic Currencies can have the following properties: ```php protected string $code = null; protected string $name = null; protected float $rate = null; protected string $prefix = null; protected string $suffix = null; protected int $mathDecimals = null; protected int $displayDecimals = null; protected int $rounding = null; protected string $decimalSeparator = null; protected string $thousandsSeparator = null; ``` For each one, there's also a `public` method. Specifying a method can be useful when your currency config is dynamic, e.g. when the currency rate is taken from some API: ```php public function rate(): float { return cache()->remember("{$this->code}.rate", 3600, function () { return Http::get("https://api.currency.service/rate/USD/{$this->code}"); }); } ``` ### Setting the default currency You can set the [default currency](#default-currency) using the `setDefault()` method: ```php currencies()->setDefault('USD'); ``` ### Setting the current currency You can set the [current currency](#current-currency) using the `setCurrent()` method: ```php currencies()->setCurrent('USD'); ``` ### Persisting a selected currency across requests If your users can select the currency they want to see the app in, the package can automatically write the current currency to a persistent store of your choice, and read from that store on subsequent requests. For example, say we want to use the `currency` session key to keep track of the user's selected session. To implement that, we only need to do this: ```php currencies() ->storeCurrentUsing(fn (string $code) => session()->put('currency', $code)) ->resolveCurrentUsing(fn () => session()->get('currency')); ``` You can add this code to your AppServiceProvider's `boot()` method. Now, whenever the current currency is changed using `currencies()->setCurrent()`, perhaps in a route like this: ```php Route::get('/currency/change/{currency}', function (string $currency) { currencies()->setCurrent($currency); return redirect()->back(); }); ``` it will also be written to the `currency` session key. The route can be used by a `