Advertisement

Money Pattern: The Right Way to Represent Value-Unit Pairs

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

The Money Pattern, defined by Martin Fowler and published in Patterns of Enterprise Application Architecture, is a great way to represent value-unit pairs. It is called Money Pattern because it emerged in a financial context and we will illustrate its use mainly in this context using PHP.


A PayPal Like Account

I have no idea how PayPal is implemented, but I think it is a good idea to take its functionality as an example. Let me show you what I mean, my PayPal account has two currencies: US Dollars and Euro. It keeps the two values separated, but I can receive money in any currency, I can see my total amount in any of the two currencies and I can extract in any of the two. For the sake of this example, imagine that we extract in any of the currencies and automatic conversion is done if the balance of that specific currency is less than what we want to transfer but yet there is still enough money in the other currency. Also, we will limit the example to only two currencies.


Getting an Account

If I were to create and use an Account object, I would like to initialize it with an account number.

function testItCanCrateANewAccount() {
	$this->assertInstanceOf("Account", new Account(123));
}

This will obviously fail because we have no Account class, yet.

class Account {

}

Well, writing that in a new "Account.php" file and requiring it in the test, made it pass. However, this is all being done just to make ourselves comfortable with the idea. Next, I am thinking of getting the account's id.

function testItCanCrateANewAccountWithId() {
	$this->assertEquals(123, (new Account(123))->getId());
}

I actually changed the previous test into this one. There is no reason to keep the first one. It lived it's life, meaning it forced me to think about the Account class and actually create it. We can now move on.

class Account {

	private $id;

	function __construct($id) {
		$this->id = $id;
	}

	public function getId() {
		return $this->id;
	}

}

The test is passing and Account is starting to look like a real class.


Currencies

Based on our PayPal analogy, we may want to define a primary and a secondary currency for our account.

private $account;

protected function setUp() {
	$this->account = new Account(123);
}

[...]

function testItCanHavePrimaryAndSecondaryCurrencies() {
	$this->account->setPrimaryCurrency("EUR");
	$this->account->setSecondaryCurrency('USD');

	$this->assertEquals(array('primary' => 'EUR', 'secondary' => 'USD'), $this->account->getCurrencies());
}

Now the above test will force us to write the following code.

class Account {

	private $id;
	private $primaryCurrency;
	private $secondaryCurrency;

[...]

	function setPrimaryCurrency($currency) {
		$this->primaryCurrency = $currency;
	}

	function setSecondaryCurrency($currency) {
		$this->secondaryCurrency = $currency;
	}

	function getCurrencies() {
		return array('primary' => $this->primaryCurrency, 'secondary' => $this->secondaryCurrency);
	}

}

For the time being, we are keeping currency as a simple string. This may change in the future, but we are not there yet.


Gimme the Money

There are endless reasons why not to represent money as a simple value. Floating point calculations? Anyone? What about currency fractionals? Should we have 10, 100 or 1000 cents in some exotic currency? Well, this is another problem we will have to avoid. What about allocating indivisible cents?

There are just too many and exotic problems when working with money to write them down in code, so we will go directly on to the solution, the Money Pattern. This is quite a simple pattern, with great advantages and many use cases, far out of the financial domain. Whenever you have to represent a value-unit pair you should probably use this pattern.

UML

The Money Pattern is basically a class encapsulating an amount and currency. Then it defines all the mathematical operations on the value with respect to the currency. "allocate()" is a special function to distribute a specific amount of money between two or more recipients.

So, as a user of Money I would like to be able to do this in a test:

class MoneyTest extends PHPUnit_Framework_TestCase {

	function testWeCanCreateAMoneyObject() {
		$money = new Money(100, Currency::USD());
	}

}

But that won't work yet. We need both Money and Currency. Even more, we need Currency before Money. This will be a simple class, so I will skip testing it for now. I am pretty sure the IDE can generate most of the code for me.

class Currency {

	private $centFactor;
	private $stringRepresentation;

	private function __construct($centFactor, $stringRepresentation) {
		$this->centFactor = $centFactor;
		$this->stringRepresentation = $stringRepresentation;
	}

	public function getCentFactor() {
		return $this->centFactor;
	}

	function getStringRepresentation() {
		return $this->stringRepresentation;
	}

	static function USD() {
		return new self(100, 'USD');
	}

	static function EUR() {
		return new self(100, 'EUR');
	}

}

That's enough for our example. We have two static functions for USD and EUR currencies. In a real application, we would probably have a general constructor with a parameter and load all the currencies from a database table or, even better, from a text file.

Next, include the two new files in the test:

require_once '../Currency.php';
require_once '../Money.php';

class MoneyTest extends PHPUnit_Framework_TestCase {

	function testWeCanCreateAMoneyObject() {
		$money = new Money(100, Currency::USD());
	}

}

This test still fails, but at least it can find Currency now. We continue with a minimal Money implementation. A little bit more than what this test strictly requires since it is, again, mostly auto-generated code.

class Money {

	private $amount;
	private $currency;

	function __construct($amount, Currency $currency) {
		$this->amount = $amount;
		$this->currency = $currency;
	}

}

Please note, we enforce the type Currency for the second parameter in our constructor. This is a nice way to avoid our clients sending in junk as currency.


Comparing Money

The first thing that came into my mind after having the minimal object up and running was that I will have to compare money objects somehow. Then I remembered that PHP is quite smart when it comes to comparing objects, so I wrote this test.

function testItCanTellTwoMoneyObjectAreEqual() {
	$m1 = new Money(100, Currency::USD());
	$m2 = new Money(100, Currency::USD());

	$this->assertEquals($m1,$m2);
	$this->assertTrue($m1 == $m2);
}

Well, that actually passes. The "assertEquals" function can compare the two objects and even the built-in equality condition from PHP "==" is telling me what I expect. Nice.

But, what about if we are interested in one being bigger than the other? To my even greater surprise, the following test also passes without any problems.

function testOneMoneyIsBiggerThanTheOther() {
	$m1 = new Money(200, Currency::USD());
	$m2 = new Money(100, Currency::USD());

	$this->assertGreaterThan($m2, $m1);
	$this->assertTrue($m1 > $m2);
}

Which leads us to...

function testOneMoneyIsLessThanTheOther() {
	$m1 = new Money(100, Currency::USD());
	$m2 = new Money(200, Currency::USD());

	$this->assertLessThan($m2, $m1);
	$this->assertTrue($m1 < $m2);
}

... a test that passes immediately.


Plus, Minus, Multiply

Seeing so much PHP magic actually working with comparisons, I could not resist to try this one.

function testTwoMoneyObjectsCanBeAdded() {
	$m1 = new Money(100, Currency::USD());
	$m2 = new Money(200, Currency::USD());
	$sum = new Money(300, Currency::USD());

	$this->assertEquals($sum, $m1 + $m2);
}

Which fails and says:

Object of class Money could not be converted to int

Hmm. That sounds pretty obvious. At this point we have to make a decision. It is possible to continue this exercise with even more PHP magic, but this approach will, at some point, transform this tutorial into a PHP cheatsheet instead of a design pattern. So, let's make the decision to implement the actual methods to add, subtract and multiply money objects.

function testTwoMoneyObjectsCanBeAdded() {
	$m1 = new Money(100, Currency::USD());
	$m2 = new Money(200, Currency::USD());
	$sum = new Money(300, Currency::USD());

	$this->assertEquals($sum, $m1->add($m2));
}

This test fails as well, but with an error telling us there is no "add" method on Money.

public function getAmount() {
	return $this->amount;
}

function add($other) {
	return new Money($this->amount + $other->getAmount(), $this->currency);
}

To sum up two Money objects, we need a way to retrieve the amount of the object we pass in as the argument. I prefer to write a getter, but setting the class variable to be public would also be an acceptable solution. But what if we want to add Dollars to Euros?

/**
 * @expectedException Exception
 * @expectedExceptionMessage Both Moneys must be of same currency
 */
function testItThrowsExceptionIfWeTryToAddTwoMoneysWithDifferentCurrency() {
	$m1 = new Money(100, Currency::USD());
	$m2 = new Money(100, Currency::EUR());

	$m1->add($m2);
}

There are several ways to deal with operations on Money objects with different currencies. We will throw an exception and expect it in the test. Alternatively, we could implement a currency conversion mechanism in our application, call it, convert both Money objects into some default currency and compare them. Or, if we would have a more sophisticated currency conversion algorithm, we could always convert from one to another and compare in that converted currency. The thing is, that when conversion comes into place, conversion fees have to be considered and things will be getting quite complicated. So let's just throw that exception and move on.

public function getCurrency() {
	return $this->currency;
}

function add(Money $other) {
	$this->ensureSameCurrencyWith($other);
	return new Money($this->amount + $other->getAmount(), $this->currency);
}

private function ensureSameCurrencyWith(Money $other) {
	if ($this->currency != $other->getCurrency())
		throw new Exception("Both Moneys must be of same currency");
}

That's better. We do a check to see if the currencies are different and throw an exception. I already wrote it as a separate private method, because I know we will need it in the other mathematical operations as well.

Subtraction and multiplication are very similar to addition, so here is the code and you can find the tests in the attached source code.

function subtract(Money $other) {
	$this->ensureSameCurrencyWith($other);
	if ($other > $this)
		throw new Exception("Subtracted money is more than what we have");
	return new Money($this->amount - $other->getAmount(), $this->currency);
}

function multiplyBy($multiplier, $roundMethod = PHP_ROUND_HALF_UP) {
	$product = round($this->amount * $multiplier, 0, $roundMethod);
	return new Money($product, $this->currency);
}

With subtraction, we have to make sure we have enough money and with multiplication, we must take actions to round things up or down so that division (multiplication with numbers less than one) will not produce "half cents". We keep our amount in cents, the lowest possible factor of the currency. We can not divide it more.


Introducing Currency to Our Account

We have an almost complete Money and Currency. It is time to introduce these objects to Account. We will start with Currency, and change our tests accordingly.

function testItCanHavePrimaryAndSecondaryCurrencies() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$this->account->setSecondaryCurrency(Currency::USD());

	$this->assertEquals(array('primary' => Currency::EUR(), 'secondary' => Currency::USD()), $this->account->getCurrencies());
}

Because of the dynamic typing nature of PHP, this test passes without any problems. However I would like to force the methods in Account to use Currency objects and do not accept anything else. This is not mandatory, but I find these kinds of type hintings extremely useful when someone else needs to understand our code.

function setPrimaryCurrency(Currency $currency) {
	$this->primaryCurrency = $currency;
}

function setSecondaryCurrency(Currency $currency) {
	$this->secondaryCurrency = $currency;
}

Now it is obvious to anyone reading this code for the first time that Account works with Currency.


Introducing Money to Our Account

The two basic actions any account must provide is: deposit - meaning adding money to an account - and withdraw - meaning removing money from an account. Depositing has a source and withdrawing has a destination other than our current account. We will not go into details about how to implement these transactions, we will only concentrate on implementing the effects these have on our account. So, we can imagine a test like this for depositing.

function testAccountCanDepositMoney() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money);

	$this->assertEquals($money, $this->account->getPrimaryBalance());
}

This will force us to write quite a lot of implementation code.

class Account {

	private $id;
	private $primaryCurrency;
	private $secondaryCurrency;
	private $secondaryBalance;
	private $primaryBalance;

	function getSecondaryBalance() {
		return $this->secondaryBalance;
	}

	function getPrimaryBalance() {
		return $this->primaryBalance;
	}

	function __construct($id) {
		$this->id = $id;
	}

	[...]

	function deposit(Money $money) {
		$this->primaryCurrency == $money->getCurrency() ? $this->primaryBalance = $money : $this->secondaryBalance = $money;
	}

}

OK, OK. I know, I wrote more than what was absolutely necessary, for production. But I don't want to bore you to death with baby-steps and I am also fairly sure the code for secondaryBalance will work correctly. It was almost entirely generated by the IDE. I will even skip testing it. While this code makes our test pass, we have to ask ourselves what happens when we do subsequent deposits? We want our money to be added to the previous balance.

function testSubsequentDepositsAddUpTheMoney() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money); //One euro in the account
	$this->account->deposit($money); //Two euros in the account

	$this->assertEquals($money->multiplyBy(2), $this->account->getPrimaryBalance());
}

Well, that fails. So we have to update our production code.

function deposit(Money $money) {
	if ($this->primaryCurrency == $money->getCurrency()){
		$this->primaryBalance = $this->primaryBalance ? : new Money(0, $this->primaryCurrency);
		$this->primaryBalance = $this->primaryBalance->add($money);
	}else {
		$this->secondaryBalance = $this->secondaryBalance ? : new Money(0, $this->secondaryCurrency);
		$this->secondaryBalance = $this->secondaryBalance->add($money);
	}
}

This is much better. We are probably done with the deposit method and we can continue with withdraw.

function testAccountCanWithdrawMoneyOfSameCurrency() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money);
	$this->account->withdraw(new Money(70, Currency::EUR()));

	$this->assertEquals(new Money(30, Currency::EUR()), $this->account->getPrimaryBalance());

}

This is just a simple test. The solution is simple, also.

function withdraw(Money $money) {
	$this->primaryCurrency == $money->getCurrency() ?
		$this->primaryBalance = $this->primaryBalance->subtract($money) :
		$this->secondaryBalance = $this->secondaryBalance->subtract($money);
}

Well, that works, but what if we want to use a Currency that is not in our account? We should throw an Excpetion for that.

/**
 * @expectedException Exception
 * @expectedExceptionMessage This account has no currency USD
 */

function testThrowsExceptionForInexistentCurrencyOnWithdraw() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money);
	$this->account->withdraw(new Money(70, Currency::USD()));
}

That will also force us to check our currencies.

function withdraw(Money $money) {
	$this->validateCurrencyFor($money);
	$this->primaryCurrency == $money->getCurrency() ?
		$this->primaryBalance = $this->primaryBalance->subtract($money) :
		$this->secondaryBalance = $this->secondaryBalance->subtract($money);
}

private function validateCurrencyFor(Money $money) {
	if (!in_array($money->getCurrency(), $this->getCurrencies()))
			throw new Exception(
				sprintf(
					'This account has no currency %s',
					$money->getCurrency()->getStringRepresentation()
				)
			);
}

But what if we want to withdraw more than what we have? That case was already addressed when we implemented subtraction on Money. Here is the test that proves it.

/**
 * @expectedException Exception
 * @expectedExceptionMessage Subtracted money is more than what we have
 */
function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money);
	$this->account->withdraw(new Money(150, Currency::EUR()));
}

Dealing With Withdraw and Exchange

One of the more difficult things to deal with when we are working with multiple currencies is exchanging between them. The beauty of this design pattern is that it allows us to somewhat simplify this problem by isolating and encapsulating it in its own class. While the logic in an Exchange class may be very sophisticated, its use becomes much easier. For the sake of this tutorial, let's imagine that we have some very basic Exchange logic only. 1 EUR = 1.5 USD.

class Exchange {

	function convert(Money $money, Currency $toCurrency) {
		if ($toCurrency == Currency::EUR() && $money->getCurrency() == Currency::USD())
			return new Money($money->multiplyBy(0.67)->getAmount(), $toCurrency);
		if ($toCurrency == Currency::USD() && $money->getCurrency() == Currency::EUR())
			return new Money($money->multiplyBy(1.5)->getAmount(), $toCurrency);
		return $money;
	}

}

If we convert from EUR to USD we multiply the value by 1.5, if we convert from USD to EUR we divide the value by 1.5, otherwise we presume we are converting two currencies of the same type, so we do nothing and just return the money. Of course, in reality this would be a much more complicated class.

Now, having an Exchange class, Account can make different decisions when we want to withdraw Money in a currency, but we do not hove enough in that specific currency. Here is a test that better exemplifies it.

function testItConvertsMoneyFromTheOtherCurrencyWhenWeDoNotHaveEnoughInTheCurrentOne() {
	$this->account->setPrimaryCurrency(Currency::USD());
	$money = new Money(100, Currency::USD()); //That's 1 USD
	$this->account->deposit($money);

	$this->account->setSecondaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO = 1.5 USD
	$this->account->deposit($money);

	$this->account->withdraw(new Money(200, Currency::USD())); //That's 2 USD

	$this->assertEquals(new Money(0, Currency::USD()), $this->account->getPrimaryBalance());
	$this->assertEquals(new Money(34, Currency::EUR()), $this->account->getSecondaryBalance());
}

We set our account's primary currency to USD and deposit one dollar. Then we set the secondary currency to EUR and deposit one Euro. Then we withdraw two dollars. Finally, we expect to remain with zero dollars and 0.34 Euros. Of course this test throws an exception, so we have to implement a solution to this dilemma.

function withdraw(Money $money) {
	$this->validateCurrencyFor($money);
	if ($this->primaryCurrency == $money->getCurrency()) {
		if( $this->primaryBalance >= $money ) {
			$this->primaryBalance = $this->primaryBalance->subtract($money);
		}else{
			$ourMoney = $this->primaryBalance->add($this->secondaryToPrimary());
			$remainingMoney = $ourMoney->subtract($money);
			$this->primaryBalance = new Money(0, $this->primaryCurrency);
			$this->secondaryBalance = (new Exchange())->convert($remainingMoney, $this->secondaryCurrency);
		}

	} else {
		$this->secondaryBalance = $this->secondaryBalance->subtract($money);
	}
}

private function secondaryToPrimary() {
	return (new Exchange())->convert($this->secondaryBalance, $this->primaryCurrency);
}

Wow, lots of changes had to be made to support this automatic conversion. What is happening is that if we are in the case of extracting from our primary currency and we don't have enough money, we convert our balance of the secondary currency to primary and try the subtraction again. If we still do not have enough money, the $ourMoney object will throw the appropriate exception. Otherwise, we will set our primary balance to zero and we will convert the remaining money back to the secondary currency and set our secondary balance to that value.

It remains up to our account's logic to implement a similar automatic conversion for secondary currency. We will not implement such a symmetrical logic. If you like the idea, consider it as an exercise for you. Also, think about a more generic private method that would do the magic of auto-conversions in both cases.

This complex change to our logic also forces us to update another one of our tests. Whenever we want to auto-convert we must have a balance, even if it is just zero.

/**
 * @expectedException Exception
 * @expectedExceptionMessage Subtracted money is more than what we have
 */
function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave() {
	$this->account->setPrimaryCurrency(Currency::EUR());
	$money = new Money(100, Currency::EUR()); //That's 1 EURO
	$this->account->deposit($money);

	$this->account->setSecondaryCurrency(Currency::USD());
	$money = new Money(0, Currency::USD());
	$this->account->deposit($money);


	$this->account->withdraw(new Money(150, Currency::EUR()));
}

Allocating Money Between Accounts

The last method we need to implement on Money is allocate. This is the logic that decides what to do when dividing money between different accounts which can't be made exactly. For example, if we have 0.10 cents and we want to allocate them between two accounts in a proportion of 30-70 percents, that is easy. One account will get three cents and the other seven. However, if we want to make the same 30-70 ratio allocation of five cents, we have a problem. The exact allocation would be 1.5 cents in one account and 3.5 in the other. But we can not divide cents, so we have to implement our own algorithm to allocate the money.

There can be several solutions to this problem, one common algorithm is to add one cent sequentially to each account. If an account has more cents than its exact mathematical value, it should be eliminated from the allocation list and receive no further money. Here is a graphical representation.

Allocations

And a test to prove our point is below.

function testItCanAllocateMoneyBetween2Accounts() {
	$a1 = $this->anAccount();
	$a2 = $this->anAccount();
	$money = new Money(5, Currency::USD());
	$money->allocate($a1, $a2, 30, 70);

	$this->assertEquals(new Money(2, Currency::USD()), $a1->getPrimaryBalance());
	$this->assertEquals(new Money(3, Currency::USD()), $a2->getPrimaryBalance());
}

private function anAccount() {
	$account = new Account(1);
	$account->setPrimaryCurrency(Currency::USD());
	$account->deposit(new Money(0, Currency::USD()));
	return $account;
}

We just create a Money object with five cents and two accounts. We call allocate and expect the two to three values to be in the two accounts. We also created a helper method to quickly create accounts. The test fails, as expected, but we can make it pass quite easily.

function allocate(Account $a1, Account $a2, $a1Percent, $a2Percent) {
	$exactA1Balance = $this->amount * $a1Percent / 100;
	$exactA2Balance = $this->amount * $a2Percent / 100;

	$oneCent = new Money(1, $this->currency);
	while ($this->amount > 0) {
		if ($a1->getPrimaryBalance()->getAmount() < $exactA1Balance) {
			$a1->deposit($oneCent);
			$this->amount--;
		}
		if ($this->amount <= 0)
			break;
		if ($a2->getPrimaryBalance()->getAmount() < $exactA2Balance) {
			$a2->deposit($oneCent);
			$this->amount--;
		}
	}
}

Well, not the simplest code, but it is working correctly, as the passing of our test proves it. The only thing we can still do to this code is to reduce the small duplication inside the while loop.

function allocate(Account $a1, Account $a2, $a1Percent, $a2Percent) {
	$exactA1Balance = $this->amount * $a1Percent / 100;
	$exactA2Balance = $this->amount * $a2Percent / 100;

	while ($this->amount > 0) {
		$this->allocateTo($a1, $exactA1Balance);
		if ($this->amount <= 0)
			break;
		$this->allocateTo($a2, $exactA2Balance);
	}
}

private function allocateTo($account, $exactBalance) {
	if ($account->getPrimaryBalance()->getAmount() < $exactBalance) {
		$account->deposit(new Money(1, $this->currency));
		$this->amount--;
	}
}

Final Thoughts

What I find amazing with this little pattern is the large range of cases where we can apply it.

We are done with our Money Pattern. We saw that it is quite a simple pattern, which encapsulates the specifics of the money concept. We also saw that this encapsulation alleviates the burden of computations from Account. Account can concentrate on representing the concept from a higher level, from the point of view of the bank. Account can implement methods like connection with account holders, IDs, transactions and money. It will be an orchestrator not a calculator. Money will take care of calculations.

What I find amazing with this little pattern is the large range of cases where we can apply it. Basically, every time you have a value-unit pair, you can use it. Imagine you have a weather application and you want to implement a representation for temperature. That would be the equivalent of our Money object. You can use Fahrenheit or Celsius as currencies.

Another use case is when you have a mapping application and you want to represent distances between points. You can easily use this pattern to switch between Metric or Imperial measurements. When you work with simple units, you can drop the Exchange object and implement the simple conversion logic inside your "Money" object.

So, I hope you enjoyed this tutorial and I am eager to hear about the different ways that you might use this concept. Thank you for reading.

Advertisement