Managing Discounts, Vouchers, and Referrals with PHP 5 Ecommerce

January 2010

Discount codes

Discount codes are a great way to both entice new customers into a store, and also to help retain customers with special discounts. The discount code should work by allowing the customer to enter a code, which will then be verified by the store, and then a discount will be applied to the order.

The following are discount options we may wish to have available in our store:

  • A fixed amount deducted from the cost of the order
  • A fixed percentage deducted from the cost of the order
  • The shipping cost altered, either to free or to a lower amount
  • Product-based discounts (although we won't cover this one in the article)

It may also be useful to take into account the cost of the customer's basket; after all if we have a $5 discount code, we probably wouldn't want that to apply for orders of $5 or lower, and may wish to apply a minimum order amount.

Discount codes data

When storing discount codes in the framework, we need to store and account for:

  • The voucher code itself, so that we can check that the customer is entering a valid code
  • Whether the voucher code is active, as we may wish to prepare some voucher codes, but not have them usable until a certain time, or we may wish to discontinue a code
  • A minimum value for the customer's basket, either as an incentive for the customer to purchase more or to prevent loss-making situations (for example a $10 discount on a $5 purchase!)
  • The type of discount:
    • Percentage: To indicate that the discount amount is a percentage to be removed from the cost
    • Fixed amount deducted: To indicate that the discount amount is a fixed amount to be removed from the order total
    • Fixed amount set to shipping: To indicate that the discount amount is to be the new value for the shipping cost
  • Discount amount; that is, the amount of discount to be applied
  • The number of vouchers issued, if we wish to limit the number of uses of a particular voucher code
  • An expiry date, so that if we wish to have the voucher code expire, codes with a date after the stored expiry date would no longer work

Discount codes database

The following table illustrates this information as database fields within a table:

The default value for num_vouchers is -1, which we will use for vouchers that are not limited to a set number of issues.





Integer (Primary Key, Auto increment)

For the framework to reference the code



The code the customer enters into the order



If the code can be used



The minimum cost of the customer's basket for the code to work for them



The type of discount



Number of times the voucher can be used



The date the voucher code expires, and is no longer usable

The following code represents this data in our database:

CREATE TABLE `discount_codes` (
`vouchercode` VARCHAR( 25 ) NOT NULL ,
`active` TINYINT( 1 ) NOT NULL ,
`min_basket_cost` FLOAT NOT NULL ,
`discount_operation` ENUM( '-', '%', 's' ) NOT NULL ,
`discount_amount` FLOAT NOT NULL ,
`num_vouchers` INT( 11 ) NOT NULL DEFAULT '-1',

Discount codes functionality

The functionality for discount codes can be encapsulated into a single function; this function should be called when the basket is loaded and updated. (That is, if the customer changes the quantity of products in the basket, we must run this; also, if the customer adds a voucher code to their order, we must obviously call this function.)

  The function needs to:

  1. Check to see if the customer has entered a voucher code with their order.
  2. If they have, it must look up the voucher code to see if it exists, or doesn't exist.
  3. If the voucher code exists, it must check to see if the code has expired and is still able to be used (that is, that num_vouchers is greater than 0 or equal to -1).
  4. Assuming this is the case, it must then check to see that the customer's basket cost is at least that of the minimum order amount in the discount codes record in the database.
  5. If the voucher is able to be used, it must then determine the type of voucher, and make the relevant discount to either the basket cost, or the shipping costs.

The following code does all of this; some of the relevant sections described are highlighted within the code:

* Consider and apply voucher codes
* Types of voucher code
* @param voucherCode String
* @return void
private function considerVouchers( $voucherCode )
//The voucher code value is checked to ensure it is not empty
// (as per point 1)
if( $voucherCode != '' )
// we need the date, to see if the voucher has expired
$cts = date('Y-m-d H:i:s');
$voucher_sql = "SELECT *, if('{$cts}' > expiry, 1, 0)
AS expired FROM discount_codes
WHERE vouchercode='{$voucherCode}' LIMIT 1";
$this->registry->getObject('db')->executeQuery( $voucher_sql );
if( $this->registry->getObject('db')->numRows() == 0 )
$this->voucher_notice = 'Sorry, the voucher code you entered
is invalid';
$voucher = $this->registry->getObject('db')->getRows();
if( $voucher['active'] == 1 )
if( $voucher['expired'] == 1 )
$this->voucher_notice = 'Sorry, this voucher has
return false;
// check to see there are some vouchers, and customer
// has enough in their basket (points 3 and 4)
if( $voucher['num_vouchers'] != 0 )
if( $this->cost >= $voucher['min_basket_cost'] )
$this->discountCode = $voucherCode;
$this->discountCodeId = $voucher['ID'];
// If the discount operation is a percentage, then
// the discount value is applied to the basket cost
// to calculate that percentage. This amount is then
// deducted from the order cost, giving a "discount
// value"% discount from the order.
if( $voucher['discount_operation'] == '%' )
$this->cost = $this->
cost - (($this->cost)/100)*$voucher['discount_amount'];
$this->voucher_notice = 'A '
. $voucher['discount_amount']
. '% discount has been applied to your order';
return true;
// If the discount operation is a subtraction, then
// the discount amount from the discount code is
// deducted from the order cost, and the order cost
// is updated to reflect this.
elseif( $voucher['discount_operation'] == '-' )
$this->cost =
$this->cost - $voucher['discount_amount'];
$this->voucher_notice = 'A discount of £'
. $voucher['discount_amount']
. ' has been applied to your order';
return true;
// Finally, if the discount operation is set to s
// then, we set the shipping cost to the discount
// value. This could allow us to set free shipping,
// or just reduce shipping costs.
elseif( $voucher['discount_operation'] == 's' )
$this->shipping_cost = $voucher['discount_amount'];
$this->voucher_notice = 'Your orders shipping cost
has been reduced to £'
. $voucher['discount_amount'];
return true;
$this->voucher_notice = 'Sorry, your order total is
not enough for your order to qualify for this
discount code';
return false;
$this->voucher_notice = 'Sorry, this was a limited
edition voucher code, there are no more instances
of that code left';
return false;
$this->voucher_notice = 'Sorry, the vocuher code you
entered is no longer active';
return false;

Now, we have the basis for our discount code feature. We can issue discount codes as an incentive to get new customers, or to encourage existing customers to make more purchases.


Reducing the number of codes available

One feature we added to the database structure for our discount codes was the ability to limit the number of times a voucher could be used, effectively allowing a code to be "issued" a set number of times. We need to take this into account, and reduce the number of vouchers in circulation, once one has been used.

This won't be done at this stage, but will need to be implemented at the final checkout stage when an order is updated to "paid", or the customer pays online. The following function will do this for us; it checks to see if a code is used, or if it's unlimited or has expired, and if it's not unlimited and hasn't already expired, it updates the code to decrement the number of uses remaining:

private function adjustDiscountCodeQuantities( $codeId )
$sql = "SELECT num_vouchers FROM discount_codes WHERE ID=" . $codeId;
$this->registry->getObject('db')->executeQuery( $sql );
if( $this->registry->getObject('db')->numRows() > 0 )
$codeData = $this->registry->getObject('db')->getRows();
if( $codeData['num_vouchers'] > 0 )
$sql = "UPDATE discount_codes SET num_vouchers=num_vouchers-1
WHERE ID=" . $codeId;
$this->registry->getObject('db')->executeQuery( $sql );

Purchasable voucher codes

Voucher codes work in the same way as discount codes, except that they are purchased for use by a customer, as opposed to given away for promotional reasons.

Existing functionality

The discount codes and product variation features already give us most of the functionality required for purchasable voucher codes.

Discount codes

As a voucher code has the same functionality as our discount codes, which we built earlier, we have a large portion of the functionality in place. When a voucher is purchased, a new record in the discount codes table will be made, with a num_vouchers value of 1.

Product variations

Let's say we have product variations feature built, which allows us to create a purchasable voucher code product, with variations that increase the price in increments, perhaps of $5.

Required additional functionality

We only need to add additional functionality to this if we wish to automate the process of generating a voucher code when a customer purchases a voucher code. Without additional functionality, we would simply notice a new order, manually create a new discount code, and then e-mail the customer with the code. However, automating this would save us quite a lot of time, so let's look at what would be involved in doing this:

  1. First, we must wait until an order has its status updated to "paid". This would either be when a payment is made online by the customer, or when an offl ine payment is received and we, as the administrator, mark the order as "paid".
  2. The order contents must be searched for anything that is a purchasable voucher code.
  3. For each of these vouchers purchased, a new record must be automatically inserted into the voucher_codes table with the following values:
    • vouchercode: A randomly generated string
    • active: 1
    • min_basket_cost: The amount of the voucher purchased
    • discount_operation: (because we are subtracting an amount from the cost)
    • discount_amount: The amount of the voucher purchased
    • num_vouchers: 1
    • expiry: A year in the future would be a suitable validity period
  4. These vouchers would then be sent through e-mail to the customer.


To reward loyal customers, we may wish to offer referral discounts to them. This would work by encouraging our customers to introduce new customers, and giving them a credit based off a percentage of orders placed by customers they refer to our store. Customers referring other customers would be given a referral code, which could either be part of a link used to promote the site, or given to customers to enter somewhere on the order process.

The referral process should work like this:

  1. Check for a referral code in the URL.
  2. If a code is found in the URL, it should be stored in a cookie, unless a previous referral code is already being stored; in that case, the older code should be used.
  3. At the checkout stage, this code should be looked up, to check if it is valid and to see the commission to be applied to the customer who passed the referral. The order number, commission value, and referring customer should be stored in a database table.
  4. When the order is updated to "paid" or "paid online", the commission should be applied to the relevant customer's account.

Database changes

This requires a new database table, as well as some database alterations to work.

New table: Referrers

A referrers table is required to link customers to a unique referral code, and a commission percentage. It would require the following fields:




Customer ID


A reference to the customer's ID number

Referral Code

Varchar (Unique)

The customer's referral code, which he/she would give to friends

Commission percentage


The percentage of commission that would be credited back to the customer's account



If the customer is a referrer, or not, as we may wish to disable a customer's referral code, if he/she is abusing it


Customers should have a credit field, showing how much credit they have with the store based on referred customers. The orders table should also be altered to store a record of any referral code used.


The workflow of this feature would work like this:

  1. An order is placed, and a referral code is associated with it.
  2. The order is marked as "paid".
  3. A lookup is performed on the referrers table to find the customer and the percentage.
  4. The commission value is calculated.
  5. The referring customer's credit value is updated to include new commission earned.

We could extend this to record a list of transactions to a customer's credit, such as dates and amounts their account was credited by, and allow reports to be generated and sent to them. However, that could occupy several chapters itself!

Checkout process consideration

This requires an important consideration when we get to the checkout stage: we must take into account any credit stored on a customer's account. If they have credit, it should be deducted from any of their own orders, to ensure they can spend the commission they earned.


We now have a number of useful features to encourage new orders and customers to our store, as well as encouraging existing customers to promote our store through affiliate codes.

This included creating a voucher code system which supported vouchers that deducted a percentage from the order total, vouchers that deducted a fixed amount from the order total, and vouchers that altered the cost of shipping to the customer. These vouchers were able to take into account expiry dates, limited-use options, and the current cost of the customer's basket.

Taking this forward, we looked at how to extend our framework to allow customers to purchase vouchers as gifts for other customers, as well as how to extend our store to support referral bonuses and incentives for our customers.

If you have read this article you may be interested to view :

You've been reading an excerpt of:

PHP 5 E-commerce Development

Explore Title