Skip to main content

How To Configure Customer Order Communication Scripts

Introduction

The purpose of this guide is to show how to configure Customer Order Communication Scripts within the Enactor Estate Manager.

Communication Scripts are used to generate rich text emails and SMS messages sent to customers. They are used to send:

  • Order confirmations
  • Order notifications, e.g. dispatch or refund notifications

Prerequisites

Resources

Before starting to maintain a communication script, you should have the following resources in place:

  • Enactor Estate Manager
  • Enactor Order Manager
  • Standard Configuration, including:
    • Base configuration
    • Order Management configuration

Prior Training/Experience

You should be familiar with the following:

  • Estate Manager configuration
  • Data broadcasting
  • Order Management operations

Configuration

Communication Script Maintenance

Communication scripts are maintained using the Communication Script Maintenance application.

Navigate to Communication Script Maintenance using the Search or the path: Configuration > CRM > Communication Script Maintenance

Selecting the menu option takes the user to the Communication Script select page. An example is shown below, with the results filtered where the name contains "store". The four scripts shown are the order confirmation and completion SMS and Email templates for an order to be collected at a Store.

Communication Script Maintenance select page filtered by name containing 'store', showing four order confirmation and completion templates

The Communication Script select page lists all configured scripts. Use the name filter to locate specific scripts. The four results shown are order confirmation and completion templates for store collection orders.

General Tab

Editing the "Order Confirmation (Store)" communication script takes the user to the General tab. If the communication script is relevant to a CRM campaign, the campaign can be selected from the drop-down.

Communication Script Maintenance General tab for the Order Confirmation (Store) script, showing CRM campaign selection

The General tab displays the script name and allows association with a CRM campaign via the drop-down.

Content Tab

The next tab, Content, allows the user to edit the content of the message. This communication script has been configured with the following properties:

ConfigurationDescription
Content TypeThe email will be sent with a content type of Rich Text (HTML). The alternative is Plain Text.
Template TypeThe template engine that will be used to generate the content is Apache FreeMarker. Other options are No Template and Simple Token Replacement.
Editor TypeChoose which editor is used in the Maintenance page to edit the content. The options are Plain Text and Rich Text (HTML). Both editors display the placeholders for the dynamic data elements. The HTML editor will render the HTML static content.

Communication Script Maintenance Content tab showing the Plain Text editor with FreeMarker template source code

The Content tab in Plain Text editor mode displays the raw FreeMarker template source. Set Content Type to Rich Text (HTML) and Template Type to Apache FreeMarker.

Communication Script Maintenance Content tab showing the Rich Text HTML editor with rendered preview of the template

The Rich Text (HTML) editor renders the HTML static content while still displaying the FreeMarker placeholders for dynamic data elements.

Attachments Tab

The final tab, Attachments, allows external files to be sent with the email. The attachments can be referenced within the FreeMarker script using the cid: notation, e.g.

<a href="#"><img alt="Facebook" src="cid:email_facebook.png" width="30" /></a>
note

It is not guaranteed that inline attachments will be displayed if the email client does not support the cid: notation.

A complete example of an order confirmation email is shown in the appendices to this document.

The email rendered by the example styled FreeMarker template will be similar to that shown below.

Rendered order confirmation email showing customer greeting, order items table with images and prices, delivery details, and branded footer

An example of the rendered order confirmation email generated by the FreeMarker template. The email includes a customer greeting, order items with images and prices, delivery or collection details, and branded footer.

Apache FreeMarker

Introduction

The communication script content is written using Apache's FreeMarker language. From the FreeMarker website, https://freemarker.apache.org, is this description:

Apache FreeMarker is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, source code, etc.) based on templates and changing data. Templates are written in the FreeMarker Template Language (FTL), which is a simple, specialized language (not a full-blown programming language like PHP). Usually, a general-purpose programming language (like Java) is used to prepare the data (issue database queries, do business calculations). Then, Apache FreeMarker displays that prepared data using templates. In the template you are focusing on how to present the data, and outside the template you are focusing on what data to present.

FreeMarker template engine diagram showing template plus data model producing output

The FreeMarker template engine combines a template with a data model to produce the final output text.

Enactor's implementation of FreeMarker performs exactly as described above. A set of Java objects representing the Order plus the order's Items, Fulfilments and Payments are populated and passed to the template engine along with the Communication Script content described above.

The output of the document generation from the static FreeMarker content, combined with dynamic Java data, is an HTML email body.

tip

A full FreeMarker reference and tutorial is available at https://freemarker.apache.org/docs/dgui.html.


Integration with Customer Orders

Java Objects

The Java objects available within the FreeMarker template engine are listed below and then described in more detail. Example JSON representations of the objects are included in the Appendices at the end of this document.

note

Not all data is available in all cases. For example, if the notification is being generated for dispatch of one fulfilment, then the list of fulfilments will be limited to one.

  • Customer Order Header
  • List of Order Items
  • List of Fulfilments
  • List of Payments
  • Customer
  • Destination Location
  • Products Map
  • Product Images Map
  • Sizes Map
  • Style Attributes Map
  • Colours Map
  • Locations Map

The sections below describe a subset of the properties for each of the types listed above.


Customer Order Header

This object holds general properties of an order.

Object reference: customerOrderHeader

Property nameJava TypeComments
customerOrderIdStringUnique order ID.
createdByUserKey.userIdStringUser ID of the POS user during order take on.
createdAtLocationKey.locationIdStringLocation ID of the POS during order take on.
createdDateDateTimeWithTimeZoneAdapterThe date the order was created. If order take on was at the POS, this is the date and time of the transaction. The Java type is an Enactor extension to Java Calendar class. See below for a formatting example.
customerOrderTypeIdStringThe order type ID of the order. Within Enactor there may be orders of different types, e.g. one order type per channel. Displaying the customer order type in an email to the customer may not be useful, but using the order type to conditionally display information may be.
customerTitleStringThe title captured for the customer.
customerFirstNameStringThe first name of the customer who created the order.
customerPostcodeStringThe customer's postcode.
customerSurnameStringThe customer's surname.
customerNumberStringThe customer's number, only set if there is a customer record in Enactor.
currencyKey.currencyIdStringCurrency ID of the values and prices in the order. Typically used by the formatter to correctly format prices. See below for an example.
orderValueLongThe total value of the order, in pence or equivalent, stored as a long value. Use the formatter with the currency ID to format for output. See below for an example.
statusStringThe status of the order - SUBMITTED, CANCELLED, AVAILABLE, COMPLETED, etc.
adjustmentsListA list of adjustments relating to the order, e.g. discounts, promotions, charges, etc. See below for details.

Customer Order Items

This list holds the list of items contained in the order. The properties for one item line in the order are shown below.

Object reference: customerOrderItemList

Property nameJava TypeComments
fulfilmentIdIntegerThe ID of the fulfilment for this line item.
lineNumberIntegerThe line number of this order item. Guaranteed to be unique in the order.
productKey.productIdStringThe ID of the ordered product. Can be used in conjunction with the Products Map and the Product Images Map to output the product description, image URL and other details.
orderedQtyIntegerThe quantity ordered for this line item. If the line has been split, for example in the case of two fulfilments for the same product, this may not be the full quantity of this product in this order.
shippedQtyIntegerThe quantity shipped for this line item, which for partial dispatches may be different from the ordered quantity.
effectiveNetValueLongThe price paid by the customer for one ordered item. Discounts and promotions have been applied to the unit price. Multiply this property by the ordered or shipped quantity to calculate the line total.
unitPriceLongThe original price of the product, without any discounts or promotions applied.
statusStringThe status of the item - SUBMITTED, CANCELLED, AVAILABLE, COMPLETED, etc.
adjustmentsListA list of adjustments relating to the order item, e.g. discounts, promotions, charges, etc. See below for details.

Customer Order Adjustment

This object holds the properties for any adjustments to an order or an order item.

Object reference: customerOrderItem.adjustments[n] or customerOrderHeader.adjustments[n] where n is the index of the adjustment. Alternatively use the iterator expression <#list customerOrderItem.adjustments as customerOrderItemAdjustment>.

Property nameJava TypeComments
adjustmentTypeStringThe type of the adjustment, e.g. DISCOUNT, PROMOTION, SHIPPING, DELIVERY, etc.
adjustmentDetailStringThe details of any adjustment, e.g. the reason ID of the discount or promotion or the product ID of a delivery product.
descriptionStringThe description of the adjustment, e.g. the reason description for a promotion.
amountLongThe amount, in pence, of the adjustment.
percentageFloatIf the adjustment was a percentage of the item's value, the percentage adjusted.

Customer Order Fulfilments

This list holds the list of fulfilments for the order items. The properties for one fulfilment in the order are shown below.

Object reference: customerOrderFulfilmentList

Property nameJava TypeComments
fulfilmentIdIntegerThe ID of the fulfilment for this line item. Using the fulfilment ID on the customer order item allows the items to be grouped by fulfilment and/or destination type.
sourceTypeStringIf the item is being sourced from a supplier, then 'supplierFulfilment', otherwise 'locationStockFulfilment' for fulfilment from an internal location.
sourceIdStringIn the case of location source type, the ID of the fulfilment location.
destinationTypeStringThe type of destination of the fulfilment, either 'COLLECTION' for click and collect or 'ADDRESS' for home delivery.
destinationLocationKey.locationIdStringIf the destination type is Collection, then the ID of the collection location. The details of the collection location can be displayed using the Locations Map, described below. If the destination type is Address, then the destination address will be populated.
destinationName.titleStringIf the destination type is Address, then the destination name will be populated.
destinationName.forenameStringIf the destination type is Address, then the destination name will be populated.
destinationName.surnameStringIf the destination type is Address, then the destination name will be populated.
deliveryChargeLongDepending on the configuration used, the delivery type, charge and product ID may be populated. The delivery charge can be formatted using the order's currency, described below.
deliveryChargeProductIdStringThe product ID for the delivery charge.
deliveryTypeKey.deliveryTypeIdStringThe delivery type ID.
trackingDetails.shippingMethodDescriptionStringDepending on the configuration used, the delivery shipping provider and method may be populated.
trackingDetails.shippingMethodIdStringThe shipping method ID.
trackingDetails.shippingProviderDescriptionStringThe shipping provider description.
trackingDetails.shippingProviderIdStringThe shipping provider ID.
trackingDetails.trackingReferenceStringIf tracking details were communicated from the fulfilment system, the tracking reference will be populated.
trackingDetails.trackingUrlStringIf tracking details were communicated from the fulfilment system, the tracking URL will be populated.

Customer Order Payments

This list holds the list of payments for the order. The properties for one payment in the order are shown below.

Object reference: customerOrderPaymentList

Property nameJava TypeComments
paymentNumberIntegerThe number of the payment. Guaranteed to be unique for this order.
paymentTypeStringThe type of the payment, typically 'customerOrderCashPayment' or 'customerOrderCardPayment'.
amountLongThe amount of this payment. Note that the amounts are always positive. The isRefund boolean below will signify that the payment was a refund back to the customer.
tenderKey.tenderIdStringThe tender ID of the payment.
currencyKey.currencyIdStringThe currency of this payment.
foreignValueLongIf the payment was in a foreign currency, the foreign value will be populated.
exchangeRateFloatIf the payment was in a foreign currency, the exchange rate will be populated.
statusStringThe status of the payment - COMMITTED, PENDING, REFUNDED, etc.
isRefundBooleanWhether this payment is a refund. Note that the amounts are always positive.
refundAvailableLongIf this payment is not a refund, the amount of the payment that is available to be refunded. See the example in the next section on using Java boolean properties.

Customer

This object holds the customer record for the order. It will be populated only if Customers are retrieved during order take on.

note

Most, if not all, of the customer properties will also be present in the order header, which may make this object superfluous.

Object reference: customer

Property nameJava TypeComments
customerNumberStringThe number of the customer. Guaranteed to be unique within the Estate.
nationalityStringThe nationality of the customer.
customerName.titleStringThe title of the customer.
customerName.forenameStringThe forename of the customer.
customerName.surnameStringThe surname of the customer.

Destination Location

The destination location is the same object used in the locations map, described above for a fulfilment location.

Object references: destinationLocation, locationsMap

Property nameJava TypeComments
descriptionStringLocation description.
locationIdStringThe ID of the location.
locationAddress.street1StringLocation address properties.
locationAddress.street2StringLocation address properties.
locationAddress.street3StringLocation address properties.
locationAddress.townStringLocation address properties.
locationAddress.countyStringLocation address properties.
locationAddress.postCodeStringLocation address properties.
locationAddress.phone1StringLocation address properties.

Products Map

This map holds details of the products in the order. The full product record is in the map, meaning that every property of the product is available. A small sample of the properties are shown below.

note

Much of the product record will be present elsewhere in the order, e.g. product price. The products map exists only as an extended repository of product information.

The map is keyed by product ID, obtained for example from the product ID in an order item:

${productsMap[customerOrderItem.productKey.productId].productDescription.string}

Object reference: productsMap

Property nameJava TypeComments
productDescription.stringStringThe default description for the product.
brandKey.groupIdStringThe brand for the product.
taxGroupKey.taxGroupIdStringThe tax group for the product.
posDetails.customerAgeRestrictionIntegerThe customer age restriction for the product.

Product Images Map

This map contains URLs of product images, keyed by product ID. Use any product ID to use the URL from the map in the FreeMarker output.

See the example in the Template Language Examples section below for more information.

Object reference: productImageURLsMap


Sizes Map

This map contains size objects, keyed by product ID. Use any product ID to retrieve the size of the product from the map.

Object reference: sizeMap

Property nameJava TypeComments
sizeIdStringThe ID of the size.
sizeRangeIdStringThe ID of the size range.
descriptionStringThe size's description. Typically this property will be used to output the size's description, e.g. "Extra Large", rather than the size ID, e.g. "MENS_TROUSERS_XL", which is held in the order item record.

Style Attributes Map

This map contains style attribute objects, keyed by product ID. Use any product ID to retrieve the style attributes of the product from the map.

Object reference: styleAttributesMap

Property nameJava TypeComments
attributeValueStringThe value of the style attribute.
styleAttribute.optionSetKey.typeStringThe type of the option set of this style attribute.

Colours Map

This map contains colour objects, keyed by product ID. Use any product ID to retrieve the colour of the product from the map.

Object reference: colourMap

Property nameJava TypeComments
colourIdStringThe ID of the colour.
colourRangeIdStringThe ID of the colour range.
descriptionStringThe colour's description. Typically this property will be used to output the colour's description, e.g. "Red", rather than the colour ID, e.g. "WOMENS_DRESSES_RED", which is held in the order item record.

Locations Map

This map contains location objects, keyed by fulfilment ID. Use the fulfilment ID to retrieve the location of the fulfilment from the map.

Object reference: locationMap

A sample set of properties of the location object are listed below. The whole location object is contained in the map, but typically the name and address are used in the output.

Property nameJava TypeComments
locationIdStringThe ID of the location.
descriptionStringThe name of the location.
locationAddress.street1StringThe address and phone number of the location.
locationAddress.street2StringThe address and phone number of the location.
locationAddress.street3StringThe address and phone number of the location.
locationAddress.townStringThe address and phone number of the location.
locationAddress.countyStringThe address and phone number of the location.
locationAddress.postCodeStringThe address and phone number of the location.
locationAddress.phone1StringThe address and phone number of the location.

Template Language Examples

This section highlights the structures used in the example templates from this document. The examples below can be seen in the screenshot of the generated email in the section above.

To replace dynamic text, use FreeMarker template language elements.


Example 1 - Displaying the customer's first name and order ID

The dynamic Java data is wrapped in ${...} and is converted to static data by the FreeMarker engine.

<p>Dear <strong>${customerOrderHeader.customerFirstName}</strong></p>
<p>Thank you for your order. Information about your order <strong>#${customerOrderHeader.customerOrderId}</strong> appears below.</p>

Example 2 - Iterating the list of items

Use the <#list ...> structure:

<#list customerOrderItemList as customerOrderItem>

The named variable after the as keyword can be used within the context of the list as in Example 1:

${customerOrderItem.orderedQty}

Example 3 - Display information from a map structure

Maps of data are passed to the FreeMarker engine, one being a map of product image URLs keyed by product ID. Use the [...] structure combined with ${...} to retrieve values from a map.

<img src=${productImageURLsMap[customerOrderItem.productKey.productId]}>

Use the locationMap object to output the description of the location rather than the location's ID:

${locationMap[customerOrderHeader.createdAtLocationKey.locationId?string].description}

Example 4 - Use a formatter to format a price

Enactor provides extensions to FreeMarker built-ins. One of these extensions can be used to format prices. Use the freeMarkerFormatter within the ${...} structure. The value of an order is multiplied by the quantity to get the total price, which is formatted using the currency ID from the order.

${freeMarkerFormatter.formatCurrency((customerOrderItem.orderedQty * customerOrderItem.effectiveNetValue),customerOrderHeader.currencyKey.currencyId)}

Use the orderValue property to output the total value of the order:

${freeMarkerFormatter.formatCurrency(customerOrderHeader.orderValue,customerOrderHeader.currencyKey.currencyId)}

Example 5 - Use a formatter to format a date

Use the freeMarkerFormatter within the ${...} structure as for the currency format. Pass the date format in a Java SimpleDateFormat format as the second parameter to the method.

${freeMarkerFormatter.formatDate(customerOrderHeader.createdDate, "dd MMM yyyy hh:mm:ss")}

Example 6 - Conditional output

The example scripts in this document use a nested iterator with a conditional <#if> to group the items in the order by fulfilment method, for example if a customer has opted for both home delivery and store collection in the same order.

<#list customerOrderFulfilmentList as customerOrderFulfilment>
<#list customerOrderItemList as customerOrderItem>
<#if customerOrderItem.fulfilmentId == customerOrderFulfilment.fulfilmentId>
-- display this item's detail here
</#if>
</#list>
</#list>

Example 7 - Conditional output revisited

The FreeMarker built-in function has_content can be used within an <#if> to determine if the Java object has data, i.e. is not null and not "empty".

This sample will only output Street 2 of the destination address if it is populated:

<#if (customerOrderFulfilment.destinationAddress.street2)?has_content >
${customerOrderFulfilment.destinationAddress.street2}<br/>
</#if>

Example 8 - Using Java boolean properties

The usual Java "toString" operation does not work in FreeMarker. Attempting to output the value of a boolean property in a FreeMarker template will produce an empty string. Using the ?string built-in will convert the boolean value to the strings specified, or use the <#if> construction to test the value and output appropriately.

<#if (customerOrderPayment.isRefund)>Refund payment</#if>

${customerOrderPayment.isRefund?string("Refund payment", "Not refund payment")}

Testing Communication Scripts

Changes made to communication scripts using the Communication Script Maintenance are applied immediately. As soon as the communication script has successfully been saved, any order communications generated will use the new script layout.

The Business Process Administration page on Estate Manager is available to administrators with suitable privileges and can be used to re-run individual business process tasks. This will allow testing of the generation of Communication Scripts without the need to complete the take on of a new order on the POS and wait for processing to complete.

Navigate to Business Process Administration using the Search or the path: Administration > Business Process > Business Process Administration

The Business Process Administration page lists all business process instances. To filter the page to show the single business process controlling the customer order that can be used for testing, go to Operations > CRM > Customer Orders and view a suitable order.

Select and copy the Internal Reference field from the customer order, shown below, and return back to the list of business processes.

Customer Order view showing the Internal Reference field to be copied for business process filtering

Copy the Internal Reference field from the customer order. This value is used to filter the Business Process Administration list to find the corresponding business process instance.

Paste the copied Internal Reference to the Instance ID filter and press Apply Filters. This will display a single customer order business process instance. Edit the business process by selecting the Edit pencil icon.

Business Process Administration list filtered by Instance ID showing a single customer order business process with Edit icon

Paste the Internal Reference into the Instance ID filter and press Apply Filters. Select the Edit pencil icon on the filtered result to view the business process tasks.

To fully verify and confirm that the business process instance and correct customer order are linked, select the View icon to display a pop-up dialog containing the properties of that particular task. One of the properties will be the CustomerOrderId, which will match the order ID used to copy the Internal Reference.

Business Process Administration task properties dialog showing CustomerOrderId matching the customer order

Select the View icon to display task properties. Verify that the CustomerOrderId matches the order ID used to find the Internal Reference.

Selecting the Rerun button on the Send Order Confirmation task will cause the business process engine to "undo" or compensate all subsequent tasks and then rerun the Send Order Confirmation task, continuing the business process from the compensated task.

The screenshot below shows that the original task has been Undone and another rerun task generated. The Send Order Confirmation task will cause the order confirmation email to be sent to the customer, in the process regenerating the email contents from the updated communication script.

Business Process Administration task list showing the original task as Undone and a new rerun task for Send Order Confirmation

After selecting Rerun, the original task is marked as Undone and a new task is generated. The order confirmation email is re-sent using the updated communication script content.
tip

Using the process described above, the email can be regenerated from the updated communication scripts many times in quick succession, without having to create a new order and wait for processing.


Appendix 1 - Simplified FreeMarker Order Confirmation

Example FreeMarker script for Order Confirmation Email, simplified.

<html xmlns="http://www.w3.org/1999/xhtml">

<h1 style="color: #5e9ca0;">Thank you for your order</h1>
<h2 style="color: #2e6c80;">Your order has been submitted. You will receive an email when it is ready for collection.</h2>

<p>Dear <strong>${customerOrderHeader.customerFirstName}</strong><br />

<span>Thank you for your order. Information about your order <strong>#${customerOrderHeader.customerOrderId}</strong> appears below. <br/>
If you need to get in touch with us about your order please contact us at: </span><a href="mailto:customerservices@yourcompany.co.uk"><span>customerservices@yourcompany.co.uk</span></a></td>
</p>

<p>
<table>
<tr bgcolor="#efedec">
<th>ITEM DESCRIPTION</th>
<th>QUANTITY</th>
<th>PRICE</th>
</tr>
<#list customerOrderFulfilmentList as customerOrderFulfilment>
<#list customerOrderItemList as customerOrderItem>
<#if customerOrderItem.fulfilmentId == customerOrderFulfilment.fulfilmentId>
<tr>
<td><img src=${productImageURLsMap[customerOrderItem.productKey.productId]} width="57" height="57" alt=${productsMap[customerOrderItem.productKey.productId].productDescription.string}/></td>
<td>${productsMap[customerOrderItem.productKey.productId].productDescription.string}</td>
<td>${customerOrderItem.orderedQty}</td>
<td>${freeMarkerFormatter.formatCurrency((customerOrderItem.orderedQty * customerOrderItem.effectiveNetValue),customerOrderHeader.currencyKey.currencyId)}</td>
</tr>
</#if>
</#list>
<tr>
<td colspan="2">&nbsp;</td>
<td colspan="2">Delivery: ${freeMarkerFormatter.formatCurrency(customerOrderFulfilment.deliveryCharge,customerOrderHeader.currencyKey.currencyId)}</td>
</tr>
<#if customerOrderFulfilment.destinationType =="ADDRESS">
<tr><td colspan="4" height="25">&nbsp;</td></tr>
<tr bgcolor="#efedec">
<td>HOME DELIVERY DETAILS</span></td>
</tr>
<tr style="border-bottom: 2px solid #efedec;">
<td>
<p>
<#if (customerOrderFulfilment.destinationAddress.street1)?has_content >
${customerOrderFulfilment.destinationAddress.street1}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.street2)?has_content >
${customerOrderFulfilment.destinationAddress.street2}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.street3)?has_content >
${customerOrderFulfilment.destinationAddress.street3}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.town)?has_content >
${customerOrderFulfilment.destinationAddress.town}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.county)?has_content >
${customerOrderFulfilment.destinationAddress.county}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.postCode)?has_content >
${customerOrderFulfilment.destinationAddress.postCode}<br/>
</#if>
<#if (customerOrderFulfilment.destinationAddress.phone1)?has_content >
${customerOrderFulfilment.destinationAddress.phone1}<br/>
</#if>
</p>
</tr>
</#if>
<#if customerOrderFulfilment.destinationType =="COLLECTION">
<tr><td colspan="4" height="25">&nbsp;</td></tr>
<tr bgcolor="#efedec">
<td>STORE COLLECTION DETAILS</span></td>
</tr>
<tr>
<td colspan="4" height="25">&nbsp;</td>
</tr>
<tr style="border-bottom: 2px solid #efedec;">
<td>
<p>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].description)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].description}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street1)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street1}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street2)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street2}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street3)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.street3}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.town)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.town}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.county)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.county}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.postCode)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.postCode}<br/>
</#if>
<#if (locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.phone1)?has_content >
${locationMap[customerOrderFulfilment.fulfilmentId?string].locationAddress.phone1}<br/>
</#if>
</p>
</td>
</tr>
</#if>
</#list>
</tr>
<tr>
<td colspan="2">&nbsp;</td>
<td colspan="2">Inclusive of VAT at 20%</span></td>
</tr>
<tr>
<td colspan="2" style="font-size: 21px;">TOTAL</td>
<td colspan="2" style="font-size: 21px;">${freeMarkerFormatter.formatCurrency(customerOrderHeader.orderValue,customerOrderHeader.currencyKey.currencyId)}</td>
</tr>

</table>
</p>
</html>

Appendix 2 - Styled FreeMarker Order Confirmation

Example FreeMarker script for Order Confirmation Email, complete with styling. The HTML for this template was generated using a WYSIWYG HTML editor, with dynamic text replaced with template language elements.

<meta content="width=device-width" name="viewport" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>Yourcompany.co.uk</title>
<link href="http://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css" />
<link href="http://fonts.googleapis.com/css?family=Bitter:400,700" rel="stylesheet" type="text/css" />
<style type="text/css"> @import url(http://fonts.googleapis.com/css?family=Montserrat:400,700); @import url(http://fonts.googleapis.com/css?family=Bitter:400,700); body{ font-family: Arial, sans-serif!important; } @media screen{ font-family: 'Montserrat', arial, sans-serif; } /* ------------------------------------------- PHONE -------------------------------------------- */ @media only screen and (max-width: 600px) { body{ font-family: 'Montserrat', arial, sans-serif; } .emailImage{ height:auto !important; max-width:600px !important; width: 100% !important; } } .ExternalClass {width: 100%;}</style>
<!--[if (gte mso 9)|(IE)]>
<table width="600" align="center" cellpadding="0" cellspacing="0" border="0" style="display:block!important; margin:0 auto!important; text-align: center;">
<tr>
<td>
<![endif]-->
<center>
<table align="center" bgcolor="#ffffff" cellpadding="0" cellspacing="0" height="100%" style="border-collapse: collapse;margin:0 auto!important; text-align: center;" valign="center" width="100%">
<tbody>
<tr>
<td align="center"> <img alt="Yourcompany" class="emailImage" src="cid:email_logo.jpg" style="display: block;" width="600" /></td>
</tr>
<tr>
<td align="center"> <img alt="" class="emailImage" src="cid:email_line.jpg" style="display: block; " width="600" /></td>
</tr>
<tr>
<td align="center" bgcolor="#FFFFFF" style="display:inline-block!important; max-width:600px!important; margin:0 auto!important; clear:both!important;">
<div style="padding: 0px; max-width:600px; margin:0 auto; display:block; width:100%;">
<table cellpadding="0" cellspacing="0" height="100%" style="border-collapse: collapse;" width="100%">
<tbody>
<tr>
<td width="3%"> &nbsp;</td>
<td width="94%">
<table cellpadding="0" cellspacing="0" height="100%" style="border-collapse: collapse;" width="100%">
<tbody>
<tr>
<td colspan="" height="30"> &amp;nbsp;</td>
</tr>
<tr>
<td align="center" bgcolor="#efedec" colspan="">
<div style="padding: 20px 35px 35px 35px; ">
<h1 style="color: #333333; font-family: Arial, sans-serif; font-size: 27px;"> <span style="font-family: 'Bitter', Arial, sans-serif !important;">Thank you for your order</span></h1>
<p style="color: #333333; font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; line-height: 25px; padding: 0px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">Your order has been submitted.<br /> You will receive an email when it is ready for collection.</span></p>
</div>
</td>
</tr>
<tr>
<td colspan="" height="20"> &amp;nbsp;</td>
</tr>
<tr>
<td align="center" colspan="3" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 400; padding: 0px; margin: 0px;">
<p> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">Dear <strong>${customerOrderHeader.customerFirstName}</strong></span></p>
<span style="font-family: 'Montserrat', Arial, sans-serif !important;">Thank you for your order. Information about your order <strong>#${customerOrderHeader.customerOrderId}</strong> appears below.<br /> If you need to get in touch with us about your order please contact us at: </span><a href="mailto:customerservices@yourcompany.co.uk" style="color: #8c827a; font-family: Arial, sans-serif; font-size: 14px; font-weight: 600; text-decoration: none; margin: 0px; padding: 0px; display:inline-block;"><span style="font-family: 'Montserrat', Arial, sans-serif !important;">customerservices@yourcompany.co.uk</span></a>
</td>
</tr>
<tr>
<td colspan="3" height="30"> &amp;nbsp;</td>
</tr>
<tr>
<td align="center" colspan="3">
${formattedOrderLines}
<table align="center" bgcolor="#ffffff" cellpadding="3" cellspacing="0" height="100%" style="border-collapse: collapse;margin:0 auto!important; text-align: center;" valign="center" width="100%">
<tbody>
<tr bgcolor="#efedec">
<th colspan="2" style="line-height: 20px; color: #746c65; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 600; padding: 5px 5px 5px 10px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">ITEM DESCRIPTION</span></th>
<th style="line-height: 20px; color: #746c65; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 600; padding: 5px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">QUANTITY</span></th>
<th style="line-height: 20px; color: #746c65; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 600; padding: 5px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">PRICE</span></th>
</tr>
<tr style="border-bottom: 2px solid #efedec;">
<td colspan="2"> &amp;nbsp;</td>
<td colspan="2" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: right; font-size: 14px; font-weight: 400; padding: 5px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">Inclusive of VAT at 20%</span></td>
</tr>
<tr style="border-bottom: 2px solid #efedec;">
<td colspan="2" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: left; font-size: 21px; font-weight: 600; padding: 10px 5px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">TOTAL</span></td>
<td colspan="2" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: right; font-size: 21px; font-weight: 600; padding: 10px 5px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">${total}</span></td>
</tr>
<tr>
<td colspan="4" height="25"> &amp;nbsp;</td>
</tr>
<tr bgcolor="#efedec">
<th colspan="4" style="line-height: 20px; color: #746c65; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 600; padding: 5px 5px 5px 10px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">STORE COLLECTION DETAILS</span></th>
</tr>
<tr>
<td colspan="4" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 400; padding: 5px; margin: 0px;">
<p> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">${destinationLocation.description}<br /> ${notEmpty(destinationLocation.locationAddress.street1) ? concat(destinationLocation.locationAddress.street1, &#39;<br /> &#39;) : &#39;&#39;} ${notEmpty(destinationLocation.locationAddress.street2) ? concat(destinationLocation.locationAddress.street2, &#39;<br /> &#39;) : &#39;&#39;} ${notEmpty(destinationLocation.locationAddress.street3) ? concat(destinationLocation.locationAddress.street3, &#39;<br /> &#39;) : &#39;&#39;} ${notEmpty(destinationLocation.locationAddress.town) ? concat(destinationLocation.locationAddress.town, &#39;<br /> &#39;) : &#39;&#39;} ${notEmpty(destinationLocation.locationAddress.county) ? concat(destinationLocation.locationAddress.county, &#39;<br /> &#39;) : &#39;&#39;} ${notEmpty(destinationLocation.locationAddress.postCode) ? concat(destinationLocation.locationAddress.postCode, &#39;<br /> &#39;) : &#39;&#39;}<br /> ${destinationLocation.locationAddress.phone1}</span></p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="" height="10"> &amp;nbsp;</td>
</tr>
<tr>
<td align="center" colspan="" style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: left; font-size: 14px; font-weight: 400; padding: 0px; margin: 0px;"> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">Kind Regards,</span></td>
</tr>
<tr>
<td> <img alt="Yourcompany" class="emailImage" src="cid:email_sig.jpg" style="display: block;" width="170" /></td>
</tr>
<tr>
<td colspan="" height="10"> &amp;nbsp;</td>
</tr>
<tr>
<td align="center"> <img alt="" class="emailImage" src="cid:email_line.jpg" style="display: block;" width="600" /></td>
</tr>
<tr>
<td style="line-height: 20px; color: #333333; font-family: Arial, sans-serif; text-align: left; font-size: 12px; padding: 0px; margin: 0px;">
<p> <a href="www.yourcompany.co.uk" style="color: #8c827a; font-family: Arial, sans-serif; font-size: 12px; font-weight: 600; text-decoration: none; margin: 0px; padding: 0px; display:inline-block;" target="_blank"><span style="font-family: 'Montserrat', Arial, sans-serif !important;">www.yourcompany.co.uk</span></a><br /> <a href="mailto:customerservices@yourcompany.co.uk" style="color: #8c827a; font-family: Arial, sans-serif; font-size: 12px; font-weight: 600; text-decoration: none; margin: 0px; padding: 0px; display:inline-block;"><span style="font-family: 'Montserrat', Arial, sans-serif !important;">customerservices@yourcompany.co.uk</span></a><br /> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">0800 000 0000</span></p>
<p> <span style="font-family: 'Montserrat', Arial, sans-serif !important;">YOURCOMPANY, ADDRESS LINE 1<br /> ADDRESS LINE 2<br /> COUNTY. POSTCODE</span></p>
<a href="#"><img alt="Facebook" src="cid:email_facebook.png" width="30" /></a> <a href="#"><img alt="Twitter" src="cid:email_twitter.png" width="30" /></a> <a href="#"><img alt="Google+" src="cid:email_google.png" width="30" /></a> <a href="#"><img alt="Pinterest" src="cid:email_pinterest.png" width="30" /></a> <a href="#"><img alt="Instagram" src="cid:email_instagram.png" width="30" /></a>
</td>
</tr>
<tr>
<td colspan="" height="30"> &amp;nbsp;</td>
</tr>
</tbody>
</table>
</td>
<td width="3%"> &nbsp;</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</center>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->

Appendix 3 - JSON Object Examples

JSON representations of the Java objects available within the FreeMarker template. Note that these are example data sets only.


Customer Order Header

{
"authorisedByUserKey" : { },
"completedDate" : "2020-12-09T10:42:43.000+00:00",
"notificationContactTypeKey" : {
"id" : "CX_ORD_NOTIFICATION"
},
"reasonId" : {
"regionId" : {
"groupTypeId" : "region"
}
},
"customerOrderId" : "20001",
"customerOrderTypeId" : "CUSTOMER_ORDER",
"createdDate" : "2020-12-09T10:42:43.000+00:00",
"lastUpdatedByUserKey" : { },
"customerNumber" : "123",
"customerFirstName" : "Beth",
"customerTitle" : "Mrs",
"customerSurname" : "Chadwick",
"customerPostcode" : "AL1 1AL",
"preferredContactTypeKey" : {
"id" : "CX_ORD_CONTACT"
},
"locationId" : {
"id" : "0001"
},
"userId" : {
"id" : "1111"
},
"deviceId" : { },
"currencyId" : {
"id" : "GBP"
},
"address" : [ {
"country" : "UK",
"county" : "Hertfordshire",
"email" : "b.chadwick@email.co.uk",
"mobilePhone" : "07495 555633",
"phone1" : "01727 555394",
"street1" : "23 St Leonards Road",
"town" : "St Albans",
"isTemplate" : false,
"countryCodeId" : { },
"typeId" : {
"id" : "CX_ORD_CONTACT"
}
} ]
}

List of Customer Order Items

[ {
"customerOrderId" : "20001",
"lineNumber" : 1,
"fulfilmentId" : 1,
"orderedQty" : 1,
"shippedQty" : 1,
"effectiveNetValue" : 4260,
"taxAmount" : 0.0,
"locationId" : { },
"userId" : { },
"productId" : {
"id" : "MB709SN-1"
},
"destinationLocationId" : { },
"reasonId" : {
"regionId" : {
"groupTypeId" : "region"
}
}
}, {
"customerOrderId" : "20001",
"lineNumber" : 2,
"fulfilmentId" : 1,
"orderedQty" : 2,
"shippedQty" : 2,
"netPrice" : 1175,
"effectiveNetValue" : 1000,
"taxAmount" : 5.0,
"locationId" : { },
"userId" : { },
"productId" : {
"id" : "DF715XM-1"
},
"destinationLocationId" : { },
"reasonId" : {
"regionId" : {
"groupTypeId" : "region"
}
},
"adjustments" : [ {
"adjustmentNumber" : 1,
"adjustmentType" : "DISCOUNT",
"adjustmentDetail" : "DISCOUNT1",
"description" : "£1.75 discount",
"amount" : 175,
"reasonKey" : {
"regionId" : {
"groupTypeId" : "region"
}
}
} ]
} ]

List of Customer Order Fulfilments

[ {
"customerOrderId" : "20001",
"fulfilmentId" : 1,
"sourceType" : "locationStockFulfilment",
"sourceId" : "0100",
"destinationType" : "COLLECTION",
"destinationLocationId" : {
"id" : "0001"
},
"destinationAddress" : {
"isTemplate" : false,
"countryCodeId" : { },
"typeId" : { }
},
"destinationName" : { },
"deliveryChargeForeignCurrencyId" : { },
"deliveryTypeId" : { },
"trackingDetails" : { }
}, {
"customerOrderId" : "20001",
"fulfilmentId" : 2,
"sourceType" : "locationStockFulfilment",
"sourceId" : "0100",
"destinationType" : "ADDRESS",
"destinationLocationId" : { },
"destinationAddress" : {
"county" : "Hertfordshire",
"phone1" : "01727 555943",
"postCode" : "AL1 1LA",
"street1" : "14 Eastbury Court ",
"town" : "St Albans",
"isTemplate" : false,
"countryCodeId" : { },
"typeId" : { }
},
"destinationName" : { },
"deliveryChargeForeignCurrencyId" : { },
"deliveryTypeId" : { },
"trackingDetails" : { }
} ]

List of Payments

{
"customerOrderId" : "20001",
"paymentNumber" : 1,
"paymentType" : "customerOrderCashPayment",
"amount" : 6610,
"tenderId" : {
"tenderId" : "CASH",
"groupId" : {
"groupTypeId" : "UK",
"variantGroupTypeId" : "All"
}
},
"currencyId" : {
"id" : "GBP"
},
"status" : "COMMITTED",
"isRefund" : false,
"refundAvailable" : 6610,
"createdByUserKey" : { }
} ]

Customer

{
"customerNumber" : "5432",
"customerName" : {
"surname" : "Chadwick",
"forename" : "Beth",
"title" : "Mrs"
},
"employeeKey" : { },
"customerType" : { },
"createdBy" : { },
"createdAt" : { }
}

Location Map

If the fulfilment type has a destination location, which is typical for Collection orders, the location map will contain details of the location, keyed by fulfilment ID.

{
"1" : {
"description" : "St Albans Store",
"locationType" : "location",
"locationAddress" : {
"countryCodeId" : { },
"typeId" : { },
"county" : "Hertfordshire",
"phone1" : "01727 555993",
"postCode" : "AL1 3BU",
"street1" : "14 High Street",
"town" : "St Albans",
"isTemplate" : false
},
"locationId" : "0001",
"emailConfiguration" : {
"locationId" : "0001",
"isTemplate" : false
},
"purgeDetails" : {
"isTemplate" : false
},
"exchangeRateConversionMethod" : "INDIRECT",
"isTemplate" : false,
"currencyId" : { },
"systemUserId" : { },
"defaultMenuGroupId" : { },
"menuGroupId" : { },
"priceGroupId" : { },
"fiscalProductGroupHierarchyId" : { },
"mmGroupHierarchyId" : { },
"exchangeRateGroupId" : { }
}
}