How the checkout process is built. Back-end and front-end perspective.
The checkout page in Litium Accelerator is an MVC page, while the actual checkout form is built as a React component. We will go through which part does what and how they interact with each other.
Configuration
The checkout page is configured as follows:
- Navigate to the Website area in back office. Go to Start and use Checkout page template to create the page.
- Then create a terms-and-conditions page, so that a link there can be included on the checkout page. It can have any template, but let's use the article page template.
- Edit the checkout page and set a link to the terms-and-conditions page:

- Create a page using the order confirmation template.
- Navigate to Settings, select Websites > Websites and choose the website where the checkout and order confirmation pages you created belong:

- Edit the checkout page and order confirmation page properties, so they point to the pages we created above. Make sure they are published and activated for the current channel. The checkout page should now be ready to use.
- Litium Accelerator can be configured to display checkout forms targeting B2C or B2B customers, or both. To configure this, set the Allow private or company customers (B2C or B2B) options in the website configuration.
Checkout page
The checkout page is built of several different parts:
- Index Razor view.
- Checkout MVC controllers
- Checkout WebAPI controller.
- Checkout component, written in React.
- Checkout action and reducer, written in Redux.
Let's take a closer look at them:
Index Razor view
The view is located under Litium.Accelerator.Mvc\Views\Checkout\Index.cshtml. It contains an empty DIV tag, with checkout as id so that the React Checkout component will be loaded to that position. It also contains the script to set the Redux state for the checkout page. The state is built by the CheckoutViewModelBuilder and set to window.__litium.preloadState, which is the global JavaScript variable. This preload state is the initial state, which we use when Redux store is created. That means the Checkout component has the data right when the page is loaded, which avoids unnecessary requests. The Checkout component load part is done in Litium.Accelerator.Mvc\Client\Scripts\index.js:
if (document.getElementById('checkout')) {
const Checkout = Loadable({
loader: () => import('./Containers/Checkout.container'),
loading: () => <div></div>,
});
ReactDOM.render(
<Provider store={store}>
<Checkout />
</Provider>,
document.getElementById('checkout')
);
}
Note that the Checkout component is loaded on demand. It is not bundled in the app.min.js file, to reduce the size of the main JavaScript file. We use the Checkout component only in the checkout page and don't need it for other pages.
Checkout controllers
There are two checkout controllers, one MVC and one WebAPI. They handle different requests:
- Checkout MVC controller: serves the request to render the checkout page, using the Index Razor view above. It also serves responses from payment providers. For example, a user who selects DIBS as payment option will be redirected to the DIBS's website to enter a credit card. The user will then be redirected back to the website, either by completing the payment or by cancelling it. Those requests are served by this controller, with the HandleResponse and HandleCancelResponse methods.
- Checkout WebApi controller: serves AJAX requests made by the client code, for example, to place an order or change the delivery/payment method. It receives the CheckoutViewModel and returns the same model. All actions are decorated with the ApiValidateAntiForgeryToken to prevent cross-site request forgeries. This is similar to the configuration in CartController for the Buy button.
When a user selects a payment option on the checkout page, an AJAX request is sent to the checkout WebAPI controller, to action SetPaymentProvider. If the selected provider has a payment widget, a PaymentWidgetResult will be sent to the client in order to render the payment widget. For example, if a user selects Klarna Checkout, a Klarna Checkout widget will be rendered in the browser. If DIBS is selected, no widget has to be rendered since the user is redirected to DIBS's payment page instead. See this article for more information about working with payment providers.
Place order request is processed in the PlaceOrder action of the WebApi checkout controller, where most of the logic is done in CheckoutServiceImpl.cs. Thanks to the framework, most of the important checkout logic is done in the cart located there.
React Checkout component
The React Checkout component takes responsible for rendering the checkout form. It consists of the cart detail section, the address form, delivery and payment options, and the order detail section. If the payment provider has a payment widget, it will be rendered in React too. For those payment providers, a small React component has to be implemented. Klarna checkout is an example of that.
The React Checkout component, like other components, has presentational and container components. If you are unsure about the difference between them, or how React components are connected to Redux, please refer to this page.
There are presentational components for:
- cart details — Cart.js
- address form for private customers — Checkout.PrivateCustomerInfo.js
- adress form for business customers — Checkout.BusinessCustomerInfo.js
- delivery options — Checkout.DeliveryMethods.js
- payment options — Checkout.PaymentMethods.js
- order details section — Checkout.OrderInfo.js and Checkout.OrderNote.js
Each component is responsible for rendering a small and specific part of the checkout form. Then the Checkout.Container.js component connects them all together, adds the validation and also connects them to Redux.
Checkout action and reducer
The actual logic is placed in Checkout.action.js and Checkout.reducer.js. While Checkout.reducer.js is very "thin" — it simply takes the data in an action and merges it into the corresponsing state — Checkout.action.js is considered "fat". The action is responsible for sending requests to the WebApi controller, process the request and dispatch the action to the reducer, in order to trigger the state change. The examples below show how the actions set payment provider and place order work:
export const setPayment = (systemId) => (dispatch, getState) => {
dispatch({
type: CHECKOUT_SET_PAYMENT,
payload: {
selectedPaymentMethod: systemId,
}
});
const { payload } = getState().checkout;
return put('/api/checkout/setPaymentProvider', payload)
.then(response => response.json())
.then(result => {
dispatch(loadCart());
dispatch(setPaymentWidget(result.paymentWidget));
})
.catch(ex => dispatch(catchError(ex, error => submitError(error))))
}
This is what happens when a user selects a payment option:
- A CHECKOUT_SET_PAYMENT action is dispatched in order to set the selected payment method Id for the Redux state. The CheckoutPaymentMethods component will set the corresponding radio button as checked, when the state is updated.
- A PUT request is sent to the SetPaymentProvider action of the checkout WebAPI controller. It has the checkout state as payload, which is the CheckoutViewModel. Note that the checkout state has the selectedPaymentMethod, so the WebApi controller has the Id of the payment method the user has selected.
- The checkout WebApi controller changes the payment method, sets PaymentWidget to CheckoutViewModel, if any, and returns CheckoutViewModel to the client.
- When the checkout action receives the response, it does two things: first it reloads the cart by calling the loadCart() function, to get the latest Cart object. This is in order to show the new price, because a payment fee might be added. Then it calls setPaymentWidget to dispatch the CHECKOUT_SET_PAYMENT_WIDGET action. The checkout reducer then updates the state to store the payment widget.
Now, depending on what we have in the payment widget, PaymentWidget will either render it or just an empty block. PaymentWidgetResult has the Id, which is the Id of the payment provider, and ResponseString is the response from the payment provider. Klarna Checkout, for instance, would have KlarnaCheckout as Id, and the HtmlSnippet as ResponseString. We use this ResponseString to render the Klarna Checkout widget.
This action is called when the Place order button is clicked:
const _submit = (url, model, dispatch) => {
return post(url, model)
.then(response => response.json())
.then(result => {
dispatch(submitDone(result));
if (result.redirectUrl) {
window.location = result.redirectUrl;
}
})
.catch(ex => {
if (ex.response) {
ex.response.json().then(error => {
dispatch(submitError(error));
dispatch(submitDone(null));
});
} else {
dispatch(submitError(ex));
}
})
;
}
The url parameter has /api/checkout as its value, and the model parameter has the Checkout state as payload, which is the CheckoutViewModel. It sends a POST request to the PlaceOrder action of the checkout WebAPI controller, which returns the new CheckoutViewModel. If the model has the RedirectUrl, the browser will then be redirected to the new URL. This is the case when a user selects a "Redirect payment" like DIBS, and will be redirected to DIBS's payment page. Or, if the order is placed, the RedirectUrl will guide the browser to navigate to the order confirmation page.
If an error occurs, it will be handled in the catch() function. The error can be a validation error or other error types. Checkout.container.js will catch the error, show the error message and scroll the browser to the incorrect field if it is a validation error. See the componentDidUpdate() function in Checkout.container.js.
Validation
Client side validation is done with Yup. Different validation schema are defined for private and business customers.
Payment and delivery methods
Payment providers and methods can be downloaded as Litium Add-ons. More info can be found here. Please consult the respecitve payment providers regarding installation and configuration.
To enable and show the payment/delivery methods in the checkout page, please consult Working with Payment providers.
