Gift Certificates in Drupal Commerce

4th May 2013

There isn't really an out-of-the-box solution for gift certificates in Drupal Commerce; there's no module (at time of writing) you can simply download, enable and forget about. Being Drupal, there are many ways to skin a cat, so to speak, so in this post I'm going to outline how I got them working on a site I built.

Of course, the way your gift certificates work may well differ from site-to-site; I had a way I wanted it to work, but your requirements might be subtly different, but hopefully whatever your requirements, some of what follows is of some use.

To outline, here's what I wanted to achieve:

  1. Gift certificates available as products, which a customer can purchase
  2. A range of values (£5, £10, £20 etc) available
  3. A product display page that differed from "conventional" products - there are less fields required, so the layout was likely to be different
  4. I wanted a customer to be able to specify a recipient for the gift certificate
  5. The customer needed to supply an email address for the recipient, so we can send it to them via email
  6. I wanted the customer to be able to specify the recipient's name, to personalise the email
  7. The customer ought to be able to enter their name as they wished it to appear on the email; the billing name on the order might well be too formal
  8. There ought to be an optional text field to add a personal message
  9. The gift certificate needed to be sent out via email when the order was completed, and paid for

As it turns out, step 3 - creating a custom product display page type - was a requirement, which I'll discuss later.

Okay, so the fundamental basis of this task is the gift certificate itself; a redemption code that could be used as payment - or part-payment - by the recipient.

This, there is a module for. Commerce Coupon takes care of all this. However out-of-the-box, it only really allows you to generate a coupon in the admin interface, whereas we need to automate this.

Let's start there, though - download and enable Commerce Coupon in the normal way.

This isn't quite enough, though - there are two sub-modules, for fixed-amount coupons and percentage-based coupons (money off). It's the fixed amount version we're interested in, so download and enable that too. You will probably want to enable the UI module bundled with Commerce coupon, too.

Play around, and note how it works - it adds a new field in the checkout process, where a customer can enter a coupon code, and the value of the corresponding coupon gets deducted from their order balance.

So far, so good. One thing I immediately found, though, is that the terminology didn't really fit what I was trying to achieve.

Fortunately, there's a simple way to change small portions of text without having to mess around wit translation files, and what-not - the String Overrides module.

Now go to Admin -> Configuration -> Regional and Language -> String overrides, where you can enter a string you want to change. I changed "Enter here your coupon code." (remember to add it exactly as it's displayed, which means including the full-stop) to "If you have a gift certificate, enter its code here" and "Granted amount" to "Value".

Now we need to create a new custom line item type. It'll be based on a product (as opposed to, say, a shipping or tax line item). You can create this by going to Admin -> Store -> Configuration -> Line item types -> Add a product line item type, and giving it a name, e.g. "Gift Certificate", or there's some code to do this automatically in my example module on Github.

Next, we need to add some fields to the line item. This requires the Commerce Custom Product module. Again, this is done automatically in my example module, or you might prefer to do it manually through the Field UI. I've added four fields, with the machine names in brackets - these are important, because we'll use them later on in the code:

  • Recipient name (field\_gift\_recipient\_name)
  • Recipient email (field\_gift\_recipient\_email)
  • Sender name (field\_gift\_sender\_name)
  • Personal message (field\_gift\_personal\_message)

Next up, we need to create a new Product Display content type, e.g. "Product Display (Gift Certificate)". I won't go into the details on how to do this, you've probably already done one. Remember I said that this was required anyway - this is because the Add to Cart form needs to act differently for this content type, because we need to add the fields that are specific to gift certificates. Don't forget to add a product reference field - you may well be able to re-use the existing field from your "conventional" products.

We need to ensure that the fields we created show up alongside the Add to Cart button for the relevant products; this is achieved through the Display settings on the Product field of the newly created Product Display type. Go to Admin -> Structure -> Content types -> Product Display (Gift Certificate) -> Manage Display (substituting Product Display (Gift Certificate) for whatever you called your new content type). Click the cog alongside the Product field to change the format settings for the Add to Cart form, and change Add to Cart line item type to Gift Certificate (or whatever you called the new line item type).

Now, if you visit the product page for our gift certificate product, you'll see that instead of a simple Add to Cart button, we've got a form instead, where in order to add a gift certificate to their cart, a customer is required to enter this additional information.

So, how do we send out a gift certificate when a customer orders it - only after, of course, the customer has paid for it.

This is where Rules comes in. Much of Commerce's functionality is based around rules, so we'll do this in much the same way.

As you're probably aware by now, a rule consists of three components:

  • an event; in this case when an order has been completed
  • a condition; we want this rule to fire when an order contains one or more gift certificates
  • an action; what we want to happen

Commerce already provides an action that fits our requirements. This event is labelled "Completing the checkout process". Go to Store -> Configuration -> Checkout settings -> Checkout rules. You'll probably see there are already a number of rules for this event, depending on what modules you have installed (for example, I'm using Commerce Stock, which decrements the stock level for each product, as well as Commerce Message, which sends out emails when an order has been placed). Click Add a checkout rule, and give it a name - e.g. "Send out a gift certificate".

Next up, we need to add a condition - we only want this rule to fire when a customer has ordered a gift certificate.

Click Add condition. Under Select the condition to add, select Order contains a particular product under the section Commerce Order.

Data selector should be commerce_order. The next field is Product SKU, so enter the SKU you specified earlier, to specify which product is of interest to us.

Operator should be set to =, for obvious reasons, and you can leave Quantity at 1. Ignore negate, and click Save.

For now, let's use an action for debugging purposes. Click Add action and under Select the action to add scroll down to System and select Display a message on the site, and enter some message, e.g. "DEBUGGING: The order contains a gift certificate!".

Now, try ordering a gift certificate, and when you complete the order you should see the message you just specified.

Here's where it gets a little more complicated. The rules module provides a number of simple actions, Commerce adds a few more, and various contributed modules add more still. But what we want to achieve can't really be achieved without writing some custom code to do what we want.

So, we need to create a new module to add the "action" to this rule. To summarise, this is what we want it to do:

  • get the order in question
  • load the line items for the order
  • look for the line items that contain products (as opposed to, say, shipping)
  • find those line items which contain a gift certificate
  • determine the value of the gift certificate
  • extract the recipient information (name, email), and the other bits of personalisation (sender's name, personal message)
  • generate a coupon of the required value
  • construct an email, containing the code for the new coupon, and using the information provided by the customer
  • send the email to the recipient's email address

Hopefully you'll see why this requires a module; it's not that complex, but enough that standard actions just won't cut it.

You'll need the email module, for the recipient's email field. Download and enable in the normal way.

First step, as always, a module .info file (in a directory called gift_certificates in your modules directory), or grab the code from Github:

name = Gift Certificates
description = Supporting functionality for gift certificates
core = 7.x
package = Commerce

dependencies[] = commerce
dependencies[] = commerce_coupon
dependencies[] = commerce_coupon_fixed_amount
dependencies[] = commerce_custom_product
dependencies[] = email

Note the dependencies - but if you've been following along, you should have these already installed.

Next up, we need to define our action. We do this by implementing hook_rules_action_info() thus:

/**
 * Implements hook_rules_action_info().
 */
function gift_certificates_rules_action_info() {
  return array(
    'gift_certificates_action_send' => array(
      'label' => t('Send Gift Certificate'),
      'group' => t('Gift Certificates'),
    ),
  );
}

Now let's start on the function which gets called when this action fires:

/**
 * Rule action; sends gift certificate(s).
 */
function gift_certificates_action_send() {

  // Get the order
  $order_id = arg(1);
  $order = commerce_order_load($order_id);

When the action fires, the order ID becomes available in arg(1) - try calling dpm (with Devel installed) if you don't believe me. There's probably a more elegant way of doing this, but let's keep it simple for now.

Next step is to iterate through the line items:

// Go through the line items
  foreach ($order->commerce_line_items[LANGUAGE_NONE] as $line_item_item) {
    $line_item_id = $line_item_item['line_item_id'];
    $line_item = commerce_line_item_load($line_item_id);

    // is this a gift certificate?
    if ($line_item->type == 'gift_certificate') {

Simple enough; we iterate through the line item IDs in the order object - each item is in the form array('line_item_id' => 123).

Then, we load the line item and then check its type; we're only interested in the line item type we created earlier; note that you might need to change this depending on how you set it up.

Next thing is to grab the product from the line item:

// create a params array, to use in the subsequent email
$params = array();

// get the product ID
$product_id = $line_item->commerce_product[LANGUAGE_NONE][0]['product_id'];

// load the product
$product = commerce_product_load($product_id);

Then we extract the information from the fields we attached to the gift certificate line item:

// get the recipient's details
$params['recipient_name'] = $line_item->field_gift_recipient_name[LANGUAGE_NONE][0]['safe_value'];
$params['email'] = $line_item->field_gift_recipient_email[LANGUAGE_NONE][0]['email'];

// and the sender details
$params['sender_name'] = $line_item->field_gift_sender_name[LANGUAGE_NONE][0]['safe_value'];

// and optionally, the personal message
$params['personal_message'] = $line_item->field_gift_personal_message[LANGUAGE_NONE][0]['safe_value'];

Next, we programmatically create the coupon:

// Create the coupon
$coupon = commerce_coupon_create('commerce_coupon_fixed');

// set to single use
$coupon->commerce_coupon_number_of_uses[LANGUAGE_NONE][0]['value'] = 1;

// set the amount
$coupon->commerce_coupon_fixed_amount[LANGUAGE_NONE][0] = array(
  'amount'  => $product->commerce_price[LANGUAGE_NONE][0]['amount'],
  'currency_code' => $product->commerce_price[LANGUAGE_NONE][0]['currency_code'],
);

// save the coupon
commerce_coupon_save($coupon);

// get the coupon code
$params['coupon_code'] = $coupon->commerce_coupon_code[LANGUAGE_NONE][0]['value'];

// add the formatted amount to the parameters
$params['coupon_amount'] = commerce_currency_format(
  $coupon->commerce_coupon_fixed_amount[LANGUAGE_NONE][0]['amount'],
  $coupon->commerce_coupon_fixed_amount[LANGUAGE_NONE][0]['currency_code']
);

There are a few things to note here:

  • We need to create a fixed amount coupon; as opposed to, say, a money-off one
  • We don't need to worry about setting the coupon code; it's done for us
  • We only want the coupon to be used once, so we need to set the number of uses to one
  • The price and currency comes from the commerce_price component of the product, which we extracted from the line item
  • We extract the coupon code from the coupon once we've saved it, and insert it into the $params array for use later
  • We also format the price, and include it in the $params array too

Finally, we send the email to the recipient, passing in the various values we've collected along the way:

// Now send the coupon via email to the recipient
gift_certificates_mail_send($params);

Here's the definition of gift_certificates_mail_send:

/**
 * Sends an e-mail.
 *
 * @param $variables
 *   An array of variables used to send the mail, including the various parameters
 */
function gift_certificates_mail_send($variables) {

  $module = 'gift_certificates';
  $key = 'gift_certificate';

  // Specify 'to' and 'from' addresses.
  $to = $variables['email'];
  $from = variable_get('site_mail', 'admin@example.com');

  $params = $variables;

  $language = language_default();

  $send = TRUE;

  $result = drupal_mail($module, $key, $to, $language, $params, $from, $send);  

}

Standard stuff; note that we get the recipient ($to) from the parameters we passed through.

This then relies on an implementation of hook_mail():

/**
 * Implements hook_mail().
 */
function gift_certificates_mail($key, &$message, $params) {
  global $user;
  global $base_url;

  $options = array(
    'langcode' => $message['language']->language,
  );

  switch ($key) {    
    case 'gift_certificate':

      $message['subject'] = t('@sender_name has sent you a @site-name gift certificate', array('@sender_name' => $params['sender_name'], '@site-name' => variable_get('site_name', 'Drupal')), $options);   

      $message['body'][] = t('@recipient_name,', array('@recipient_name' => $params['recipient_name']), $options);

      $message['body'][] = t('@sender_name has sent you a gift certificate for @coupon_amount to spend at @site-name.', array('@sender_name' => $params['sender_name'], '@coupon_amount' => $params['coupon_amount'], '@site-name' => variable_get('site_name', 'Drupal')), $options);

      $message['body'][] = t('To use your gift certificate, go to @site-url and enter the code: @coupon_code at checkout.', array('@coupon_code' => $params['coupon_code'], '@site-url' => $base_url), $options);

      if (strlen($params['personal_message'])) {
        $message['body'] = t('@sender_name says:', array('@sender_name' => $params['sender_name']), $options);           
        $message['body'][] = $params['personal_message'];
      }

      break;
  }
}

This simply takes the values we've collected - recipient name, sender name, personal message, coupon amount (which we've already formatted) and coupon code, as well as the site name and its URL - and builds the email to send to the coupon's recipient.

And that's it, basic gift certificate functionality. It's not quite there, as there are a few other considerations:

  • If an order contains only gift certificates, we probably shouldn't apply any shipping charges
  • You may need to enable Commerce No Payment in order to allow people to pay the full balance on an order using a gift certificate

...but, hopefully, this will get you started.