Version 4 > Developer Guides > E-Commerce Overview

E-Commerce Developer Overview

E-commerce is the process by which sales are negotiated and completed on-line. This guide explains the shopping, checkout, and payment process to assist developers in creating e-commerce-enabled features.

Shopping Carts, Invoices, and Receipts

All purchases are a collection of items that collectively comprise the purchase order. Each item has characteristics like name, description, quantity, size, cost, and accounting classifications, and is specified as a receivable_item record. Collectively, the purchase order has additional characteristics like the purchaser (account), and the purchase date, which are specified as a receivable record.

While the purchase is being assembled, the receivable record has a status of "inactive", and is essentially the shopping cart. While in this state, new items can be added to the receivable, as the customer wishes.

Once the purchase is finalized, the receivable record switches to an "active" status, and effectively becomes an invoice. It is no longer editable, and it becomes a debit on the customer's account.

Once payment has been received, the invoice become a receipt. No status change is needed, since the receivable is still a debit on the account; however, that debit is now offset by a matching payment/credit.

The E-commerce Process

Online shopping is broken into four phases:

  1. Shopping - the customer selects items they wish to purchase, and adds them to their cart
  2. Checkout - the customer provides identification information, and the purchase is finalized with the addition of taxes, discounts, and other adjustments
  3. Payment - the customer pays for the purchase
  4. Receipt - if payment is successful, the purchased items are processed and a receipt is delivered

1. Shopping

The e-commerce process begins as the customer navigates around the website, selecting items to purchase, and adding them to their shopping cart.

Every item that can be sold must implement its own "add to cart" function. The particulars will vary depending on the type of item being purchased. For example:

  • merchandise might require the selection of certain product options (such as colour) and a quantity
  • event tickets might require filling out a registration form
  • memberships may require selecting a membership type, and filling out an application form

Every type of sellable item must implement its own add-to-cart user interface flow. Once the necessary information has been collected/provided, the code can add the purchased item to the shopping cart using code like this:

my $cart = new Modules::Finance::Cart();
$out .= $cart->add(%item);

The add() method returns a short message to confirm the addition of the item to the cart. The hash %item contains a description of the purchased item, which is basically a receivable_item record, based on the purchase information that was collected. You can specify any of the following values (boldfaced items are strongly recommended, others are optional):

item short text description of the purchased item
description longer text description, or additional details about the purchased item
quantity number or amount of the item that was purchased (defaults to 1)
size size of the purchased item, normally used for calculating shipping costs
cost unit cost of the item (multiplied by the quantity to get the item subtotal)
acctcode_id sales code of the item (see GL Codes)
acctcode2 secondary sales subcode (see GL Codes)
acctcode3 tertiary sales subcode (see GL Codes)
parent item ID that this item is associated with; ties the two items together in the cart, so that if the parent item is deleted, this item will also be deleted along with it
uid user who entered this sale (optional, defaults to the customer)
note additional details about the item that are not shown on the invoice/receipt
objtype reference to a DB record that defines the sold item
objid reference to a DB record that defines the sold item
country country for tax purposes (optional, defaults to purchaser's country)
provstate province/state for tax purposes (optional, defaults to purchaser's province/state)

To display the current shopping cart, use:

$out .= $cart->show();

This is recommended after adding something to the cart, since it provides feedback about the state of the purchase, and gives the customer the option to check out or resume shopping.

If the customer does not immediately check out, they can always return to their shopping cart by going to the Pay module service page. It is a good idea to provide a shortcut to this page, such as with a shopping cart icon at the top of every page.

The shopping cart provides extra functions for managing the purchase, such as by:

  • altering quantities
  • removing items from the cart

2. Checkout

When the customer is done shopping, they can proceed to checkout by clicking on the "Checkout" button. The checkout phase is comprised of the following sub-steps:

  1. provide purchaser's contact information (eg. billing and/or shipping addresses)
  2. accept any coupon codes the purchaser may wish to redeem
  3. calculate any surcharges (for example, taxes, shipping costs, etc.) and add them to the total
  4. present the final invoice total to the customer for confirmation

If possible, the checkout will skip any of these steps that are not needed, in order to speed up the overall process. For instance, if the customer is already known to the system, they can skip step 1 since we already have their contact information.

To accept the final invoice total and complete the sale, the customer selects their payment method using one of the buttons: 

  • Pay now - takes the customer to an e-commerce payment screen to pay immediately by credit cart or other e-commerce method
  • Pay later by invoice - if the website allows it, the customer can pay using an offline method such as check/cheque or EFT
  • Complete order - if the invoice total is $0, the customer will not be asked to pay, but they must still confirm the final order

3. Payment

For e-commerce payments, the customer will be directed to a payment screen that is generated by the website's payment processor, where they can provide their credit card details to complete the payment. The particulars of this step will vary between payment processors. If this payment fails, they can cancel the payment, which will return them to their shopping cart. For there they can return to shopping, or attempt to check out again using a different payment method.

For off-line payments, the customer can make the payment using their preferred method. When payment is received, the administrator will need to manually record receipt of the payment using the Payments module. Best practice is to record the invoice # being paid, to ensure the specific purchase is properly processed.

4. Receipt

When payment is received, an email receipt is sent to the customer to confirm the completion of the sale. This receipt is e-mailed to the billing address email that the customer provided at checkout. Note that this is not necessarily the same as the customer's regular email address that they use for their day-to-day website communications. For e-commerce payments, the receipt is also displayed on-screen.

The purchased items are then processed, where necessary. For example, registrations are marked as confirmed, membership applications are accepted, and so forth. The details will vary depending on what sort of items were purchased. In some cases, this may trigger additional email notifications, such as event confirmations, membership welcome messages, and so on.

Purchased Objects

If your invoice line item specifies an object type (objtype) and object ID (objid), those are taken to be a database table and record ID that describes the purchased item. 

For example, if you purchase ticket 4567 to an event, you can add the item to the cart with the following settings:

objtype = ticket
objid = 4567

and that invoice line item will thenceforth be "attached" to that ticket. In cases where the purchased item is a content object (for example, products, profiles, job postings), the object type can be either "content" or the more specific content type (eg. "product", "profile", etc.).

If an object type and ID are defined for a purchased item, you can instantiate the purchased object itself using:

my $obj = $item->purchased_object();

In the above ticket example, this would return a Modules::Registration::Ticket object, for ticket ID 4567.

The shopping cart will attempt to communicate with the purchased object as it proceeds through the sales process. The object class can choose to implement any of the following calls, if it needs to be responsive to the mechanics of the sales flow:

  • sale_select($item) called when an object is added to the cart
  • sale_quantity($item,$old_quantity,$new_quantity) called when the quantity of an item is altered
  • sale_delete($item) called when an object is removed from the cart
  • sale_validate($item,$checkout) called to verify that an object is permitted in the cart; $checkout is true if the user is checking out, false if simply viewing their cart
  • sale_activate($item) called when the sale is closed and the invoice is activated
  • sale_complete($item) called when the invoice is paid in full
  • sale_note($item) will be used to add special notes to the receipt (see Purchase Messages, below)

In all cases, $item is the Modules::Finance::ReceivableItem object that represents the invoice line item.

Example: For the ticket example above, when the ticket is added to the cart, it can be marked as temporarily held for sale so that it does not get sold to someone else while still in the cart, but if the ticket is removed from the cart, it can be released for re-sale to someone else. If the customer checks out and chooses to pay later by invoice, the ticket can be updated to permanently reserved for the customer. If the customer pays immediately, the ticket can be marked as sold and confirmed.

Note that these are all optional calls; they can be ignored if you have no interest in interacting with the purchase flow.

E-commerce Setup

Configuration

The local e-commerce configurations are kept in the Pay.conf configuration file. Many aspects of the e-commerce experience can be customized with settings in this file, including various headings, messages, and buttons. The default file includes many settings that can be uncommented and ustomized accordingly, including settings to manage:

  • your e-commerce payment processor account
  • formatting of the shopping cart
  • what sort of contact info should be collected at checkout
  • which sales codes are fulfilled automatically, and which require manual fulfillment
  • various headings and messages that are through the sales process
  • receipts and other email notifications
  • defaults for the admin control panels
  • cart help and receipt annotations

Payment Processor

Set your preferred payment processor using the config setting "gateway", for example:

gateway = AuthorizeNet

And then set your e-commerce merchant account settings using the gateway name as a configuration category, for example: 

AuthorizeNet.transaction_key = 1234567890
AuthorizeNet.api_key = ABCDEFGHIJK

Your payment gateway must match one of the plug-in payment gateways in the Modules/Pay subdirectory, or you must create your own payment processor plug-in and install it in this location.

If you do not have your merchant account setup yet, you can use the Test gateway for a simple e-commerce payment simulator. If you use the Test gateway, make sure the pay.cgi testing script is installed in your cgi-bin directory.

Persistent Data Store

You must enable the persistent data store to track state and user sessions, since otherwise HTTP is a stateless protocol and will not remember what is in your cart. The store configurations are kept in cgi/Local.pm, and the utility for starting and stopping the store is in bin/store.pl.

When the persistent data store is enabled, adding something to the cart will add an invoice # to the user's session. The user will receive a session ID cookie so that we can keep track of this session until their shopping trip is done. If they do not accept this cookie, or if they have firewalls or security software that blocks cookies from your website, then we will not be able to recognize the returning customer on subsequent page views, and will not be able to reconnect them to their shopping cart.

In any case, the session will persist for up to 1 hour of idle time before the session manager concludes that the customer abandoned their purchase and discards it. As long as the customer continues to interact with the website, the session will be refreshed, so in principle they can take as long as they like to shop, as long as they don't let their session sit idle for more than 1 hour.

Shopping Cart Shortcut

If the customer clicks away from their purchase flow for any reason, they can lose contact with their purchase, and it may not be obvious how to get back to where they were, or how to resume. 

Best practice is to include a shortcut to the shopping cart somewhere at the top of the navigation. It is common to indicate this link using a small cart icon or button. This link should direct to the Payment module service page, which displays the current shopping cart by default. From there, the cart will generate a "continue shopping" link to help the customer return to where they were in their purchase flow.

The shopping cart will maintain a cookie called "cart_contents" which contains the current number of items in the cart. The value of this cookie will go up and down as items are added to and removed from the cart. It can be helpful to display this value up front to make it more apparent that the customer has items waiting in the cart to prompt them to checkout. To do this, you can use a short snippet of Javascript to display the cart contents as a number or badge beside your cart shortcut link. For example:

var ncart;
var ca = document.cookie.split(';');
for(var i = 0; i < ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') { c = c.substring(1); }
    if (c.indexOf("cart_contents=") == 0) { ncart = c.substring(14, c.length); }
}
if (ncart) { document.write("("+ncart+")"); }

Note that if the customer abandons their purchase, the cart will eventually (in about an hour) be discarded, but the cart_contents cookie may persist longer than that, suggesting there are still items in the cart. The cookie will be updated the next time the customer views their cart.

Continue Shopping Link

The customer can manage their own cart contents, for example by removing items or changing quantities. Cart operations like these redirect the customer to the shopping cart service page, which might interrupt the purchase flow that they were previously in the middle of. The shopping cart provides a "continue shopping" link to return the customer to the original purchase flow so that they can resume where they left off with their purchase.

The destination of the continue shopping link is taken from one of the following, in this order:

  1. A custom Pay_continue_shopping handler can be installed to provide a custom override to the following rules
  2. $session{continue_shopping_url} - any code that provides a shopping experience can set the continue_shopping_url in the customer's session, which will link them back to whatever URL the code deems appropriate.
  3. The continue_shopping_url configuration setting (in Pay.conf) can specify a fixed URL to always redirect to.
  4. If none of the above are defined, the cart will redirect to the HTTP_REFERER from the last time the customer added something to their cart.

#2 is the recommended way for shopping code to manage the shopping flow. Simply update the continue_shopping_url session variable as needed.

Sales Annotations

Shopping carts, invoices, and receipts can be augmented with special annotations to provide extra information to customers.

Shopping Cart Help

Two special content objects can be added to your system to provide additional help for customers:

ShoppingCartHelp - this content is displayed on all shopping cart views, and can communicate any special information on how purchases can be selected and paid for on your website.

ShoppingCartHelpEmpty - this is displayed in the case where the visitor visits their shopping cart, but the cart is empty. It can include some links to help the customer get started with their purchase.

In addition to these, you can provide special payment instructions that are shown when the customer begins checkout. In the Pay.conf configuration file, include those instructions in a config setting called payment_method_help. For example:

payment_method_help = Note that we accept e-commerce payments by Visa or Mastercard only.

Content-sensitive instructions can automatically be displayed with the shopping cart, based on the items that are currently in the cart. These are set up using the cart_help configuration settings in the Pay.conf configuration file.

To add a message to this, you must know the object type and object ID of the purchase. You can specify the purchased object itself, or you can specify one of the encapsulating records in the data hierarchy. For example, a registration normally involves purchasing a ticket, and the object type will be "ticket" and the object ID will be the ticket #. However, tickets all belong to a fee, and fees all belong to an event, so it is normally more useful to specify messages that apply to all tickets in the fee or event.

Add your messages to Pay.conf in the following format:

cart_help.OBJTYPE.OBJID.message = text to display if object is in cart (can include HTML markup)

Example:

# show this message to registrants in event #1234
cart_help.event.1234.message = Note that refunds can only be given if cancellation notice is received at least 14 days before the conference. 

Note that you can specify * as the OBJID, and the message will apply to *any* object of that type.

You can control who sees your messages using the following settings:

cart_help.OBJTYPE.OBJID.access = minimum access level to view this message
cart_help.OBJTYPE.OBJID.maxaccess = maximum access level to view this message

Example:

# advise customer of membership discount
cart_help.event.1234.message = $100 off your membership renewal if you also renew right now.
# only show this message to active members
cart_help.event.1234.access = 2

You can also block messages based on having other items in the cart. Use the following format:

cart_help.OBJTYPE.OBJID.unless.OTHER-OBJTYPE = [ array of OTHER-OBJID ]

If there are any items in the cart matching OTHER-OBJTYPE and OTHER-OBJID, then the original message will be suppressed.

Example:

# stop showing membership discount message if they have selected renewal in the following 2 membership types
cart_help.event.1234.unless.membership_type += 555
cart_help.event.1234.unless.membership_type += 556 

Purchase Messages

Similar to cart help, completed invoices and receipts can also be annotated with special instructions, notes, or other messages based on the items purchased. Instead of providing help for the shopper, it provides information about completed purchases.

Unspecific messages for certain purchase types can be specified using Pay.conf configuration settings like

message.acctcode.TYPE = message text

Where type is a general accounting code, as specified in your Billing Adjustments module. For example, 

message.acctcode.DONATION = The Foo Foundation is a 501(c)(3) non-profit. Please keep this receipt for tax purposes.

Messages that are specific to individual purposes can be generated by the sale_note() method in any purchasable object class. If a purchased object returns a message to this call, it will also be appended to invoices and receipts. These messages can provide information specific to this purchase, such as links to online courses or webinars, or a link to a personal membership profile. 

Receipt and Invoice formatting

Receipts and invoices have a default format that looks approximately like this: 

[header goes here]

Receipt 12345

Your Organization Name

Sold to: Customer Name
Customer Billing Address 

Jan 31, 2022

QuantityDescriptionUnit PriceSubtotal
1 First Item 5.00 5.00
2 Second Item 0.75 1.50
  Tax (5%)   0.33
  TOTAL   6.83 

Paid by e-commerce on Jan 31, 2022.

[Additional purchase messages go here]

[footer goes here]

Note that:

  • the header and footer are not defined by default, but you can define them (see below)
  • purchase messages may or may not be included, depending on your configurations and the items in the sale

If this is format sufficient for your needs, you do not need to do anything further. If the only adjustment you require is to wrap up the above with a header and footer (for instance, a company logo on top, and a refund policy link at the bottom) then you can define two content objects, ReceivableHead and ReceivableFoot, which will be used to sandwich the above.

If you require more control, or wish to change the layout of the receipt internals, then you can set up your own custom template(s) for receipts and/or invoices. Use the following merge codes, enclosed in [ [double brackets] ], in your template to place particular pieces of information:

  • name - for example, "Invoice 4567"
  • description - (optional) if any special invoice identification has been specified in receivable.invoice
  • seller - seller name, ie. your organization
  • buyer - complete buyer info, including name and contact info
  • buyer_name - just the buyer's account name
  • buyer_contact - the buyer's billing address
  • buyer_fullcontact - the buyer's complete contact info (address, phone, email, etc)
  • receivable - the receivable details
  • payment - payment info/notes
  • notes - other notes about the purchase
  • footer - general information about payments, refunds, policies, tax #s
  • number - the receivable ID
  • invoice_number - custom invoice #
  • name - combination of number+invoice_number
  • date - the purchase date
  • uid - user # associated with the account
  • acct_id - account id
  • acct_code - account code (alternate account id)
  • taxno - account's tax exemption number

You can install your special template under one or more of the following content names:

  • receipt_template - used for receipts only
  • invoice_template - used for invoices only
  • receivable_template - used for both invoices or receipts

It is best to hard-code full static image URLs in the HTML, because receipts and invoices are often viewed outside your website, such as in email notifications.