This article explains the necessary steps for showing the receipt of the order to an end customer. It also covers the checks that need to be done before showing the receipt.
The order receipt is the page that the end customer will be re-directed back to in Litium by the payment provider. The url to the receipt page is supplied to the payment provider in initialisation calls with the checkoutFlowInfo object as explained in previous articles.
When the payment provider calls this url, the following steps need to be performed:
Before the order receipt is shown to the user, it is important to check whether the order has been correctly paid. In some situations the payment provider may re-direct to the receipt page, but in fact the payment has failed.
The following code fragment fetches the current order and checks the payment status, before showing the receipt. If the status is incorrect, an error message will be displayed and not the receipt.
The code assumes that the order receipt is displayed using a web user control with the name m_checkoutOrderReceipt. The code for this user control is presented separately below this code.
string externalMessage = string.Empty;
// Check and sync the payment info.
var order = ModuleECommerce.Instance.Orders[CurrentState.ShoppingCart.OrderCarrier.ID, CurrentState.Token];
PaymentStatus paymentStatus = PaymentStatus.Unknown;
if (order != null && order.PaymentInfo != null)
{
//find the payment info object.
PaymentInfo paymentInfo;
if (!string.IsNullOrEmpty(Request[ParameterConstants.ECOM_PAYMENT_INFO_ID]))
{
Guid paymentInfoId = new Guid(Request[ParameterConstants.ECOM_PAYMENT_INFO_ID].ToString());
paymentInfo = order.PaymentInfo.Get(paymentInfoId);
}
else
{
//if request did not contain the payment info id, probably this is a old request comming from a
//payment provider not updated 46. Use the first payment info object.
paymentInfo = order.PaymentInfo[0];
}
//if the expected paid or reserved status is not reached, check whether we can synchronize.
if (paymentInfo != null)
{
if ((paymentInfo.PaymentStatus == PaymentStatus.ExecuteReserve || paymentInfo.PaymentStatus == PaymentStatus.ExecuteCharge || paymentInfo.PaymentStatus == PaymentStatus.ExecuteCapture) && paymentInfo.PaymentProvider.CanSyncPaymentStatus)
paymentStatus = paymentInfo.CheckPaymentStatus(CurrentState.Token, out externalMessage);
else
paymentStatus = paymentInfo.PaymentStatus;
}
//respond based on the payment state received.
if (paymentStatus == PaymentStatus.ChargeFailed || paymentStatus == PaymentStatus.ReserveFailed || paymentStatus == PaymentStatus.CaptureFailed)
{
var callBackResult = CurrentState.ShoppingCart.PaymentProviderCallbackResult;
if (callBackResult != null)
{
//append to external message in case CheckPaymentStatus above has returned one.
externalMessage = (string.IsNullOrEmpty(externalMessage)) ? callBackResult.ErrorMessage : externalMessage + ", "+callBackResult.ErrorMessage;
}
//make the step to be order payment, and show the error message from the provider.
//in this example, m_checkOutOrderPayment is the control responsible for showing the error message.
m_checkOutOrderPayment.SetErrorMessage(externalMessage);
}
else if (paymentStatus == PaymentStatus.Paid || paymentStatus == PaymentStatus.Reserved)
{
//if its paid or reserved, we should show the receipt to the user.
//also see the m_placeHolderCheckOutOrderReceipt unload method,
//we are clearing the shopping cart after it is being shown, to prepare it to accept a new order.
m_placeHolderCheckOutOrderReceipt.Visible = true;
CurrentState.ShoppingCart.Clear();
m_checkOutOrderReceipt.DataBind(order.ID);
}
else if (paymentStatus == PaymentStatus.Pending)
{
m_placeHolderCheckOutOrderReceipt.Visible = true;
CurrentState.ShoppingCart.Clear();
m_checkOutOrderReceipt.DataBind(order.ID);
//Below we show a warning message saying that the order is still pending.
m_checkOutOrderReceipt.SetErrorMessage(CurrentState.WebSite.Strings[CheckOutStringConstants.STRING_PAYMENT_PENDING]);
}
}
The order receipt user control
The above code assumes that the page has this web control that shows the order receipt to the user. Note that this web control is custom databound based on the current order's order id.
Code behind:
using System;
using System.Collections.Generic;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using Litium.Foundation.Modules.ECommerce.Carriers;
namespace Site.CMS.Templates.UserControls
{
/// <summary>
/// Class for the Step 4: Order receipt user control
/// </summary>
public partial class CheckOutOrderReceipt : App_Code.Site.CMS.UserControls.BaseUserControl
{
/// <summary>
/// Datas the bind.
/// </summary>
/// <param name="orderID">The order ID.</param>
public void DataBind(Guid orderID)
{
m_orderRepeater.DataSource = new List<Guid>() { orderID };
DataBind();
}
/// <summary>
/// ItemDataBound event handler for the ShoppingCart.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="RepeaterItemEventArgs"/> instance containing the event data.</param>
protected void m_orderRowRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if ((e.Item.ItemType == ListItemType.Item) || (e.Item.ItemType == ListItemType.AlternatingItem))
{
// If it exists a campaign price, delete the css class of old price
HtmlGenericControl priceListControl = e.Item.FindControl("m_productlistPrice") as HtmlGenericControl;
if (priceListControl != null)
{
if ((e.Item.DataItem as OrderRowCarrier).UnitCampaignPrice == 0)
priceListControl.Attributes.Remove("Class");
}
}
}
/// <summary>
/// Sets error message
/// </summary>
public void SetErrorMessage(string message)
{
m_placeHolderPaymentError.Visible = true;
m_literalPaymentStatus.Text = message;
}
}
}
The .ascx page: (Note that only order row rendering is shown, you can use the repeater to render the order totals as well.)
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CheckOutOrderReceipt.ascx.cs" Inherits="Site.CMS.Templates.UserControls.CheckOutOrderReceipt" %>
<%@ Register TagPrefix="site" TagName="CheckOutDeliveryInfo" Src="CheckOutDeliveryInfo.ascx" %>
<%@ Register TagPrefix="site" TagName="CheckOutOrderTotals" Src="CheckOutOrderTotals.ascx" %>
<%@ Register TagPrefix="site" TagName="CheckOutOrderDetails" Src="CheckOutOrderDetails.ascx" %>
<Ecom:OrderRepeater runat="server" ID="m_orderRepeater">
<HeaderTemplate>
<h1><web:Text ID="Text1" Name="Title4" runat="server" /></h1>
<web:Text ID="Text2" Name="Text4" runat="server" />
<ecom:GoogleAnalyticsScript runat="server" />
</HeaderTemplate>
<FooterTemplate>
</FooterTemplate>
<ItemTemplate>
<li id="order<Ecom:OrderID runat=server />">
<Ecom:OrderRowRepeater ID="m_orderRowRepeater" OnItemDataBound="m_orderRowRepeater_ItemDataBound" runat="server">
<HeaderTemplate>
<table class="orderrows orderdata common">
<caption><web:WebSiteString ID="WebSiteString1" Name="CheckOutOrderRowsCaption" runat="server" /></caption>
<thead>
<tr>
<th scope="col"><web:WebSiteString ID="WebSiteString2" Name="CheckOutArticleImage" runat="server" /></th>
<th scope="col"><web:WebSiteString ID="WebSiteString3" Name="CheckOutArticleNumber" runat="server" /></th>
<th scope="col" class="quantity"><web:WebSiteString ID="WebSiteString4" Name="CheckOutQuantity" runat="server" /></th>
<th scope="col" class="numeric"><Web:WebSiteString ID="WebSiteString5" Name="CheckOutUnitPrice" runat="server" /></th>
<th scope="col" class="numeric"><Web:WebSiteString ID="WebSiteString6" Name="CheckOutSubTotal" runat="server" /></th>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
<ItemTemplate>
<tr>
<td><PC:DisplayImage ID="DisplayImage1" MaxWidth="50" runat="server" /></td>
<td><PC:ArticleNumber ID="ArticleNumber1" runat="server" /></td>
<td class="quantity">
<Ecom:OrderRowQuantity runat="server" />
</td>
<td class="numeric">
<ul class="prices plain">
<li class="campaignprice"><Ecom:OrderRowCampaignPrice DisplayCurrencySymbol="true" runat="server" /></li>
<li class="oldprice" id="m_productlistPrice" runat="server"><Ecom:OrderRowListPrice DisplayCurrencySymbol="true" runat="server" /></li>
</ul>
</td>
<td class="numeric"><Ecom:OrderRowTotalPrice DisplayCurrencySymbol="true" runat="server" /></td>
</tr>
</ItemTemplate>
</Ecom:OrderRowRepeater>
</li>
</ItemTemplate>
</Ecom:OrderRepeater>
It is important that all relevant fields and the order grand total is displayed. In particular, make sure that the Order Id and Payment Id are displayed so that the user can track the order easily with the merchant.
Consider clearing the shopping cart if the order is a successful order, so that the user will get a clean shopping cart to place a new order.
Clearing the shopping cart is done by calling the CurrentState.Current.ShoppingCart.Clear() method. In the above example it is called after checking the payment status.