A simple package for working with money.

Related tags

Miscellaneous money
Overview

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.

You can read more about why we built it and how it works on our forum: New package: archtechx/money.

Installation

Require the package via composer:

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, base value, default currency, current currency, rounding, math decimals, display decimals, and a few others. Refer to the Terminology section for definitions.

Money

Important: As an implementation of the Money pattern, 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:

// Incorrect
$money = money(1500);
$money->times(3); // ❌
$money->value(); // 1500

// Correct
$money = money(1500);
$money = $money->times(3); // ✅
$money->value(); // 4500

Creating Money instances

// 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

// 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

$money = money(2200);
$money->convertTo(CZK::class);

Comparing money instances

Equality of monetary value

// 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

// 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:

$money = money(1000);
$money = $money->addTax(20.0); // 20%
$money->value(); // 1200

Accessing the decimal value

$money = Money::fromDecimal(100.0, new USD);
$money->value(); // 10000
$money->decimals(); // 100.0

Formatting money

You can format money using the ->formatted() method:

$money = Money::fromDecimal(40.25, USD::class);
$money->formatted(); // $40.25

The method optionally accepts overrides for the currency specification:

$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:

$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:

$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:

$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

// 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.

// 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 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:

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:

currencies()->remove('USD');
currencies()->remove(USD::class);

Removing all currencies

To remove all currencies, you can use the clear() method:

currencies()->clear();

Resetting currencies

Can be useful in tests. This reverts all your changes and makes the CurrencyManager use USD as the default currency.

currencies()->reset();

Currency logic

Currencies can have the following properties:

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:

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 using the setDefault() method:

currencies()->setDefault('USD');

Setting the current currency

You can set the current currency using the setCurrent() method:

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:

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:

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 <form> in your navbar, or any other UI element.

Terminology

This section explains the terminology used in the package.

Values

Multiple different things can be meant by the "value" of a Money object. For that reason, we use separate terms.

Base value

The base value is the value passed to the money() helper:

$money = money(1000);

and returned from the ->value() method:

$money->value(); // 1000

This is the actual integer value of the money. In most currencies this will be the cents.

The package uses the base value for all money calculations.

Decimal value

The decimal value isn't used for calculations, but it is the human-readable one. It's typically used in the formatted value.

$money = Money::fromDecimal(100.0); // $100 USD
$money->value(); // 10000
$money->decimal(); // 100.0

Value in default currency

This is the value of a Money object converted to the default currency.

For example, you may want to let administrators enter the price of a product in any currency, but still store it in the default currency.

It's generally recommended to use the default currency in the "code land". And only use other currencies for displaying prices to the user (e.g. customer) or letting the administrators enter prices of things in a currency that works for them.

Of course, there are exceptions, and sometimes you may want to store both the currency and the value of an item. For that, the package has JSON encoding features if you wish to store the entire Money object in a single database column.

Storing the integer price and the string currency as separate columns is, of course, perfectly fine as well.

Formatted value

The formatted value is the Money value displayed per its currency spec. It may use the prefix, suffix, decimal separator, thousands separator, and the display decimals.

For example:

money(123456, new CZK)->formatted(); // 1 235 Kč

Note that the display decimals can be different from the math decimals.

For the Czech Crown (CZK), the display decimals will be 0, but the math decimals will be 2. Meaning, cents are used for money calculations, and the decimal() method will return the base value divided by 100, but the display decimals don't include any cents.

Raw formatted value

For the inverse of what was just explained above, you can use the rawFormatted() method. This returns the formatted value, but uses the math decimals for the display decimals. Meaning, the value in the example above will be displayed including cents:

money(123456, new CZK)->formatted(); // 1 235,56 Kč

This is mostly useful for currencies like the Czech Crown which generally don't use cents, but can use them in specific cases.

Currencies

Current currency

The current currency refers to the currently used currency.

By default, the pacakge doesn't use it anywhere. All calls such as money() will use the provided currency, or the default currency.

The current currency is something you can convert money to in the final step of calculations, right before displaying it to the user in the browser.

Default currency

The default currency is the currency that Money defaults to in the context of your codebase.

The money() helper, Money::fromDecimal() method, and new Money() all use this currency (unless a specific one is provided).

It can be a good idea to use the default currency for data storage. See more about this in the Value in default currency section.

Math decimals

The math decimals refer to the amount of decimal points the currency has in a math context.

All math operations are still done in floats, using the base value, but the math decimals are used for knowing how to round the money after each operation, how to instantiate it with the Money::fromDecimal() method, and more.

Display decimals

The display decimals refer to the amount of decimals used in the formatted value.

Extra features

Livewire support

The package supports Livewire out of the box. You can typehint any Livewire property as Money and the monetary value & currency will be stored in the component's state.

class EditProduct extends Component
{
    public Money $price;

    // ...
}

Livewire's custom type support isn't advanced yet, so this is a bit harder to use in the Blade view — a wrapper Alpine component is recommended. In a future release, wire:model will be supported for currency and value directly.

The component can look roughly like this:

<div x-data="{
    money: {
        value: {{ $price->decimal() }},
        currency: {{ $price->currency()->code() }},
    },

    init() {
        $watch('money', money => $wire.set('money', {
            value: Math.round(money.value / 100),
            currency: money.currency.
        }))
    },
}" x-init="init">
    Currency: <select x-model="currency">...</select>
    Price: <input x-model="value" type="number" step="0.01">
</div>

JSON serialization

Both currencies and Money instances can be converted to JSON, and instantiated from JSON.

$currency = new CZK;
$json = json_encode($currency);
$currency = Currency::fromJson($json);

$foo = money(100, 'CZK');
$bar = Money::fromJson($money->toJson());
$money->is($bar); // true

Tips

💡 Accepted currency code formats

Most methods which accept a currency accept it in any of these formats:

currency(USD::class);
currency(new USD);
currency('USD');

money(1000, USD::class)->convertTo('CZK');
money(1000, 'USD')->convertTo(new CZK);
money(1000, new USD)->convertTo(CZK::class);

💡 Dynamically add currencies

Class currencies are elegant, but not necessary. If your currency specs come from the database, or some API, you can register them as arrays.

// LoadCurrencies middleware

currencies()->add(cache()->remember('currencies', 3600, function () {
    return UserCurrencies::where('user_id', auth()->id())->get()->toArray();
});

Where the DB call returns an array of array currencies following the format mentioned above.

Development & contributing

Run all checks locally:

./check

Code style will be automatically fixed by php-cs-fixer.

No database is needed to run the tests.

Comments
  • Change currency

    Change currency

    I have used several different currency packages. So maybe I'm using this package incorrectly.

    I have added two currencies, SEK and EUR with SEK as default. EUR has a rate of 0.097027 in relation to SEK.

    If I use the helper money and then runcurrencies()->setCurrent ('EUR'); only suffixes and prefixes are affected. Prices are still in SEK value. So they have not been converted.

    Do I have to use convertTo() too or how is the idea to work? I expected all prices to be converted to EURO.

    opened by dalholm 5
  • Money instance creation from a formatted string

    Money instance creation from a formatted string

    This PR adds an additional method to create a money instance from a formatted string.

    Changes overview:

    • New static method fromFormatted() inside the Money class which:
      1. Accepts currency and currency configuration overrides (optional)
      2. Tries to extract the currency if it is not provided by the user
      3. Throws an exception if no currency matches the prefix and suffix of the formatted string
      4. Throws an exception if multiple currencies match the prefix and suffix of the formatted string
      5. Calls the PriceFormatter::resolve() to get the decimal value
    • New static method resolve() inside the PriceFormatter class to manage the decimal extraction. A process on formatted string:
      1. Removes the prefix and suffix
      2. Removes everything other than the number of the decimal separator
      3. Replaces decimal formatted string with a . (period) to allow the following float conversion
      4. Converts to float and returns the decimal value
    • New tests inside the MoneyTest class for the new method
    • Readme updates to inform users about the new method's functionality
    • Improve code readability by using the pow() PHP function

    Closes https://github.com/archtechx/money/issues/1

    opened by gauravmak 3
  • Money class is final

    Money class is final

    I wanted to add a custom object cast for the Money, but unfortunately the class is marked as final and therefore can't be extended. I got around this by creating another class which uses the packages money class under the hood, but it would be much more convenient if I could extend your package's money class.

    Any chance you drop the final keyword?

    opened by Naoray 3
  • Change default mathDecimals value

    Change default mathDecimals value

    Is there a way to alter the default value for mathDecimals? Only using 2 mathDecimals causes issues when you want to work with decimal numbers. For instance if you want to do the following:

    // mathDecimals = 2
    
    $money1 = Money::fromDecimal(100.12);
    $money1 = $money1->subtractTax(25.0);
    
    $money2 = new Money($money1->value());
    $money2 = $money2->addTax(25.0);
    
    dd($money1, $money2->decimal()); // $money2->decimal() will return 100.13, instead of 100.12.
    

    So my issue is that it is not high enough precision in storing prices in cent, I need to store it in one hundredth of a cent, which will require mathDecimals to be 4 instead. The result of that change, will be:

    // mathDecimals = 4
    
    $money1 = Money::fromDecimal(100.12);
    $money1 = $money1->subtractTax(25.0);
    
    $money2 = new Money($money1->value());
    $money2 = $money2->addTax(25.0);
    
    dd($money1, $money2->decimal()); // $money2->decimal() will return 100.12, as expected.
    

    Perhaps a config file could be added for the package, where you could override the default mathDecimal value?

    opened by 4ice 1
  • Divide rate with 100

    Divide rate with 100

    Needed for the calculation to be correct according to the documentation. $money->addTax(20) and not $money->addTax(0.2)

    Of course, the easy way is just to update the documentation. But i would prefer the documented version as in this pull request e.g: $money->addTax(20)

    opened by dalholm 1
  • Change currency rate on the fly

    Change currency rate on the fly

    Before I start something unnecessary, I thought I would check if there is a good way to update an exchange rate on the fly?

    Use case You have an order with a specific exchange rate. A couple of days later, the course has changed and you want to present what they paid.

    Note, In my system, all prices are saved in a base currency.

    An idea would be something similar

    currency()->updateRate(1.0);
    
    enhancement 
    opened by dalholm 3
Releases(v0.4.0)
  • v0.4.0(May 16, 2022)

    What's Changed

    • Fixes #11 - Remove trailing zeros from formatted price by @4ice in https://github.com/archtechx/money/pull/18

    New Contributors

    • @4ice made their first contribution in https://github.com/archtechx/money/pull/18

    Full Changelog: https://github.com/archtechx/money/compare/v0.3.0...v0.4.0

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Apr 5, 2022)

    What's Changed

    • Readme updates and code cleanup by @gauravmak in https://github.com/archtechx/money/pull/14
    • Docker-compose not required by @gauravmak in https://github.com/archtechx/money/pull/15
    • Money instance creation from a formatted string by @gauravmak in https://github.com/archtechx/money/pull/16

    New Contributors

    • @gauravmak made their first contribution in https://github.com/archtechx/money/pull/14

    Full Changelog: https://github.com/archtechx/money/compare/v0.2.4...v0.3.0

    Source code(tar.gz)
    Source code(zip)
  • v0.2.5(Feb 8, 2022)

    What's Changed

    • Support Laravel 9 by @abrardev99 in https://github.com/archtechx/money/pull/13

    New Contributors

    • @abrardev99 made their first contribution in https://github.com/archtechx/money/pull/13

    Full Changelog: https://github.com/archtechx/money/compare/v0.2.3...v0.2.5

    Source code(tar.gz)
    Source code(zip)
  • v0.2.4(Jan 20, 2022)

    What's Changed

    • Support Laravel 9 by @abrardev99 in https://github.com/archtechx/money/pull/13

    New Contributors

    • @abrardev99 made their first contribution in https://github.com/archtechx/money/pull/13

    Full Changelog: https://github.com/archtechx/money/compare/v0.2.3...v0.2.4

    Source code(tar.gz)
    Source code(zip)
  • v0.2.3(Dec 28, 2021)

    Patch release.

    What's Changed

    • Divide rate with 100 by @dalholm in https://github.com/archtechx/money/pull/6

    New Contributors

    • @dalholm made their first contribution in https://github.com/archtechx/money/pull/6

    Full Changelog: https://github.com/archtechx/money/compare/v0.2.2...v0.2.3

    Source code(tar.gz)
    Source code(zip)
  • v0.2.2(Dec 13, 2021)

  • v0.2.1(Dec 9, 2021)

  • v0.2.0(Nov 28, 2021)

  • v0.1.0(Nov 16, 2021)

Owner
ARCHTECH
Meticulously architected web applications.
ARCHTECH
A plugin for working with popular money libraries in Pest

This package is a plugin for Pest PHP. It allows you to write tests against monetary values provided by either brick/money or moneyphp/money using the same declarative syntax you're used to with Pest's expectation syntax.

Luke Downing 19 Oct 30, 2022
Laravel package to convert English numbers to Bangla number or Bangla text, Bangla month name and Bangla Money Format

Number to Bangla Number, Word or Month Name in Laravel | Get Wordpress Plugin Laravel package to convert English numbers to Bangla number or Bangla te

Md. Rakibul Islam 50 Dec 26, 2022
A pool for players to mine money

MoneyPool v1.0.2 A pool for players to mine money

Toby 4 Jun 22, 2021
The stock market inflation adjusted for the US-money supply

Inflation Chart Inflation Chart is a project to find the intrinsic value of stock markets, stock prices, goods and services by adjusting them to the a

levelsio 96 Apr 14, 2022
> Create e-wallet, send money, withdraw and check balance all via USSD protocol

Mobile Money USSD solution Create e-wallet, send money, withdraw and check balance all via USSD protocol Create e-wallet Step 1 Step 2 Step 3 Step 4 S

Emmanuel HAKORIMANA 1 Nov 3, 2021
A money and currency library for PHP

Brick\Money A money and currency library for PHP. Introduction Working with financial data is a serious matter, and small rounding mistakes in an appl

Brick 1.3k Jan 5, 2023
A PHPStan package that supports working with Extbase

PHPStan for Extbase This package provides a couple of stubs and services to make your life easier when working with PHPStan and Extbase. Examples clas

Alexander Schnitzler 7 Dec 10, 2021
Strings Package provide a fluent, object-oriented interface for working with multibyte string

Strings Package provide a fluent, object-oriented interface for working with multibyte string, allowing you to chain multiple string operations together using a more readable syntax compared to traditional PHP strings functions.

Glowy PHP 14 Mar 12, 2022
A set of utilities for working with vk api!

vk-utils Документация на русском языке Installation composer require labile/vk-utils How to use it? Simple example use Astaroth\VkUtils\Client; $api

null 1 Jan 3, 2022
Iran decoration platform is an open source Php web application where you can find your job as a freelancer working in people home in decoration positions and others.

Iran-Decoration Platform Iran decoration platform is an open source Php web application where you can find your job as a freelancer working in people

AmirHossein Mohammadi 8 Dec 14, 2022
I am actively working on this - v1.3 stable-DEV

Batch-OBF-php This Obf .bat files v swag ngl only reason im posting the code is cuz i people have said im ratting them by changing the code of the .ba

INZO_Technologies 1 Jan 26, 2022
A library for working with StatsD in PHP

StatsD PHP Library A library for working with StatsD in PHP. Install Via Composer: composer require league/statsd To use the Statsd Service Provider,

The League of Extraordinary Packages 339 Dec 19, 2022
BetterWPDB - Keeps you safe and sane when working with custom tables in WordPress.

BetterWPDB - Keeps you safe and sane when working with custom tables in WordPress.

Snicco 21 Dec 15, 2022
PHP library with basic objects and more for working with Facebook/Metas Conversions API

PHP library with basic objects and more for working with Facebook/Metas Conversions API Installation The easiest way to install this library is by ins

null 5 Dec 5, 2022
PHP library for working with Demandware XML files

PHP Demandware XML A PHP library for working with Demandware XML files. Exporting: Supports category, product, variant and assignment files and allows

Fusions PIM 3 Dec 14, 2022
Tools for working with the SPDX license list and validating licenses.

composer/spdx-licenses SPDX (Software Package Data Exchange) licenses list and validation library. Originally written as part of composer/composer, no

Composer 1.4k Dec 26, 2022
A complete solution for group projects in organizations that lets you track your work in any scenario. Working in a team is a cumbersome task, ease it using our project management system.

SE-Project-Group24 What is Evolo? Evolo is Dashboard based Project Management System. A complete solution for group projects in organizations that let

Devanshi Savla 2 Oct 7, 2022
Spin up a working Statamic instance quickly & easily with Docker

Spin Up Statamic Allows you to create your own self-contained Statamic project complete site config, Antlers/Blade/Twig template files, assets, and de

nystudio107 11 Jun 2, 2023