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.
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.
Online shopping is broken into four phases:
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:
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:
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:
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:
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.
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.
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 cartsale_quantity($item,$old_quantity,$new_quantity)
called when the quantity of an item is alteredsale_delete($item)
called when an object is removed from the cartsale_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 cartsale_activate($item)
called when the sale is closed and the invoice is activatedsale_complete($item)
called when the invoice is paid in fullsale_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.
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:
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.
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.
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.
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:
$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.#2 is the recommended way for shopping code to manage the shopping flow. Simply update the continue_shopping_url session variable as needed.
Shopping carts, invoices, and receipts can be augmented with special annotations to provide extra information to customers.
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
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.
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 AddressJan 31, 2022
Quantity Description Unit Price Subtotal 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:
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:
You can install your special template under one or more of the following content names:
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.