mirror of
https://github.com/archtechx/money.git
synced 2025-12-12 19:34:02 +00:00
Initial commit
This commit is contained in:
commit
8847454577
33 changed files with 2435 additions and 0 deletions
261
src/Money.php
Normal file
261
src/Money.php
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ArchTech\Money;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use JsonSerializable;
|
||||
use Livewire\Wireable;
|
||||
|
||||
final class Money implements JsonSerializable, Arrayable, Wireable
|
||||
{
|
||||
protected int $value;
|
||||
protected Currency $currency;
|
||||
|
||||
/** Create a new Money instance. */
|
||||
public function __construct(int $value, Currency|string $currency = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->currency = currency($currency);
|
||||
}
|
||||
|
||||
/** Create a new Money instance with the same currency. */
|
||||
protected function new(int $value): self
|
||||
{
|
||||
return new self($value, $this->currency);
|
||||
}
|
||||
|
||||
/** Create a Money instance from a decimal value. */
|
||||
public static function fromDecimal(float $decimal, Currency|string $currency = null): self
|
||||
{
|
||||
return new static(
|
||||
(int) round($decimal * 10 ** currency($currency)->mathDecimals()),
|
||||
currency($currency)
|
||||
);
|
||||
}
|
||||
|
||||
/** Add money (in base value). */
|
||||
public function add(int $value): self
|
||||
{
|
||||
return $this->new($this->value + $value);
|
||||
}
|
||||
|
||||
/** Add money (from another Money instance). */
|
||||
public function addMoney(self $money): self
|
||||
{
|
||||
return $this->add(
|
||||
$money->convertTo($this->currency)->value()
|
||||
);
|
||||
}
|
||||
|
||||
/** Subtract money (in base value). */
|
||||
public function subtract(int $value): self
|
||||
{
|
||||
return $this->new($this->value - $value);
|
||||
}
|
||||
|
||||
/** Subtract money (of another Money instance). */
|
||||
public function subtractMoney(self $money): self
|
||||
{
|
||||
return $this->subtract(
|
||||
$money->convertTo($this->currency)->value()
|
||||
);
|
||||
}
|
||||
|
||||
/** Multiply the money by a coefficient. */
|
||||
public function multiplyBy(float $coefficient): self
|
||||
{
|
||||
return $this->new(
|
||||
(int) round($this->value * $coefficient)
|
||||
);
|
||||
}
|
||||
|
||||
/** Multiply the money by a coefficient. */
|
||||
public function times(float $coefficient): self
|
||||
{
|
||||
return $this->multiplyBy($coefficient);
|
||||
}
|
||||
|
||||
/** Divide the money by a number. */
|
||||
public function divideBy(float $number): self
|
||||
{
|
||||
if ($number == 0) {
|
||||
$number = 1;
|
||||
}
|
||||
|
||||
return $this->new(
|
||||
(int) round($this->value() / $number)
|
||||
);
|
||||
}
|
||||
|
||||
/** Add a % fee to the money. */
|
||||
public function addFee(float $rate): self
|
||||
{
|
||||
return $this->multiplyBy(
|
||||
round(1 + $rate, $this->currency->mathDecimals())
|
||||
);
|
||||
}
|
||||
|
||||
/** Add a % tax to the money. */
|
||||
public function addTax(float $rate): self
|
||||
{
|
||||
return $this->addFee($rate);
|
||||
}
|
||||
|
||||
/** Subtract a % fee from the money. */
|
||||
public function subtractFee(float $rate): self
|
||||
{
|
||||
return $this->divideBy(
|
||||
round(1 + $rate, $this->currency->mathDecimals())
|
||||
);
|
||||
}
|
||||
|
||||
/** Subtract a % tax from the money. */
|
||||
public function subtractTax(float $rate): self
|
||||
{
|
||||
return $this->subtractFee($rate);
|
||||
}
|
||||
|
||||
/** Get the base value of the money in the used currency. */
|
||||
public function value(): int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/** Get the used currency. */
|
||||
public function currency(): Currency
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/** Get the decimal representation of the value. */
|
||||
public function decimal(): float
|
||||
{
|
||||
return $this->value / 10 ** $this->currency->mathDecimals();
|
||||
}
|
||||
|
||||
/** Format the value. */
|
||||
public function formatted(mixed ...$overrides): string
|
||||
{
|
||||
return PriceFormatter::format($this->decimal(), $this->currency, variadic_array($overrides));
|
||||
}
|
||||
|
||||
/** Format the raw (unrounded) value. */
|
||||
public function rawFormatted(mixed ...$overrides): string
|
||||
{
|
||||
return $this->formatted(array_merge(variadic_array($overrides), [
|
||||
'displayDecimals' => $this->currency->mathDecimals(),
|
||||
]));
|
||||
}
|
||||
|
||||
/** Get the string representation of the Money instance. */
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->formatted();
|
||||
}
|
||||
|
||||
/** Convert the instance to an array representation. */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'value' => $this->value,
|
||||
'currency' => $this->currency->code(),
|
||||
];
|
||||
}
|
||||
|
||||
/** Check if the value equals the value of another Money instance, adjusted for currency. */
|
||||
public function equals(self $money): bool
|
||||
{
|
||||
return $this->valueInDefaultCurrency() === $money->valueInDefaultCurrency();
|
||||
}
|
||||
|
||||
/** Check if the value and currency match another Money instance. */
|
||||
public function is(self $money): bool
|
||||
{
|
||||
return $this->currency()->code() === $money->currency()->code()
|
||||
&& $this->equals($money);
|
||||
}
|
||||
|
||||
/** Get the data used for JSON serializing this object. */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/** Convert the instance to JSON */
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/** Instantiate Money from JSON. */
|
||||
public static function fromJson(string|array $json): self
|
||||
{
|
||||
if (is_string($json)) {
|
||||
$json = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
return new static($json['value'], $json['currency']);
|
||||
}
|
||||
|
||||
/** Value in the default currency. */
|
||||
public function valueInDefaultCurrency(): int
|
||||
{
|
||||
$mathDecimalDifference = $this->currency->mathDecimals() - currencies()->getDefault()->mathDecimals();
|
||||
|
||||
return $this
|
||||
->divideBy($this->currency->rate())
|
||||
->divideBy(10 ** $mathDecimalDifference)
|
||||
->value();
|
||||
}
|
||||
|
||||
/** Convert the money to a different currency. */
|
||||
public function convertTo(Currency|string $currency): self
|
||||
{
|
||||
// We're converting from the current currency to the default currency, and then to the intended currency
|
||||
$newCurrency = currency($currency);
|
||||
$mathDecimalDifference = $newCurrency->mathDecimals() - currencies()->getDefault()->mathDecimals();
|
||||
|
||||
return new static(
|
||||
(int) round($this->valueInDefaultCurrency() * $newCurrency->rate() * 10 ** $mathDecimalDifference, 0),
|
||||
$currency
|
||||
);
|
||||
}
|
||||
|
||||
/** Convert the Money to the current currency. */
|
||||
public function toCurrent(): self
|
||||
{
|
||||
return $this->convertTo(currencies()->getCurrent());
|
||||
}
|
||||
|
||||
/** Convert the Money to the current currency. */
|
||||
public function toDefault(): self
|
||||
{
|
||||
return $this->convertTo(currencies()->getDefault());
|
||||
}
|
||||
|
||||
/** Round the Money to a custom precision. */
|
||||
public function rounded(int $precision = null): self
|
||||
{
|
||||
$precision ??= $this->currency->rounding();
|
||||
|
||||
return $this->new(((int) round($this->value, -$precision)));
|
||||
}
|
||||
|
||||
/** Get the money rounding (typically this is the difference between the actual value and the formatted value.) */
|
||||
public function rounding(): int
|
||||
{
|
||||
return $this->rounded()->value() - $this->value();
|
||||
}
|
||||
|
||||
public function toLivewire()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public static function fromLivewire($value)
|
||||
{
|
||||
return static::fromJson($value);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue