{"id":20012310,"url":"https://github.com/archtechx/money","last_synced_at":"2025-05-15T17:01:36.853Z","repository":{"id":40302468,"uuid":"386329627","full_name":"archtechx/money","owner":"archtechx","description":"A simple package for working with money.","archived":false,"fork":false,"pushed_at":"2025-03-11T16:41:14.000Z","size":50,"stargazers_count":164,"open_issues_count":3,"forks_count":12,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-31T21:46:19.943Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/archtechx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-15T15:01:30.000Z","updated_at":"2025-03-11T16:38:47.000Z","dependencies_parsed_at":"2024-06-18T22:42:57.480Z","dependency_job_id":"4958390a-e9b6-4995-872f-2d03edde5b5c","html_url":"https://github.com/archtechx/money","commit_stats":{"total_commits":42,"total_committers":8,"mean_commits":5.25,"dds":0.6190476190476191,"last_synced_commit":"8a0eeae7c762c343f4bd1efac0e3a5575d3a03f5"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":"archtechx/template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fmoney","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fmoney/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fmoney/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fmoney/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/archtechx","download_url":"https://codeload.github.com/archtechx/money/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247737788,"owners_count":20987721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T07:29:42.858Z","updated_at":"2025-04-07T22:07:08.641Z","avatar_url":"https://github.com/archtechx.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Money\n\nA simple package for working with money.\n\nMain features:\n- Simple API\n- Livewire integration\n- Custom currency support\n- Highly customizable formatting\n- Rounding logic for compliant accounting\n\nThis package is our implementation of the [Money pattern](https://martinfowler.com/eaaCatalog/money.html).\n\nYou 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).\n\n## Installation\n\nRequire the package via composer:\n```sh\ncomposer require archtechx/money\n```\n# Usage\n\nThe package has two main classes:\n- `Money` which represents monetary values\n- `Currency` which is extended by the currencies that you're using\n\nThis 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.\n\n## Money\n\n**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:\n\n```php\n// Incorrect\n$money = money(1500);\n$money-\u003etimes(3); // ❌\n$money-\u003evalue(); // 1500\n\n// Correct\n$money = money(1500);\n$money = $money-\u003etimes(3); // ✅\n$money-\u003evalue(); // 4500\n```\n\n### Creating `Money` instances\n```php\n// Using cents\n$money = money(1500); // $15.00; default currency\n$money = money(1500, 'EUR'); // 15.00 €\n$money = money(2000, new USD); // $20.00\n$money = money(3000, CZK::class); // 30 Kč\n\n// Using decimals\n$money = Money::fromDecimal(15.00, 'EUR'); // 15.00 €\n$money = Money::fromDecimal(20.00, new USD); // $20.00\n$money = Money::fromDecimal(30.00, CZK::class); // 30 Kč\n```\n\n### Arithmetics\n\n```php\n// Addition\n$money = money(1000);\n$money = $money-\u003eadd(500);\n$money-\u003evalue(); // 1500\n\n// Subtraction\n$money = money(1000);\n$money = $money-\u003esubtract(500);\n$money-\u003evalue(); // 500\n\n// Multiplication\n$money = money(1000);\n$money = $money-\u003emultiplyBy(2); // alias: -\u003etimes()\n$money-\u003evalue(); // 2000\n\n// Division\n$money = money(1000);\n$money = $money-\u003edivideBy(2);\n$money-\u003evalue(); // 500\n```\n\n### Converting money to a different currency\n\n```php\n$money = money(2200);\n$money-\u003econvertTo(CZK::class);\n```\n\n### Comparing money instances\n\n**Equality of monetary value**\n```php\n// Assuming CZK is 25:1 USD\n\n// ✅ true\nmoney(100, USD::class)-\u003eequals(money(100, USD::class));\n\n// ❌ false\nmoney(100, USD::class)-\u003eequals(money(200, USD::class));\n\n// ✅ true\nmoney(100, USD::class)-\u003eequals(money(2500, CZK::class));\n\n// ❌ false\nmoney(100, USD::class)-\u003eequals(money(200, CZK::class));\n```\n\n**Equality of monetary value AND currency**\n```php\n// Assuming CZK is 25:1 USD\n\n// ✅ true\nmoney(100, USD::class)-\u003eis(money(100, USD::class));\n\n// ❌ false: different monetary value\nmoney(100, USD::class)-\u003eis(money(200, USD::class));\n\n// ❌ false: different currency\nmoney(100, USD::class)-\u003eis(money(2500, CZK::class));\n\n// ❌ false: different currency AND monetary value\nmoney(100, USD::class)-\u003eis(money(200, CZK::class));\n```\n\n### Adding fees\n\nYou can use the `addFee()` or `addTax()` methods to add a % fee to the money:\n```php\n$money = money(1000);\n$money = $money-\u003eaddTax(20.0); // 20%\n$money-\u003evalue(); // 1200\n```\n\n### Accessing the decimal value\n\n```php\n$money = Money::fromDecimal(100.0, new USD);\n$money-\u003evalue(); // 10000\n$money-\u003edecimal(); // 100.0\n```\n\n### Formatting money\n\nYou can format money using the `-\u003eformatted()` method. It takes [display decimals](#display-decimals) into consideration.\n\n```php\n$money = Money::fromDecimal(40.25, USD::class);\n$money-\u003eformatted(); // $40.25\n```\n\nThe method optionally accepts overrides for the [currency specification](#currency-logic):\n```php\n$money = Money::fromDecimal(40.25, USD::class);\n\n// $ 40.25 USD\n$money-\u003eformatted(decimalSeparator: ',', prefix: '$ ', suffix: ' USD');\n```\n\nThe overrides can also be passed as an array:\n```php\n$money = Money::fromDecimal(40.25, USD::class);\n\n// $ 40.25 USD\n$money-\u003eformatted(['decimalSeparator' =\u003e ',', 'prefix' =\u003e '$ ', 'suffix' =\u003e ' USD']);\n```\n\nThere's also `-\u003erawFormatted()` if you wish to use [math decimals](#math-decimals) instead of [display decimals](#display-decimals).\n```php\n$money = Money::new(123456, CZK::class);\n$money-\u003erawFormatted(); // 1 234,56 Kč\n```\n\nConverting the formatted value back to the `Money` instance is also possible. The package tries to extract the currency from the provided string:\n```php\n$money = money(1000);\n$formatted = $money-\u003eformatted(); // $10.00\n$fromFormatted = Money::fromFormatted($formatted);\n$fromFormatted-\u003eis($money); // true\n```\n\nIf you had passed overrides while [formatting the money instance](#formatting-money), the same can passed to this method.\n```php\n$money = money(1000);\n$formatted = $money-\u003eformatted(['prefix' =\u003e '$ ', 'suffix' =\u003e ' USD']); // $ 10.00 USD\n$fromFormatted = Money::fromFormatted($formatted, USD::class, ['prefix' =\u003e '$ ', 'suffix' =\u003e ' USD']);\n$fromFormatted-\u003eis($money); // true\n```\n\nNotes:\n1) If currency is not specified and none of the currencies match the prefix and suffix, an exception will be thrown.\n2) If currency is not specified and multiple currencies use the same prefix and suffix, an exception will be thrown.\n3) `fromFormatted()` misses the cents if the [math decimals](#math-decimals) are greater than [display decimals](#display-decimals).\n\n### Rounding money\n\nSome currencies, such as the Czech Crown (CZK), generally display final prices in full crowns, but use cents for the intermediate math operations. For example:\n\n```php\n$money = Money::fromDecimal(3.30, CZK::class);\n$money-\u003evalue(); // 330\n$money-\u003eformatted(); // 3 Kč\n\n$money = $money-\u003etimes(3);\n$money-\u003evalue(); // 990\n$money-\u003eformatted(); // 10 Kč\n```\n\nIf the customer purchases a single `3.30` item, he pays `3 CZK`, but if he purchases three `3.30` items, he pays `10 CZK`.\n\nThis 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.\n\n#### Getting the used rounding\n\nFor that use case, our package lets you get the rounding difference using a simple method call:\n```php\n$money = Money::fromDecimal(9.90, CZK::class);\n$money-\u003edecimal(); // 9.90\n$money-\u003eformatted(); // 10 Kč\n$money-\u003erounding(); // +0.10 Kč = 10\n\n$money = Money::fromDecimal(3.30, CZK::class);\n$money-\u003edecimal(); // 3.30\n$money-\u003eformatted(); // 3 Kč\n$money-\u003erounding(); // -0.30 Kč = -30\n```\n\n#### Applying rounding to money\n\n```php\n// Using the currency rounding\n$money = Money::fromDecimal(9.90, CZK::class);\n$money-\u003edecimal(); // 9.90\n$money = $money-\u003erounded(); // currency rounding\n$money-\u003edecimal(); // 10.0\n\n// Using custom rounding\n$money = Money::fromDecimal(2.22, USD::class);\n$money-\u003edecimal(); // 2.22\n$money = $money-\u003erounded(1); // custom rounding: 1 decimal\n$money-\u003edecimal(); // 2.20\n```\n\n## Currencies\n\nTo work with the registered currencies, use the bound `CurrencyManager` instance, accessible using the `currencies()` helper.\n\n### Creating a currency\n\nThis package provides only USD currency by default.\n\nYou can create a currency using one of the multiple supported syntaxes.\n```php\n// anonymous Currency object\n$currency = new Currency(\n    code: 'FOO',\n    name: 'Foo currency',\n    rate: 1.8,\n    prefix: '# ',\n    suffix: ' FOO',\n);\n\n// array\n$currency = [\n    'code' =\u003e 'FOO',\n    'name' =\u003e 'Foo currency',\n    'rate' =\u003e 1.8,\n    'prefix' =\u003e '# ',\n    'suffix' =\u003e ' FOO',\n];\n\n// class\nclass FOO extends Currency\n{\n    protected string $code = 'FOO';\n    protected string $name = 'Foo currency';\n    protected float $rate = 1.8;\n    protected string $prefix = '# ';\n    protected string $suffix = ' FOO';\n}\n```\n\nSee 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:\n1. The code of the currency (e.g. `USD`)\n2. The name of the currency (e.g. `United States Dollar`)\n\n### Adding a currency\n\nRegister a new currency:\n```php\ncurrencies()-\u003eadd(new USD);\ncurrencies()-\u003eadd(USD::class);\ncurrencies()-\u003eadd($currency); // object or array\n```\n\n### Removing a specific currency\n\nTo remove a specific currency, you can use the `remove()` method:\n```php\ncurrencies()-\u003eremove('USD');\ncurrencies()-\u003eremove(USD::class);\n```\n\n### Removing all currencies\n\nTo remove all currencies, you can use the `clear()` method:\n```php\ncurrencies()-\u003eclear();\n```\n\n### Resetting currencies\n\nCan be useful in tests. This reverts all your changes and makes the `CurrencyManager` use `USD` as the default currency.\n\n```php\ncurrencies()-\u003ereset();\n```\n\n### Currency logic\n\nCurrencies can have the following properties:\n```php\nprotected string $code = null;\nprotected string $name = null;\nprotected float $rate = null;\nprotected string $prefix = null;\nprotected string $suffix = null;\nprotected int $mathDecimals = null;\nprotected int $displayDecimals = null;\nprotected int $rounding = null;\nprotected string $decimalSeparator = null;\nprotected string $thousandsSeparator = null;\n```\n\nFor 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:\n\n```php\npublic function rate(): float\n{\n    return cache()-\u003eremember(\"{$this-\u003ecode}.rate\", 3600, function () {\n        return Http::get(\"https://api.currency.service/rate/USD/{$this-\u003ecode}\");\n    });\n}\n```\n\n### Setting the default currency\n\nYou can set the [default currency](#default-currency) using the `setDefault()` method:\n```php\ncurrencies()-\u003esetDefault('USD');\n```\n\n### Setting the current currency\n\nYou can set the [current currency](#current-currency) using the `setCurrent()` method:\n```php\ncurrencies()-\u003esetCurrent('USD');\n```\n\n### Persisting a selected currency across requests\n\nIf 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.\n\nFor 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:\n```php\ncurrencies()\n    -\u003estoreCurrentUsing(fn (string $code) =\u003e session()-\u003eput('currency', $code))\n    -\u003eresolveCurrentUsing(fn () =\u003e session()-\u003eget('currency'));\n```\nYou can add this code to your AppServiceProvider's `boot()` method.\n\nNow, whenever the current currency is changed using `currencies()-\u003esetCurrent()`, perhaps in a route like this:\n```php\nRoute::get('/currency/change/{currency}', function (string $currency) {\n    currencies()-\u003esetCurrent($currency);\n\n    return redirect()-\u003eback();\n});\n```\nit will also be written to the `currency` session key. The route can be used by a `\u003cform\u003e` in your navbar, or any other UI element.\n\n# Terminology\n\nThis section explains the terminology used in the package.\n\n## Values\n\nMultiple different things can be meant by the \"value\" of a `Money` object. For that reason, we use separate terms.\n\n### Base value\n\nThe base value is the value passed to the `money()` helper:\n```php\n$money = money(1000);\n```\nand returned from the `-\u003evalue()` method:\n```php\n$money-\u003evalue(); // 1000\n```\n\nThis is the actual integer value of the money. In most currencies this will be the cents.\n\nThe package uses the base value for all money calculations.\n\n### Decimal value\n\nThe decimal value isn't used for calculations, but it is the human-readable one. It's typically used in the formatted value.\n```php\n$money = Money::fromDecimal(100.0); // $100 USD\n$money-\u003evalue(); // 10000\n$money-\u003edecimal(); // 100.0\n```\n\n### Value in default currency\n\nThis is the value of a `Money` object converted to the default currency.\n\nFor example, you may want to let administrators enter the price of a product in any currency, but still store it in the default currency.\n\nIt'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.\n\nOf 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](#json-serialization) if you wish to store the entire `Money` object in a single database column.\n\nStoring the integer price and the string currency as separate columns is, of course, perfectly fine as well.\n\n### Formatted value\n\nThe 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](#display-decimals).\n\nFor example:\n```php\nmoney(123456, new CZK)-\u003eformatted(); // 1 235 Kč\n```\n\nNote that the [display decimals](#display-decimals) can be different from the [math decimals](#math-decimals).\n\nFor 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.\n\n### Raw formatted value\n\nFor 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:\n```php\nmoney(123456, new CZK)-\u003erawFormatted(); // 1 234,56 Kč\n```\n\nThis is mostly useful for currencies like the Czech Crown which generally don't use cents, but **can** use them in specific cases.\n\n## Currencies\n\n### Current currency\n\nThe current currency refers to the currently used currency.\n\nBy default, the package doesn't use it anywhere. All calls such as `money()` will use the provided currency, or the default currency.\n\nThe 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.\n\n### Default currency\n\nThe default currency is the currency that Money defaults to in the context of your codebase.\n\nThe `money()` helper, `Money::fromDecimal()` method, and `new Money()` all use this currency (unless a specific one is provided).\n\nIt can be a good idea to use the default currency for data storage. See more about this in the [Value in default currency](#value-in-default-currency) section.\n\n### Math decimals\n\nThe math decimals refer to the amount of decimal points the currency has in a math context.\n\nAll math operations are still done in floats, using the [base value](#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.\n\n### Display decimals\n\nThe display decimals refer to the amount of decimals used in the [formatted value](#formatted-value).\n\n# Extra features\n\n## Livewire support\n\nThe package supports Livewire out of the box. You can typehint any Livewire property as `Money` and the monetary value \u0026 currency will be stored in the component's state.\n\n```php\nclass EditProduct extends Component\n{\n    public Money $price;\n\n    // ...\n}\n```\n\nLivewire'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.\n\nThe component can look roughly like this:\n```html\n\u003cdiv x-data=\"{\n    money: {\n        value: {{ $price-\u003edecimal() }},\n        currency: {{ $price-\u003ecurrency()-\u003ecode() }},\n    },\n\n    init() {\n        $watch('money', money =\u003e $wire.set('money', {\n            value: Math.round(money.value / 100),\n            currency: money.currency.\n        }))\n    },\n}\" x-init=\"init\"\u003e\n    Currency: \u003cselect x-model=\"currency\"\u003e...\u003c/select\u003e\n    Price: \u003cinput x-model=\"value\" type=\"number\" step=\"0.01\"\u003e\n\u003c/div\u003e\n```\n\n## JSON serialization\n\nBoth currencies and `Money` instances can be converted to JSON, and instantiated from JSON.\n\n```php\n$currency = new CZK;\n$json = json_encode($currency);\n$currency = Currency::fromJson($json);\n\n$foo = money(100, 'CZK');\n$bar = Money::fromJson($money-\u003etoJson());\n$money-\u003eis($bar); // true\n```\n\n## Tips\n\n### 💡 Accepted currency code formats\n\nMost methods which accept a currency accept it in any of these formats:\n```php\ncurrency(USD::class);\ncurrency(new USD);\ncurrency('USD');\n\nmoney(1000, USD::class)-\u003econvertTo('CZK');\nmoney(1000, 'USD')-\u003econvertTo(new CZK);\nmoney(1000, new USD)-\u003econvertTo(CZK::class);\n```\n\n### 💡 Dynamically add currencies\n\nClass currencies are elegant, but not necessary. If your currency specs come from the database, or some API, you can register them as arrays.\n\n```php\n// LoadCurrencies middleware\n\ncurrencies()-\u003eadd(cache()-\u003eremember('currencies', 3600, function () {\n    return UserCurrencies::where('user_id', auth()-\u003eid())-\u003eget()-\u003etoArray();\n});\n```\n\nWhere the DB call returns an array of array currencies following the [format mentioned above](#creating-a-currency).\n\n## Development \u0026 contributing\n\nRun all checks locally:\n\n```sh\n./check\n```\n\nCode style will be automatically fixed by php-cs-fixer.\n\nNo database is needed to run the tests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchtechx%2Fmoney","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchtechx%2Fmoney","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchtechx%2Fmoney/lists"}