Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Money Laravel Package

elegantly/laravel-money

Seamlessly integrate Brick/Money with Laravel using expressive value objects. MoneyCast automatically casts Eloquent attributes to Money, MoneyParser safely converts strings, integers, and floats, and the ValidMoney rule validates input with min/max boundaries and nullable support.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and decide storage before you write a single cast.

composer require elegantly/laravel-money
php artisan vendor:publish --tag="money-config"

This gives you config/money.php with a single default_currency key. Set it to your primary currency and leave it alone unless you have a reason — it is a global fallback, not a per-model setting.

The package is a thin adapter over brick/money plus Laravel glue. There are exactly three things to learn, in this order:

  1. Elegantly\Money\MoneyCast — Eloquent attribute ⇄ Brick\Money\Money
  2. Elegantly\Money\MoneyParser — mixed input → Money (or null)
  3. Elegantly\Money\Rules\ValidMoney — validation rule for requests/Livewire

Because the package is small (~19 stars), there is effectively no community knowledge base. Read the source first — src/MoneyCast.php, src/MoneyParser.php, src/Rules/ValidMoney.php are short enough to read in ten minutes and will answer most questions the README does not.

The first use case to wire up: an invoice.

Schema::create('invoices', function (Blueprint $table) {
    $table->id();
    $table->bigInteger('amount');   // minor units: 1000 = $10.00
    $table->string('currency', 3);  // ISO 4217
    $table->timestamps();
});
use Brick\Money\Money;
use Elegantly\Money\MoneyCast;

/**
 * @property Money $amount
 * @property string $currency
 */
class Invoice extends Model
{
    protected function casts(): array
    {
        return [
            'amount' => MoneyCast::of('currency'),
        ];
    }
}

Do this on one model in one branch before touching anything else. The schema decision (bigInteger minor units + currency code) is the one thing that is expensive to reverse later; the cast itself is trivial and replaceable.

Sanity-check the round trip in tinker immediately:

$i = Invoice::create(['amount' => 1000, 'currency' => 'USD']);
$i->refresh()->amount;              // Money USD 10.00
$i->amount = Money::of('12.34', 'USD');
$i->save();
$i->getRawOriginal('amount');       // 1234

If getRawOriginal isn't the minor-unit integer you expect, stop and fix it before building on top of it.

Implementation Patterns

Fixed currency vs. column currency. Use MoneyCast::of('EUR') for constants (a plan price, a fee table) and MoneyCast::of('currency') when the row carries its own currency. Don't mix both on the same model — a fixed-currency cast that ignores the currency column will silently produce wrong objects the day someone inserts a EUR row.

Build a formatter layer yourself. This is the biggest gap. The package gives you value objects, not presentation. Add an accessor or an API Resource:

// Display
$invoice->amount->formatTo('en_US');        // "$10.00" — from brick/money

// API (explicit and lossless)
return [
    'amount'   => $invoice->amount->getMinorAmount()->toInt(),
    'currency' => $invoice->amount->getCurrency()->getCurrencyCode(),
    'display'  => $invoice->amount->formatTo('en_US'),
];

Prefer the minor-integer + currency-code shape over any float for anything crossing a wire. Consuming clients can reconstruct exactly; floats cannot.

Parsing untrusted input. MoneyParser is the right entry point for imports, CSV uploads, webhook payloads, and Livewire properties, because it normalises the messy cases in one call:

MoneyParser::parse(null, 'EUR');      // null
MoneyParser::parse('', 'EUR');        // null
MoneyParser::parse(100.10, 'EUR');    // EUR 100.10
MoneyParser::parse('100.10', 'EUR');  // EUR 100.10

The float case is the interesting one: 100.10 as a float is 100.09999999... in binary. The parser rounds to the currency's scale, which is exactly what you want at a boundary — but never do math after turning a float into Money and expect the original intent to survive. Parse at the edge, then only ever operate on Money.

Validation. ValidMoney accepts nullable, min, max, and (since v2.3.1) decimal bounds:

'price' => [new ValidMoney(nullable: false, min: 0, max: 99.99)],

Use it in Form Requests and in Livewire via #[Validate(...)]. Treat min/max as major units (dollars, not cents) — confirm this in a test for your currency, because getting it backwards is a silent off-by-100.

Write two helpers in your own codebase and use them everywhere. Something like Money::ofMinor($cents, $currency) for DB-shaped values and MoneyParser::parse($input, $currency) for user-shaped values. Naming the two conversion directions makes code review catch the errors.

Guard currency equality at boundaries. Brick/Money throws on arithmetic between different currencies. That's correct behaviour, but it surfaces as an exception deep in a service class. Validate currency as an explicit, allowed ISO code in the request (Rule::in(['EUR','USD'])) so the failure happens at the form, not at the totals calculation.

Testing. Assert on minor units in the DB and on Money equality in code:

$this->assertDatabaseHas('invoices', ['amount' => 1234, 'currency' => 'USD']);
$this->assertTrue($invoice->amount->isEqualTo(Money::of('12.34', 'USD')));

Factories should set integers ('amount' => 1000), never 10.00 — a float in a factory is the fastest way to smuggle a bug into every test that follows.

Gotchas and Tips

Serialization changed in v2.0.0 and it matters. MoneyCast's serialize() now returns a float (100.0), not the string "USD 100.00". That flows into attributesToArray(), toJson(), and every assertJson/assertExactJson test. Floats in a JSON money payload are a precision hazard for consumers. Override deliberately: define API Resources (as above) and don't rely on raw ->toJson() of a model with a money cast.

->of() is overloaded. The same argument is a currency code or a column name. Nothing validates it. Misspell 'currency' and you get a confusing currency-parse error at hydration time rather than at boot. Consider a quick test that instantiates and saves each money-cast model.

Bulk and raw writes bypass casts. Invoice::query()->update(['amount' => 1234]) writes raw; ->increment() writes raw. Make sure humans writing these paths know they're in minor units, and that any Money object must be converted with getMinorAmount()->toInt() first.

Aggregates are raw and currency-blind. ->sum('amount') returns minor units, and summing across mixed currencies produces a meaningless number. If you report totals, group by currency in SQL, or load Money objects and reduce with plus() so brick enforces the currency check.

Zero- and three-decimal currencies. JPY has 0 decimals, KWD/BHD have 3. Any hand-written * 100 / / 100 will be wrong for them. Always let brick do the scale conversion — Money::of() and getMinorAmount() are currency-aware; your arithmetic isn't.

Rounding is opt-in, and division throws. Splitting an amount across line items or installments requires an explicit rounding mode; silent truncation is not brick's default. MoneyParser gained a configurable rounding mode in v2.3.2 — check the config before assuming a default, and write the "split €10.00 three ways" test.

Locale-aware string parsing is not guaranteed. "1.000,50" is not handled the way you might hope. If you accept user input in multiple locales, normalise the separator before it reaches MoneyParser, and make ValidMoney the gate that rejects anything unparseable.

Upgrading from the old name. Before v2.0.0 this was finller/laravel-money with the Finller\Money namespace. Any tutorial or StackOverflow answer you find is probably for the old namespace — a search-and-replace on Finller\Money → Elegantly\Money covers it, but re-check serialization behaviour in tests because that also changed in v2.0.0.

Pin and isolate. One maintainer, 19 stars, fast-moving minor versions (v2.3.1 changed decimal min/max, v2.3.2 added rounding config). Pin ^2.3 in composer.json. The real dependency is brick/money, which is stable and widely used; this package is a ~200-line convenience layer. Wrapping MoneyCast/MoneyParser calls behind your own App\Support\Money façade costs an hour and means a future swap is a one-file change instead of a grep across the codebase.

Debugging tip. Casts are lazy — the currency column is read when the amount attribute is accessed. If you see a null currency or a wrong-currency object, dump $model->getRawOriginal() for both columns before suspecting the package; nine times out of ten the row itself is wrong.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc