laranex/laravel-money
A Laravel package for working with money. It provides value objects, arithmetic, formatting, and currency handling so you can store, calculate, and display amounts accurately without floating-point errors, making it useful for pricing, billing, and e-commerce features.
This release (v4.0.0-alpha.8) ships with an autogenerated, empty changelog — it is a diff-only alpha bump with no documented features, fixes, or breaking changes. Treat it as a maintenance release and do not rewrite existing money-handling code because of it. The practical guidance below is unchanged from the v4 alpha line.
Minimal steps to adopt:
composer require laranex/laravel-money — and because you are on an alpha, pin the exact version ("laranex/laravel-money": "4.0.0-alpha.8") rather than using ^4.0. Alpha-to-alpha bumps have historically shipped without release notes, so an unpinned constraint can silently move you across API changes.php artisan vendor:publish --tag=...) and set your default currency, default locale, and rounding preference there — not in code.src/ casts/rules directories; with an empty changelog these are the authoritative source of truth for what the v4 API actually accepts.decimal(10,2) column + float arithmetic on an invoice or order total. Define the column as an integer minor-units column (or keep the decimal but go through the package's cast), add the cast to the Eloquent model, and render via the package's formatting helper so no number_format() remains in your Blade files.Verify on a throwaway branch before touching production models — alphas of this package have reshuffled namespaces between minors, and alpha.8 gives you no changelog to tell you whether it did so again.
Model casts as the boundary. The highest-value pattern is to let the cast own the conversion: database value ↔ money object. Controllers and jobs then never see integers-of-cents or floats. Anything that reads $order->total gets a money instance with a currency attached, which kills the class of bug where a total is formatted with the wrong symbol because the currency lived only in a view.
Money arithmetic in the domain layer, formatting at the edge. Additions, multiplications (tax, quantity), and allocations (splitting a discount across line items) belong in services or value objects. Formatting and symbol placement belong in resources, API serializers, and Blade. Keep the two apart so a change of locale never touches business logic, and so tests assert integers/strings rather than rendered output.
Multi-currency as a first-class column. Store the currency code beside every amount and read it from the model rather than from config. A single default_currency config value is fine for a single-market app, but the moment you add a second currency, config-driven currency becomes a data-corruption bug — historical rows get reinterpreted under a new default.
API resources. Serialize money as a pair ({"amount": "1250.00", "currency": "EUR"}) or as integer minor units plus a currency code, never as a bare float. Consumers in JS will break on floats and you will not notice until a reconciliation fails.
Validation at the request layer. Pair a money validation rule with a FormRequest so malformed input never reaches the cast. This is the cheapest place to reject three-decimal input for a two-decimal currency.
Allocation over division. For splitting amounts (installments, proportional discounts), use the package's allocation helper rather than divide() — division leaves remainders that must be distributed, and hand-rolled remainder logic is where penny-drift bugs live.
Testing. Freeze the currency/locale config in your test base class and assert on minor units in feature tests. Add one test per currency you actually support; the failure mode of a money package is almost always a locale/currency mismatch, not arithmetic.
composer.json. Do not let CI resolve ^4.0.0-alpha.de locale and a en locale differ in separators and symbol position. If a formatted value looks wrong, check the locale before you check the arithmetic.(float) anywhere upstream of the package reintroduces the bug the package exists to prevent.$casts plus a DB column type that matches what the cast writes.How can I help you explore Laravel packages today?