cult3

Creating a PHP Shopping Basket Part 1 - Money, Currency and Taxes

Jan 21, 2015

Table of contents:

  1. Dealing with Money in PHP
  2. The problem of taxes
  3. Jurisdictions
  4. Conclusion

Ecommerce web applications are one of the most common new online businesses in 2015. A lot of companies want to either start their new business on the Internet or move their traditional offline business online.

There are many really good Open Source solutions for Ecommerce such as Magento or hosted solutions like Shopify.

However, due to bespoke requirements or the desire to integrate with existing legacy systems, you might find yourself in the situation where you need to roll your own application.

Building an Ecommerce application is no walk in the park as it’s a lot more involved than your average CRUD MVC gig. There are a huge amount of things you need to think about when dealing with payments, transactions and customers.

One of the big headaches is the link between the product pages and the payment gateway. Its usually not as simple as pulling a product from the database and then sending the customer to enter their credit card details.

Your problems are also multiplied when you have to deal with different currencies, tax rates and all the intricacies of the online shopping process and regulation.

This is a new short mini-series on building a PHP shopping basket. We will be looking at the problem of taking the raw product data and transforming it into the correct format for your payment gateway and transaction history.

Due to the intricate nature of the online shopping process and the need to satisfy the bespoke requirements of developers all over the world, we will also be looking at how we can write a generic PHP package that can be easily extended.

Dealing with Money in PHP

One of the fundamental building blocks of an Ecommerce application is the fact that we need to deal with money.

One of the big problems with naive implementations of an Ecommerce application is that the developer has allowed the database to dictate what type the money value should be stored as.

However, there are many potential problems when working with money in the form of rounding errors or mixing currencies without a conversion process.

The first important thing to realise is, when dealing with money, we definitely should not be using native values. Money is not just an number, it is also a currency. By using native values you run the risk or accidentally combining money values of different currencies.

Money is the classic example of a Value Object (What is the difference between Entities and Value Objects?). By encapsulating money values as Value Objects we can encode logic that protects the invariants of how money should work in the real world.

For example, a money value should always be a combination of a numeric value and a currency and you should never be able to blindly mix money values of two different currencies.

One of the most important characteristics of Value Objects is the fact that they are immutable. This means when the value of a Value Object must change, the object is destroyed and a new one is returned in its place. When you pay for something and receive change, you do not receive the same cash note but with a lower value, you receive a totally new cash value.

To read more about the problems of dealing with money in Ecommerce applications, take a look at How to handle money and currency in web applications.

Instead of reinventing the wheel, I’m going to be using mathiasverraes/money for this package.

So whenever you see an instance of Money you will know where it has come from.

The problem of taxes

Nearly every country in the world has taxes in one form or another, but if we look at a list of tax rates for each country, we can see that every country uses different rates!

What makes this even more complicated is countries like the United States who have different tax rates for each state as well as different tax rates for different types of products!

In order to solve this problem, we need a way to set the tax rate at runtime. We can’t use a lookup table of tax rates because it would never be 100% accurate and we would be fighting against it in situations where a developer needed a bespoke rate.

Instead we need to provide a common way of defining an object that meets the requirements of a tax rate.

This is the perfect situation for an interface (When should I code to an Interface?).

By defining an interface we can allow any object that implements the interface to be used as part of the package. This means we can provide implementations for common international tax rates, but we also allow anyone to add their own tax rate object for their country or region by simply implementing the interface.

So firstly we can define the TaxRate interface:

<?php namespace PhilipBrown\Basket;

interface TaxRate
{
    /**
     * Return the Tax Rate as a float
     *
     * @return float
     */
    public function float();

    /**
     * Return the Tax Rate as a percentage
     *
     * @return int
     */
    public function percentage();
}

This interface is simply enforcing any object that implements it to have the two methods above. In this case we want to be able to access the rate as a float (e.g 0.20) or as a percentage (e.g 20).

We can also provide the first implementation for the United Kingdom’s VAT:

<?php namespace PhilipBrown\Basket\TaxRates;

use PhilipBrown\Basket\TaxRate;

class UnitedKingdomValueAddedTax implements TaxRate
{
    /**
     * @var float
     */
    private $rate;

    /**
     * Create a new Tax Rate
     *
     * @return void
     */
    public function __construct()
    {
        $this->rate = 0.2;
    }

    /**
     * Return the Tax Rate as a float
     *
     * @return float
     */
    public function float()
    {
        return $this->rate;
    }

    /**
     * Return the Tax Rate as a percentage
     *
     * @return int
     */
    public function percentage()
    {
        return intval($this->rate * 100);
    }
}

By defining the interface we have decoupled the implementation of the tax rate. Now anyone can use their own tax rate without every having to open a pull request of hack internal logic.

Jurisdictions

One of the common constructs when dealing with transactions in a certain locale is the combination of a currency and a tax rate.

When two objects are more often required as a single unit, I think it is better to encapsulate them as a single object, rather than individual atomic units.

Again as with the tax rates, I don’t want to have to define every possible combination, and I don’t want to force developers to open a pull request to contribute a new combination to satisfy their requirements.

To get around this problem I can once again define an interface:

<?php namespace PhilipBrown\Basket;

interface Jurisdiction
{
    /**
     * Return the Tax Rate
     *
     * @return TaxRate
     */
    public function rate();

    /**
     * Return the currency
     *
     * @return Money\Currency
     */
    public function currency();
}

This interface is again pretty simple as we are only defining two methods to return the two properties of the class.

And once again I can define the first implementation as guidance:

<?php namespace PhilipBrown\Basket\Jurisdictions;

use Money\Currency;
use PhilipBrown\Basket\Jurisdiction;
use PhilipBrown\Basket\TaxRates\UnitedKingdomValueAddedTax;

class UnitedKingdom implements Jurisdiction
{
    /**
     * @var Currency
     */
    private $currency;

    /**
     * @var TaxRate
     */
    private $tax;

    /**
     * Create a new Jurisdiction
     *
     * @return void
     */
    public function __construct()
    {
        $this->tax = new UnitedKingdomValueAddedTax();
        $this->currency = new Currency("GBP");
    }

    /**
     * Return the Tax Rate
     *
     * @return TaxRate
     */
    public function rate()
    {
        return $this->tax;
    }

    /**
     * Return the currency
     *
     * @return Money\Currency
     */
    public function currency()
    {
        return $this->currency;
    }
}

Now if anyone wants to use their own combination of currency and tax rate, they don’t need to open a pull request to get their code added. By defining an interface we are leaving the package open to extension by anyone who consumes it.

Conclusion

The world of Ecommerce is definitely more complex than it looks on the surface. I think it’s pretty hard to estimate exactly what is involved at the start, especially when you start dealing with both physical and digital products and you have customers all over the world.

There is an overwhelming amount of complexity when it comes to international commerce and the accounting liabilities a company will face.

Dealing with money is also fraught in just about any type of application. Money is not something you want to make a mistake with because there could be big consequences for you or the company you are working for.

You should never deal with money as native types as there are too many important rules that should be observed. Money is the absolute classic example of a Value Object and so we can use a package from the Open Source community to satisfy our needs.

One of the difficulties of international commerce can be dealing with taxes and currencies for countries all over the world. Everyone has their own rules and rates, and so trying to encapsulate every possible combination would be a nightmare.

When a developer uses your Open Source package, she should never have to open a pull request to add a basic thing like the tax rate she needs to do her job.

A good Open Source library will understand where bespoke solutions will be required and what points of extension should be made available.

In today’s tutorial we’ve seen how defining an interface can allow any developer to provide their own object to satisfy their unique requirements. This means we have decoupled our code from any concrete implementation and we’re allowing any other developer to provide their own implementation to satisfy their requirements.

Next week we’re going to be building the next part of this open source page. But if you want to skip ahead and see the source code, it’s available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.