Skip to content

PHP Laravel Money Package 💰

License

Notifications You must be signed in to change notification settings

iaa-inc/laracash

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PHP Laravel Money Package

Logo

GitHub stars Total Downloads Latest Stable Version License

  • Laravel wrapper over MoneyPHP library
  • Provides a convenient way to work with the money column as a Value Object
  • Uses the Custom Casts Laravel 7.x feature

Features

  • Convenient work with the native MoneyPHP library and Laravel Eloquent ORM
  • Money columns casting
  • Currencies columns casting
  • Supported concepts
    • Money columns with default Currency (without a specific column)
    • Currency columns without Money
    • Many Money columns reference to one Currency column
    • Money to Currencies columns mapping

Requirements

  • PHP >= 7.2.5
  • Laravel v7.x
  • JSON PHP Extension (ext-json)

Suggest

  • BCMath (ext-bcmath) and GMP (ext-gmp) PHP Extensions for calculations with large integers
  • Intl PHP Extension (ext-intl) for formatting

Installation

Require package

composer require andriichuk/laracash

Publish vendor settings

php artisan vendor:publish --provider="Andriichuk\Laracash\ServiceProviders\LaracashServiceProvider" --tag="config"

Default settings

[
    'currency' => 'USD',
    'locale' => 'en_US',
]

Usage Concepts

Money columns with default Currency

<?php

namespace App;

use Andriichuk\Laracash\Casts\MoneyCast;
use Illuminate\Database\Eloquent\Model;
use Money\Money;

/**
 * Class OrderItem
 *
 * @property Money $price
 * @property Money $discount
 */
class OrderItem extends Model
{
    protected $fillable = ['name', 'price', 'discount'];

    protected $casts = [
        'price' => MoneyCast::class,
        'discount' => MoneyCast::class,
    ];
}
OrderItem::create([
    'name' => 'Order Item',
    'price' => makeMoney(1000),
    'discount' => makeMoney(15),
]);

Currency columns without Money

<?php

namespace App;

use Andriichuk\Laracash\Casts\CurrencyCast;
use Andriichuk\Laracash\Model\HasCurrency;
use Andriichuk\Laracash\Model\HasCurrencyInterface;
use Illuminate\Database\Eloquent\Model;
use Money\Currency;

/**
 * Class Rate
 *
 * @property Currency $currency
 */
class Rate extends Model implements HasCurrencyInterface
{
    use HasCurrency;

    protected $fillable = ['name', 'price', 'currency', 'native_currency'];

    protected $casts = [
        'currency' => CurrencyCast::class,
        'native_currency' => CurrencyCast::class,
    ];
}
Rate::create([
    'name' => 'Rate #1', 
    'price' => 99, 
    'currency' => new Currency('USD'), 
    'native_currency' => 'UAH',
]);

Many Money columns reference to one Currency column

use Andriichuk\Laracash\Casts\CurrencyCast;
use Andriichuk\Laracash\Casts\MoneyCast;
use Andriichuk\Laracash\Model\HasCurrency;
use Andriichuk\Laracash\Model\HasMoneyWithCurrency;
use Andriichuk\Laracash\Model\HasMoneyWithCurrencyInterface;
use Illuminate\Database\Eloquent\Model;
use Money\Currency;
use Money\Money;

/**
 * Class Transaction
 *
 * @property Money $payment_amount
 * @property Money $discount
 * @property Currency $currency
 */
class Transaction extends Model implements HasMoneyWithCurrencyInterface
{
    use HasMoneyWithCurrency;
    use HasCurrency;

    protected $fillable = ['name', 'payment_amount', 'discount', 'currency'];

    protected $casts = [
        'payment_amount' => MoneyCast::class,
        'discount' => MoneyCast::class,
        'currency' => CurrencyCast::class,
    ];

    public function getCurrencyColumnFor(string $field): string
    {
        return 'currency';
    }
}

If the currency is in a related model, just return an empty string ('') in getCurrencyColumnFor().

Money to Currencies columns mapping

use Andriichuk\Laracash\Casts\CurrencyCast;
use Andriichuk\Laracash\Casts\MoneyCast;
use Andriichuk\Laracash\Model\HasCurrency;use Andriichuk\Laracash\Model\HasMoneyWithCurrency;
use Andriichuk\Laracash\Model\HasMoneyWithCurrencyInterface;
use Illuminate\Database\Eloquent\Model;
use Money\Currency;
use Money\Money;

/**
 * Class Product
 *
 * @property Money $payment_amount
 * @property Money $discount
 * @property Currency $currency
 */
class Product extends Model implements HasMoneyWithCurrencyInterface 
{
    use HasMoneyWithCurrency;
    use HasCurrency;

    protected $fillable = ['name', 'price', 'currency', 'book_price', 'native_currency'];

    protected $casts = [
        'price' => MoneyCast::class,
        'currency' => CurrencyCast::class,

        'book_price' => MoneyCast::class,
        'native_currency' => CurrencyCast::class
    ];

    public function getCurrencyColumnFor(string $field): string
    {
        return [
            'price' => 'currency',
            'book_price' => 'native_currency',
        ][$field] ?? '';
    }
}
Product::create([
    'price' => \Money\Money::USD(1000),
    'book_price' => Money::UAH(25000),
]);

If you want to use magic accessors (*_as_currency, *_as_decimal) for money fields then you should add HasMoney trait to your Eloquent Model (accessors will be added automatically)

<?php

namespace App;

use Andriichuk\Laracash\Casts\MoneyCast;
use Andriichuk\Laracash\Model\HasMoney;
use Illuminate\Database\Eloquent\Model;
use Money\Money;

/**
 * Class Product
 *
 * @property Money $price
 * @property-read string $price_as_currency
 * @property-read string $price_as_decimal
 */
class Product extends Model
{
    use HasMoney;

    protected $fillable = ['name', 'price'];

    protected $casts = [
        'price' => MoneyCast::class,
    ];
}

Now you can call magic fields

use App\Product;

$product = Product::find(1);

$product->price_as_decimal; // "10.00"
$product->price_as_currency; // "$10.00"

$product->price = 5000;

$product->price_as_decimal; // "50.00"
$product->price_as_currency; // "$50.00"

Display money data in the form input field

Assign model

use App\Product;
use Illuminate\Support\Facades\Route;

Route::view('/', 'productForm', ['product' => Product::find(1)]);

Present money object as a decimal value

<input type="number" name="price" value="{{ formatMoneyAsDecimal($product->price) }}">

{{-- or with magic syntax --}}
<input type="number" name="price" value="{{ $product->price_as_decimal }}">

Get money from request

use App\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('products/{product}', function (Product $product, Request $request) {
    $product->price = parseMoneyDecimal($request->get('price')); // 55.99 => Money::USD(5599)
});

Serialization

Define model resource

use App\Product;
use Illuminate\Http\Resources\Json\JsonResource;

/**
 * Class ProductResource
 *
 * @mixin Product
 */
final class ProductResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'price_as_currency' => $this->price_as_currency, // or formatMoneyAsCurrency($this->price)
        ];
    }
}

Apply resource to the model

use App\Product;
use App\Http\Resources\ProductResource;

Route::get('products/{product}', function (Product $product) {
    return new ProductResource($product);
});

Output

{
  "data": {
    "id": 1,
    "name": "Product name",
    "price": {
      "amount": "1000",
      "currency": "USD"
    },
    "price_as_currency": "$10.00"
  }
}

Model Creation

Using scalar values (int|string)

use App\Product;

Product::create([
    'name' => 'The First Product',
    'price' => 100,
]);

Using Money\Money object:

use App\Product;
use Money\Money;

Product::create([
    'name' => 'The Second Product',
    'price' => Money::USD(100),
]);

Using facade:

use Andriichuk\Laracash\Facades\Laracash;
use App\Product;

Product::create([
    'name' => 'The Third Product',
    'price' => Laracash::factory()->make(100)
]);

Using helper function:

use App\Product;

Product::create([
    'name' => 'The Fourth Product',
    'price' => makeMoney(100)
]);

Bitcoin creation

use Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

// Using Facade
Laracash::factory()->makeBitcoin(1000000000);

// Using helper
makeBitcoin(1000000000);

// Using native library factory call
Money::XBT(1000000000);

Retrieving data

use App\Product;

$product = Product::find(1);

dd($product->price);
Money\Money {#403 â–¼
  -amount: "1000"
  -currency: Money\Currency {#404 â–¼
    -code: "USD"
  }
}

Operations

Check original library docs for more information

use Andriichuk\Laracash\Facades\Laracash;
use App\Product;

$product = Product::find(1);

$product->price = $product->price->add(Laracash::factory()->make(2000));

$product->save();

API

Creation

Money instance creation using Laracash facade.

*If you do not pass the second argument currency, then it will take from config file

use \Andriichuk\Laracash\Facades\Laracash;

Laracash::factory()->make(1000);
Laracash::factory()->make('10000000000000');

Specify currency

use \Andriichuk\Laracash\Facades\Laracash;
use \Money\Currency;

Laracash::factory()->make(1000, 'USD');
Laracash::factory()->make(1000, new Currency('USD'));

// Or use native method Money::USD(100)
Money\Money {#403 â–¼
  -amount: "1000"
  -currency: Money\Currency {#404 â–¼
    -code: "USD"
  }
}

Formatting

Money instance formatting. More info

Decimal

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

Laracash::formatter()->formatAsDecimal(Money::USD(100)); // "1.00"
formatMoneyAsDecimal(Money::USD(100)); // "1.00"

Using Intl extension

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

Laracash::formatter()->formatAsIntlDecimal(Money::USD(100)); // "1"
Laracash::formatter()->formatAsIntlDecimal(Money::USD(100), 'uk_UA'); // "1"

Intl currency

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

Laracash::formatter()->formatAsIntlCurrency(Money::USD(100)); // "$1.00"
Laracash::formatter()->formatAsIntlCurrency(Money::USD(100), 'uk_UA'); // "1,00 USD"
formatMoneyAsCurrency(Money::USD(100)); // "$1.00"
formatMoneyAsCurrency(Money::XBT(1000000000)); // "Ƀ10.00"

Specify custom Intl formatting style

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;
use NumberFormatter;

Laracash::formatter()->formatIntlWithStyle(Money::USD(100), 'en_US', NumberFormatter::SPELLOUT); // "one"
Laracash::formatter()->formatIntlWithStyle(Money::USD(100), 'en_US', NumberFormatter::SCIENTIFIC); // "1E0"

Bitcoin

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

Laracash::formatter()->formatBitcoin(Money::XBT(1000000000)); // "Ƀ10.00"

// or use helper function
formatMoneyAsCurrency(makeBitcoin(1000000000)); // "Ƀ10.00"

Bitcoin as decimal

use \Andriichuk\Laracash\Facades\Laracash;
use Money\Money;

Laracash::formatter()->formatBitcoinAsDecimal(Money::XBT(1000000000)); // "10.00000000"

// or use helper function
formatMoneyAsDecimal(makeBitcoin(1000000000)); // "10.00000000"

Parsing

More info

Intl parse money string with currency

use Andriichuk\Laracash\Facades\Laracash;

Laracash::parser()->parseIntlCurrency('$1.00');

Result

Money\Money {#369 â–¼
    -amount: "100"
    -currency: Money\Currency {#368 â–¼
      -code: "USD"
    }
  }

Parse decimal

use Andriichuk\Laracash\Facades\Laracash;

Laracash::parser()->parseDecimal('1.30');
parseMoneyDecimal('1.30');

Result

Money\Money {#368 â–¼
  -amount: "130"
  -currency: Money\Currency {#367 â–¼
    -code: "USD"
  }
}

Tests

Run features and unit tests:

./vendor/bin/phpunit

Credits

License

Laracash is an open-sourced software licensed under the MIT license.

About

PHP Laravel Money Package 💰

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • PHP 100.0%