PHP implementation of Fowler's Money pattern.

Overview

Money

Latest Version GitHub Workflow Status Total Downloads

Email

Money PHP

PHP library to make working with money safer, easier, and fun!

"If I had a dime for every time I've seen someone use FLOAT to store currency, I'd have $999.997634" -- Bill Karwin

In short: You shouldn't represent monetary values by a float. Wherever you need to represent money, use this Money value object. Since version 3.0 this library uses strings internally in order to support unlimited integers.

<?php

use Money\Money;

$fiveEur = Money::EUR(500);
$tenEur = $fiveEur->add($fiveEur);

list($part1, $part2, $part3) = $tenEur->allocate([1, 1, 1]);
assert($part1->equals(Money::EUR(334)));
assert($part2->equals(Money::EUR(333)));
assert($part3->equals(Money::EUR(333)));

The documentation is available at http://moneyphp.org

Requirements

PHP 5.6+. Other than that, this library has no external requirements. MoneyPHP will not provide any support to PHP versions that are not supported by the language itself. There might be additional dependencies for specific feature, e.g. the Swap exchange implementation, check the documentation for more information.

Install

Via Composer

$ composer require moneyphp/money

Features

  • JSON Serialization
  • Big integer support utilizing different, transparent calculation logic upon availability (bcmath, gmp, plain php)
  • Money formatting (including intl formatter)
  • Currency repositories (ISO currencies included)
  • Money exchange (including Swap implementation)

Documentation

Please see the official documentation.

Testing

We try to follow BDD and TDD, as such we use both phpspec and phpunit to test this library.

$ composer test

Running the tests in Docker

Money requires a set of dependencies, so you might want to run it in Docker.

First, build the image locally:

$ docker build -t moneyphp .

Then run the tests:

$ docker run --rm -it -v $PWD:/app -w /app moneyphp vendor/bin/phpunit --exclude-group segmentation

Contributing

We would love to see you helping us to make this library better and better. Please keep in mind we do not use suffixes and prefixes in class names, so not CurrenciesInterface, but Currencies. Other than that, Style CI will help you using the same code style as we are using. Please provide tests when creating a PR and clear descriptions of bugs when filing issues.

Security

If you discover any security related issues, please contact us at [email protected].

License

The MIT License (MIT). Please see License File for more information.

Acknowledgements

This library is heavily inspired by Martin Fowler's Money pattern. A special remark goes to Mathias Verraes, without his contributions, in code and via his blog, this library would not be where it stands now.

Comments
  • deal with integer limits

    deal with integer limits

    Solves #111.

    Probably has quite some impact on the library. Review carefully. Rather see some more comments on the design decisions than on some PSR-2 compliance. The latter will be solved - if there is any compliance oversights - when there is agreement on the design.

    Hard aspect of this PR was rounding in GMP and BC Math libraries. Had a look at some other libraries for this to implement.

    opened by frederikbosch 48
  • Doctrine integration

    Doctrine integration

    There are two possible options in my mind.

    Option 1

    Same as in #327.

    Use the money object itself as an embeddable.

    Money\Money:
        type: embeddable
        fields:
            amount:
                type: string
        embedded:
            currency:
                class: Money\Currency
    
    Money\Currency:
        type: embeddable
        fields:
            code:
                type: string
                length: 3
    

    Pros:

    • Simple
    • Easy to integrate

    Cons:

    • Not really flexible

    Option 2

    Use a separate embeddable.

    <?php
    
    namespace Money\Doctrine;
    
    final class Money
    {
        private $amount;
        private $currencyCode;
    
        /**
         * @return \Money\Money
         */
        public function getValue()
        {
            return new \Money\Money($this->amount, new \Money\Currency($this->currencyCode));
        }
    
        /**
         * @param \Money\Money $money
         */
        public function setValue(\Money\Money $money)
        {
            $this->amount = $money->getAmount();
            $this->currencyCode = $money->getCurrency()->getCode();
        }
    }
    

    And the mapping:

    <doctrine-mapping>
        <embeddable name="Money\Doctrine\Money">
            <field name="amount" type="string" />
            <field name="currencyCode" type="string" />
        </embeddable>
    </doctrine-mapping>
    

    Then in the entity:

    class ObjectWithPrice
    {
        /**
         * @var \Money\Doctrine\Money
         */
        private $price;
    
        public function getPrice()
        {
            return $this->price->getValue();
        }
    
        public function setPrice(\Money\Money $price)
        {
            $this->price->setValue($price);
        }
    }
    

    Since VO equality is not checked based on identity, but value this solution is valid. The advantage is the flexibility gained (less doctrine embed magic), but I am not sure it worth the extra effort.

    @pamil I would love to hear your opinion as well.

    opened by sagikazarmark 37
  • Extend currencies with subunit

    Extend currencies with subunit

    Format currencies with subunits by using a subunit provider. Built-in providers are Constant and ISO. A follow-up PR might refactor ISOCurrencies and ISOProvider to use a single ISORepository or ISOFactory.

    Questions:

    1. Is Provider the correct name in this context?
    2. What do you feel about the architectural decisions?
    3. What about namespaces? Is everything in the correct place?

    Please give me feedback on those important issues. Afterwards we can discuss secondary style issues. /cc @sagikazarmark

    Fixes: #221 #232 #233 #223.

    opened by frederikbosch 34
  • Quick money (butchering this library for the glory of satan, and CPU cycles)

    Quick money (butchering this library for the glory of satan, and CPU cycles)

    This is a draft: still playing around with it.

    Ultimately, I'm mostly toying around with the library, removing bits that can be removed without substantial BC Breaks (yes, there will be BC breaks) and inlining code that can be inlined (yes, the code will be inlined).

    I suggest avoiding an immediate code review until this is ready and I have stable performance results (most of the benchmarks were performed while my PC was under high load, so the values in the commit messages are not reliable).

    In addition to all this, I still need to check if the GMP calculator can be used instead of BcMath, in order of priority, since GMP seems to generally be a bit quicker.

    Notable improvements:

    • [x] full type coverage (100% type coverage by vimeo/psalm)
    • [x] 80%+ mutation test coverage (roave/infection-static-analysis-plugin)
    • [x] removal of a lot of internal API
    • [x] roughly 50% performance improvements in happy-path usage of Money, Number and Currency
    • [x] removal of Calculator discovery by-default (you want a custom calculator? configure it manually!)

    Benchmark results

    ❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=ref_original_5 --retry-threshold=3 --iterations=15 --revs=1000 --warmup=2
    PHPBench @git_tag@ running benchmarks...
    with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
    with PHP version 8.0.3, xdebug ❌, opcache ✔
    
    \Benchmark\Money\NumberInstantiationBench
    
        benchConstructorWithZeroIntegerAmount...R1 I2 - [Mo0.210μs vs Mo0.163μs] +28.42% (±1.29%)
        benchConstructorWithPositiveIntegerAmou.R1 I8 - [Mo0.308μs vs Mo0.626μs] -50.87% (±0.82%)
        benchConstructorWithNegativeIntegerAmou.R2 I2 - [Mo0.305μs vs Mo0.683μs] -55.40% (±0.90%)
        benchConstructorWithZeroAndFractionalAm.R1 I7 - [Mo0.316μs vs Mo0.576μs] -45.04% (±0.78%)
        benchConstructorWithFractionalAmount....R1 I14 - [Mo0.436μs vs Mo1.144μs] -61.85% (±0.80%)
        benchConstructorWithNegativeFractionalA.R1 I7 - [Mo0.435μs vs Mo1.200μs] -63.75% (±1.15%)
        benchFromStringWithZeroIntegerAmount....R1 I3 - [Mo0.319μs vs Mo0.208μs] +53.54% (±0.99%)
        benchFromStringWithPositiveIntegerAmoun.R1 I4 - [Mo0.426μs vs Mo0.676μs] -37.02% (±1.05%)
        benchFromStringWithNegativeIntegerAmoun.R1 I14 - [Mo0.423μs vs Mo0.731μs] -42.22% (±0.91%)
        benchFromStringWithZeroAndFractionalAmo.R2 I8 - [Mo0.476μs vs Mo0.689μs] -30.97% (±1.00%)
        benchFromStringWithFractionalAmount.....R2 I14 - [Mo0.609μs vs Mo1.257μs] -51.53% (±0.79%)
        benchFromStringWithNegativeFractionalAm.R1 I13 - [Mo0.612μs vs Mo1.304μs] -53.11% (±1.06%)
    
    \Benchmark\Money\MoneyOperationBench
    
        benchAdd................................R1 I14 - [Mo1.244μs vs Mo1.338μs] -7.07% (±1.45%)
        benchSubtract...........................R1 I14 - [Mo1.254μs vs Mo1.315μs] -4.65% (±1.36%)
        benchMultiply...........................R3 I10 - [Mo1.297μs vs Mo2.346μs] -44.70% (±0.96%)
        benchDivide.............................R3 I14 - [Mo2.105μs vs Mo3.074μs] -31.52% (±1.03%)
        benchSum................................R1 I2 - [Mo2.231μs vs Mo3.630μs] -38.56% (±0.89%)
        benchMin................................R1 I14 - [Mo1.195μs vs Mo1.317μs] -9.22% (±1.13%)
        benchMax................................R1 I2 - [Mo1.191μs vs Mo1.307μs] -8.83% (±0.59%)
        benchAvg................................R2 I12 - [Mo1.190μs vs Mo1.309μs] -9.12% (±1.10%)
        benchRatioOf............................R2 I14 - [Mo0.783μs vs Mo1.232μs] -36.48% (±0.69%)
        benchMod................................R1 I4 - [Mo0.761μs vs Mo0.781μs] -2.60% (±1.24%)
        benchIsSameCurrency.....................R1 I14 - [Mo0.081μs vs Mo0.107μs] -24.24% (±0.96%)
        benchIsZero.............................R1 I1 - [Mo0.242μs vs Mo0.226μs] +7.02% (±0.96%)
        benchAbsolute...........................R1 I14 - [Mo0.345μs vs Mo0.307μs] +12.60% (±0.99%)
        benchNegative...........................R1 I14 - [Mo1.396μs vs Mo1.595μs] -12.48% (±1.01%)
        benchIsPositive.........................R1 I8 - [Mo0.236μs vs Mo0.219μs] +7.94% (±0.80%)
        benchCompare............................R1 I14 - [Mo0.320μs vs Mo0.361μs] -11.35% (±0.84%)
        benchLessThan...........................R1 I0 - [Mo0.351μs vs Mo0.394μs] -10.89% (±0.46%)
        benchLessThanOrEqual....................R1 I14 - [Mo0.354μs vs Mo0.386μs] -8.06% (±0.73%)
        benchEquals.............................R1 I14 - [Mo0.391μs vs Mo0.141μs] +177.89% (±0.96%)
        benchGreaterThan........................R1 I2 - [Mo0.352μs vs Mo0.386μs] -8.93% (±0.68%)
        benchGreaterThanOrEqual.................R1 I8 - [Mo0.352μs vs Mo0.387μs] -9.02% (±0.63%)
    
    \Benchmark\Money\MoneyInstantiationBench
    
        benchConstructorWithZeroIntegerAmount...R2 I14 - [Mo0.149μs vs Mo0.175μs] -15.11% (±0.73%)
        benchConstructorWithPositiveIntegerAmou.R1 I0 - [Mo0.174μs vs Mo0.245μs] -29.24% (±1.32%)
        benchConstructorWithNegativeIntegerAmou.R1 I14 - [Mo0.175μs vs Mo0.248μs] -29.54% (±0.77%)
        benchConstructorWithZeroStringAmount....R1 I4 - [Mo0.183μs vs Mo0.162μs] +13.28% (±0.59%)
        benchConstructorWithPositiveStringAmoun.R1 I3 - [Mo0.205μs vs Mo0.180μs] +13.46% (±0.54%)
        benchConstructorWithNegativeStringAmoun.R1 I8 - [Mo0.209μs vs Mo0.185μs] +12.83% (±0.74%)
    
    Subjects: 39, Assertions: 0, Failures: 0, Errors: 0
    

    CI check status:

    • [x] ~~Composer Normalize (pull_request) - Failing after 19s - somehow didn't check my updated composer.json?~~ :thinking:
    • [ ] PrettyCI — Code formatting - no longer relevant - can be disabled?
    • [ ] Build (5.6) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] Build (7.0) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] Build (7.1) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] Build (7.2) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] Build (7.3) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] Build (7.4) Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] PHP-CS-Fixer Expected - no longer expected - need adjustment of branch protection rules pre-merge
    • [ ] PHPStan Expected - no longer expected - need adjustment of branch protection rules pre-merge

    Fixes

    • [x] fixes #633 (PHP 8 compat is now declared with a stricter range)
    • [x] fixes #619 (PHP 8 compat)
    • [x] fixes #615 (no longer allowing null amount)
    • [x] fixes #583 (interface of taggable cache in 1.0.0)
    • [x] fixes #575 ("5000\n" is NOT a numeric-string, so that value is now considered illegal at type level)
    • [x] fixes #474 (interface is now much stricter: json de-serialization is completely up to the end-user)
    • [x] fixes #465 (Calculator and Number are now internal)
    • [x] fixes #258 (negative ratio in Money#allocate() is unsupported)
    • [x] fixes #7 (internals of Money now use numeric-string and BcMathCalculator by default)
    opened by Ocramius 33
  • Money\Money is final

    Money\Money is final

    Why is the Money Class final and not implements a interface?

    • I can't Mock the Class in php unit.
    • I cant't decouple your lib from my Project via a Proxy Class -> no extend possible.

    may i miss understand something

    kind/question 
    opened by renepenner 30
  • Add a class in order for the developer to instantiate his own Currencies.

    Add a class in order for the developer to instantiate his own Currencies.

    Added a class SimpleCurrency in order for the developer to be able to add his own currencies.

    Edit:

    I don't mind if you believe that this does not belong in this package but I think that there should be a way for a developer to instantiate his own currencies. (He might need his own currencies for a Game App).

    kind/enhancement area/currencies 
    opened by gmponos 25
  • Money sum

    Money sum

    Hello everyone

    I want to make a sum for the amount of more than 2 Money objects. I thought I can make this using the add function, until I read the documentation and I've seen that add function returns a string not a Money object.

    Do you have any idea how I could make a sum for more than 2 Money objects?

    kind/enhancement area/money 
    opened by melokki 25
  • add 'unitsToString' function to Money\Money

    add 'unitsToString' function to Money\Money

    For legacy systems I need to convert to/from floats. To convert I do the following:

    use Money\Money;
    use Money\Currency;
    $float = 123.34;
    $money = new Money(Money::stringToUnits((string)$float), new Currency('USD'));
    var_dump($money);
    /*
    object(Money\Money)#2 (2) {
      ["amount":"Money\Money":private]=>
      int(12334)
      ["currency":"Money\Money":private]=>
      object(Money\Currency)#3 (1) {
        ["name":"Money\Currency":private]=>
        string(3) "USD"
      }
    }
    */
    
    $float = $money->getAmount() / 100;
    var_dump($float);
    /*
    float(123.34)
    */
    

    For example money_format() expects the parameter to be a float: http://php.net/manual/en/function.money-format.php

    Shouldn't it make sense to have a unitsToString() in Money\Money? Maybe even unitsToFloat() and floatToUnits() .

    I can create a pull-request for this if you would consider including this change.

    opened by johanwilfer 22
  • Add extra optional argument subunits to IntlMoneyFormatter.

    Add extra optional argument subunits to IntlMoneyFormatter.

    With the extra argument the user of formatter can now also control the number of subunits for a currency. This is fundamentally different than the amount fraction digits to be displayed (presentation vs business rule). It is optional because in the majority of the cases the subunits will equal the number of fraction digits.

    opened by frederikbosch 21
  • Converter is also final

    Converter is also final

    There are many issues where discussed why Money class is final, it's value object and so on. But why is Converter is final? It also doesn't implement any interface, so no way to mock it in tests. Converter is not value object, right?

    opened by ossinkine 20
  • Add IndirectExchange

    Add IndirectExchange

    Adds a decorator for Exchange that will query the underlying exchange to find a shortest chain of conversions to go from the base currency to the counter currency using breadth-first search.

    opened by mcordingley 20
  • Why is divisor argument in mod method of type Money

    Why is divisor argument in mod method of type Money

    Hey, forgive me if this is a dummy question. I might be missing something totally obvious.

    But I was asking myself, why the argument for the mod method is of type Money. Shouldn't the$divisor argument here be the same as for the divide method?

    Thanks!

    opened by helhum 5
  • introduce Teller class

    introduce Teller class

    This PR introduces a Teller class, nominally to help ease legacy codebases away from float math over to using Money object.

    It differs from the tentative offering in #629 in several ways, most notably that it is PHP 5.6 compatible, and reproduces a larger number of Money methods, using their same names.

    I have attempted to match the existing project conventions; please let me know if changes of any kind are needed.

    Tests and docs are included.

    fixes #629

    opened by pmjones 1
  • Offer a

    Offer a "Teller" object

    Hi -- in my legacy refactoring work, I have found it useful to introduce a Teller (as in "bank teller") object to ease the transition from float math to Money objects. I wanted to give this project the chance to review, then accept or reject it, before I consider releasing it as a library of its own. The Teller class is pasted below; if you find it suitable for inclusion, let me know and I'll send a PR proper with docs and tests for it. Thanks, hope you are all doing well!

    <?php
    /**
     * Legacy codebases often use float math for monetary calculations, which leads
     * to problems with fractions-of-pennies in monetary values. The proper
     * solution is to introduce a Money object, and use Money objects in place of
     * float math. However, doing so can be quite an onerous task, especially when
     * the float values need to be moved to and from database storage; intercepting
     * and coercing the float values (often represented by strings) can be very
     * difficult and time-consuming.
     *
     * To help ease the transition from float math to Money objects, use the Teller
     * class to replace float math for monetary calculations in place:
     *
     * ```php
     * // before
     * $price = 234.56;
     * $discount = 0.05;
     * $discountAmount = $price * $discount; // 11.728
     *
     * // after
     * $teller = Teller::new('USD');
     * $discountAmount = $teller->multiply($amount, $discount); // '11.73'
     * ```
     *
     * The main drawback is that you cannot use two currencies for calculations;
     * you can use only one.
     */
    
    namespace Pmjones;
    
    use InvalidArgumentException;
    use Money\Currencies\ISOCurrencies;
    use Money\Currency;
    use Money\Formatter\DecimalMoneyFormatter;
    use Money\Money;
    use Money\MoneyFormatter;
    use Money\MoneyParser;
    use Money\Parser\DecimalMoneyParser;
    
    class Teller
    {
        /**
         * Static convenience factory.
         */
        public static function new(
            string $currency,
            int $roundingMode = Money::ROUND_HALF_UP
        ) {
            $currency = new Currency($currency);
            $currencies = new ISOCurrencies();
            $parser = new DecimalMoneyParser($currencies);
            $formatter = new DecimalMoneyFormatter($currencies);
    
            return new static($currency, $parser, $formatter, $roundingMode);
        }
    
        /** @var ISOCurrencies */
        protected $currencies;
    
        /** @var Currency */
        protected $currency;
    
        /** @var MoneyFormatter */
        protected $formatter;
    
        /** @var MoneyParser */
        protected $parser;
    
        /** @var int Rounding mode for multiply/divide */
        protected $roundingMode;
    
        /**
         * Constructor.
         */
        public function __construct(
            Currency $currency,
            MoneyParser $parser,
            MoneyFormatter $formatter,
            int $roundingMode = Money::ROUND_HALF_UP
        ) {
            $this->currency = $currency;
            $this->parser = $parser;
            $this->formatter = $formatter;
            $this->roundingMode = $roundingMode;
        }
    
        /**
         * Returns a "zero" decimal monetary value.
         */
        public function zero() : string
        {
            return '0.00';
        }
    
        /**
         * Returns an integer less than, equal to, or greater than zero if a decimal
         * monetary value is respectively less than, equal to, or greater than
         * another.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function compare($value, $other) : int
        {
            $value = $this->convertToMoney($value);
            $other = $this->convertToMoney($other);
    
            return $value->compare($other);
        }
    
        /**
         * Are two decimal monetary values equal to each other?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function equals($value, $other) : bool
        {
            return $this->compare($value, $other) === 0;
        }
    
        /**
         * Are two decimal monetary values not equal to each other?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function notEquals($value, $other) : bool
        {
            return $this->compare($value, $other) !== 0;
        }
    
        /**
         * Is one decimal monetary value less than another?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function lessThan($value, $other) : bool
        {
            return $this->compare($value, $other) < 0;
        }
    
        /**
         * Is one decimal monetary value less than or equal to another?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function lessThanOrEquals($value, $other) : bool
        {
            $compare = $this->compare($value, $other);
    
            return $compare <= 0;
        }
    
        /**
         * Is one decimal monetary value greater than another?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function greaterThan($value, $other) : bool
        {
            return $this->compare($value, $other) > 0;
        }
    
        /**
         * Is one decimal monetary value greater than or equal to another?
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value.
         */
        public function greaterThanOrEquals($value, $other) : bool
        {
            $compare = $this->compare($value, $other);
    
            return $compare >= 0;
        }
    
        /**
         * Is the decimal monetary value equal to zero?
         *
         * @param mixed $value The decimal monetary value.
         */
        public function isZero($value) : bool
        {
            return $this->equals($value, $this->zero());
        }
    
        /**
         * Is the decimal monetary value not equal to zero?
         *
         * @param mixed $value The decimal monetary value.
         */
        public function isNotZero($value) : bool
        {
            return ! $this->isZero($value);
        }
    
        /**
         * Is the decimal monetary value greater than zero?
         *
         * @param mixed $value The decimal monetary value.
         */
        public function isGreaterThanZero($value) : bool
        {
            return $this->greaterThan($value, $this->zero());
        }
    
        /**
         * Is the decimal monetary value less than zero?
         *
         * @param mixed $value The decimal monetary value.
         */
        public function isLessThanZero($value) : bool
        {
            return $this->lessThan($value, $this->zero());
        }
    
        /**
         * Adds a series of decimal monetary values to each other in sequence.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value; this param exists
         * to make sure at least one other value is being added.
         *
         * @param mixed[] $others Subsequent other decimal monetary values.
         *
         * @return string The calculated decimal monetary value.
         */
        public function add($value, $other, ...$others) : string
        {
            $value = $this->convertToMoney($value);
            $value = $value->add($this->convertToMoney($other));
    
            foreach ($others as $other) {
                $value = $value->add($this->convertToMoney($other));
            }
    
            return $this->convertToString($value);
        }
    
        /**
         * Subtracts a series of decimal monetary values from each other
         * in sequence.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param mixed $other Another decimal monetary value; this param exists to
         * make sure at least one value *is* being subtracted.
         *
         * @param mixed[] $others Subsequent decimal monetary values.
         *
         * @return string The calculated decimal monetary value.
         */
        public function subtract($value, $other, ...$others) : string
        {
            $value = $this->convertToMoney($value);
            $value = $value->subtract($this->convertToMoney($other));
    
            foreach ($others as $other) {
                $value = $value->subtract($this->convertToMoney($other));
            }
    
            return $this->convertToString($value);
        }
    
        /**
         * Multiplies the decimal monetary value by -1. Note that this will convert
         * negative values to positive ones.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @return string The calculated decimal monetary value.
         */
        public function negative($value) : string
        {
            return $this->multiply($value, -1);
        }
    
        /**
         * Multiplies a decimal monetary value by a factor.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param float|int|string $multiplier The multiplier.
         *
         * @return string The calculated decimal monetary value.
         */
        public function multiply($value, $multiplier) : string
        {
            $money = $this->convertToMoney($value)->multiply($multiplier, $this->roundingMode);
    
            return $this->convertToString($money);
        }
    
        /**
         * Divides a decimal monetary value by a factor.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @param float|int|string $divisor The divisor.
         *
         * @return string The calculated decimal monetary value.
         */
        public function divide($value, $divisor ) : string
        {
            $money = $this->convertToMoney($value)->divide($divisor, $this->roundingMode);
    
            return $this->convertToString($money);
        }
    
        /**
         * Returns an absolute decimal monetary value.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @return string The calculated decimal monetary value.
         */
        public function abs($value) : string
        {
            $abs = abs($this->convertToString($value));
    
            return $this->convertToString($abs);
        }
    
        /**
         * Converts a decimal monetary value to a Money object.
         *
         * @param mixed $value A decimal monetary value.
         *
         * @return Money
         */
        public function convertToMoney($value) : Money
        {
            return $this->parser->parse((string) $value, $this->currency);
        }
    
        /**
         * Converts a value into a Money object, then into a decimal monetary
         * string.
         *
         * @param mixed $value Typically a Money object, int, float, or string
         * representing a decimal monetary value.
         *
         * @return string
         */
        public function convertToString($value) : string
        {
            if (! $value instanceof Money) {
                $value = $this->convertToMoney($value);
            }
    
            return $this->formatter->format($value);
        }
    }
    
    opened by pmjones 6
  • MoneyRange implementation

    MoneyRange implementation

    We're using this library in a couple of projects, and have hit upon the need for a monetary range a number of times. While we can work around this, it's much cleaner using a MoneyRange value object.

    I'm aware this is slightly extending the scope of this library, so it perhaps doesn't belong here and should be in a separate library, but it's been a useful addition for us.

    Examples

    $range = MoneyRange::GBP(100, 1000);
    
    $range->contains(Money::GBP(200)); // returns true
    $range->midPoint(); // returns Money::GBP(500)
    $range->lessThan(Money::GBP(2000)); // returns true
    $range->lessThan(MoneyRange::GBP(2000, 3000)); // returns true
    $range->multiply(2); // returns MoneyRange::GBP(200, 2000)
    

    I'd welcome feedback on this!

    opened by jaikdean 2
Releases(v4.1.0)
Begining of an e-commerce website using PHP and the MVC Pattern

Begining of an e-commerce website using PHP and the MVC Pattern

Ulysse Valdenaire 5 Dec 25, 2022
Example of Pre-Loader Implementation for Magento 2

Example of preloader Implements optimistic preloader for configurable product data without taking into account simple product status for: Price of con

EcomDev B.V. 10 Dec 12, 2021
A simple shopping cart implementation for Laravel

LaravelShoppingcart A simple shoppingcart implementation for Laravel. Installation Install the package through Composer. Run the Composer require comm

Rob Gloudemans 3.5k Jan 7, 2023
Shopping Cart Implementation for Laravel Framework

Shopping Cart Implementation for Laravel Framework

darryl fernandez 1.2k Jan 4, 2023
A simple shoppingcart implementation for Phalcon.

Phalcon Shopping Cart A simple shoppingcart implementation for Phalcon. Installation Install the package through Composer. Run the Composer require co

Sergey Mukhin 3 Oct 7, 2022
A simple shopping cart implementation for Laravel

LaravelShoppingcart This is a fork of Crinsane's LaravelShoppingcart extended with minor features compatible with Laravel 8+. An example integration c

Patrick 453 Jan 2, 2023
A framework agnostic, multi-gateway payment processing library for PHP 5.6+

Omnipay An easy to use, consistent payment processing library for PHP Omnipay is a payment processing library for PHP. It has been designed based on i

The League of Extraordinary Packages 5.7k Jan 4, 2023
PHP 7+ Payment processing library. It offers everything you need to work with payments: Credit card & offsite purchasing, subscriptions, payouts etc. - provided by Forma-Pro

Supporting Payum Payum is an MIT-licensed open source project with its ongoing development made possible entirely by the support of community and our

Payum 1.7k Jan 5, 2023
LiteCart - Free Shopping Cart Platform - Built with PHP, jQuery HTML 5 and CSS 3

LiteCart is a lightweight e-commerce platform for online merchants. Developed in PHP, HTML 5, and CSS 3. LiteCart is a registered trademark, property

LiteCart 153 Dec 28, 2022
A free shopping cart system. OpenCart is an open source PHP-based online e-commerce solution.

OpenCart is a free open source ecommerce platform for online merchants. OpenCart provides a professional and reliable foundation from which to build a successful online store.

OpenCart 6.6k Dec 31, 2022
PHP payment library to easily integrate Baltic banklinks (supports old and new iPizza protocol), E-commerce gateaway (Estcard, Nets Estonia), Liisi Payment Link and Pocopay.

PHP Payment library PHP payment library to easily integrate Baltic banklinks, E-commerce gateaway (Estcard, Nets Estonia), Liizi Payment Link and Poco

Rene Korss 34 Apr 27, 2022
Full-featured e-commerce platform with multi-domain and multi-language support for PHP 8

Surikata.io Full-featured e-commerce platform with multi-domain and multi-language support for PHP 8. Free to use for both commercial and personal pro

null 8 Apr 5, 2022
Simple E-Comerce build use Laravel 5.6 require php version 5.6 or 7.4

Simple E-Comerce build use Laravel 5.6 require php version 5.6 or 7.4

Rangga Darmajati 3 Oct 7, 2021
Benefit PHP Shopping Cart Class (Simple Lightweight)

Benefit-PHP-Shopping-Cart-Class Benefit PHP Shopping Cart Class (Simple Lightweight) Table of Contents Initialization Get All Get Item Get Item Child

Osman Cakmak 8 Sep 6, 2022
A PHP package to simplify using DPO Payment API in your application.

DPO (Direct Pay Online) PHP Package The best DPO php package, simple Ever This is the package that will help you add DPO Payment API to your PHP Appli

Zepson Technologies 3 Nov 8, 2022
StrongShop 是一款免费开源的跨境电商商城网站。基于 PHP Laravel6 框架开发,遵循 BSD-3-Clause 开源协议,免费商用。支持多语言,多货币,多种国际配送方式。PayPal 支付,国际信用卡支付。PC Web 端和移动端自适应。

跨境电商独立站的理想选择之一 StrongShop 简介 StrongShop 是一款免费开源的跨境电商商城网站。 StrongShop 是基于 PHP Laravel 框架开发的一款 Web 商城系统。 开发缘起是公司的一套跨境商城系统,原先公司使用的系统是基于 Ecshop 二次开发的,后来因为

OpenStrong 38 Dec 9, 2022
Fausse page de connexion faite en PHP, inclu: blacklist IP, redirection au choix, webhook discord, logs. Pour les informations: IP, mdp, email, géolocalisation, navigateur et bien d'autre

Scam page PayPal v2 1. Tutoriel explicatif [striked] > https://www.youtube.com/watch?v=5_gggGhVZlY [not striked] > https://mega.nz/file/8Idw0BgR#4g3Q0

Esio_dev 20 Nov 13, 2022
Chargily ePay Gateway PHP

Chargily ePay Gateway PHP Make ePayment gateway integration with Chargily easier Currently support payment by CIB / EDAHABIA cards and soon by Visa /

Chargily 20 Nov 3, 2022
Aimeos PHP e-commerce framework for ultra fast online shops, scalable marketplaces, complex B2B applications

Aimeos is an Open Source e-commerce framework for online shops consisting of the e-commerce library, the administration interface and different front-ends. It's a modular stack that offers an unmatched combination of flexibility and speed.

Aimeos 2.6k Dec 30, 2022