Commerce.js React Checkout
This is a guide on adding checkout order capture functionality to React application using Commerce.js. This is a continuation from the previous guide on implementing cart functionality.
Overview
The aim for this guide is to create a checkout page to capture our cart items into an order as well and add a confirmation page to display a successful order. Below outlines what this guide will achieve:
- Add page routing to the application
- Generate a checkout token to capture the order
- Create a checkout page with a form
- Create a confirmation page to display an order reference and receipt
Requirements
What you will need to start this project:
- An IDE or code editor
- NodeJS, at least v8
- npm or yarn
- React devtools (recommended)
Prerequisites
This project assumes you have some knowledge of the below concepts before starting:
- Basic knowledge of JavaScript
- Some knowledge of React
- An idea of the JAMstack architecture and how APIs work
Some things to note
- The purpose of this guide is to focus on the Commerce.js checkout integration, using Vue.js to build out the application. We will therefore not be covering styling details
- We will not be going over any Font Awesome usage
- We will not be going over any UI details that do not pertain much to Commerce.js methods
Checkout
1. Set up routing
For fully functional SPAs (single page applications) to scale, you will need to add routing in order to navigate to
various view pages such to a cart or checkout flow. For a Commerce.js application, this is where you have the full
flexibility of creating fully custom checkout flow. Let's jump right back to where we left off from the previous cart
guide and add react-router-dom, a routing library for React web application, to
our project. You will only need to install react-router-dom
and not react-router
, which is for React Native. Install
the routing library by running the below command.
yarn add react-router-dom
# OR
npm i react-router-dom
After installing, you'll need to make sure the router component is rendered at the root of your element hierarchy to
have access to the routes you will be setting up. First, go into src/index.js
, import the
[BrowserRouter](https://reactrouter.com/web/guides/primary-components)
component in and wrap the <App>
element in
the router component.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById("root"));
Now that you've added routing to serve your whole application, you'll now need to add page components that you'll be
creating in the next section to the Route Matchers components
<Route>
and <Switch>
in App.js
.
First, import the Route Matchers components Route
and Switch
from react-router-dom
.
import { Switch, Route } from 'react-router-dom'
Next, in your render function, wrap your <Cart>
and <Products>
components in a <Route>
element.
<Route
path="/"
exact
render={(props) => {
return (
<>
<Hero
merchant={merchant}
/>
{ this.renderCartNav() }
{isCartVisible &&
<Cart
{...props}
cart={cart}
onUpdateCartQty={this.handleUpdateCartQty}
onRemoveFromCart={this.handleRemoveFromCart}
onEmptyCart={this.handleEmptyCart}
/>
}
<ProductsList
{...props}
products={products}
onAddToCart={this.handleAddToCart}
/>
</>
);
}}
/>
Let's walk through the above code block - The <Route>
includes the attributes:
path
's value points to the path you want to route torender
outputs the components included in the view page indicated at thepath
value. You can optionally passprops
to the render attribute.exact
attribute ensures the<Route path>
matches the entire URL.
Now that you've set the initial route component, include a <Checkout>
component which you will get to creating next.
return (
<div className="app">
<Switch>
<Route
path="/"
exact
render={(props) => {
return (
<>
<Hero
merchant={merchant}
/>
{ this.renderCartNav() }
{isCartVisible &&
<Cart
{...props}
cart={cart}
onUpdateCartQty={this.handleUpdateCartQty}
onRemoveFromCart={this.handleRemoveFromCart}
onEmptyCart={this.handleEmptyCart}
/>
}
<ProductsList
{...props}
products={products}
onAddToCart={this.handleAddToCart}
/>
</>
);
}}
/>
<Route
path="/checkout"
exact
render={(props) => {
return (
<Checkout
{...props}
cart={cart}
/>
)
}}
/>
</Switch>
</div>
);
Note the <Switch>
element, which was imported from the react-router-dom
module earlier, wrapping around the
<Route>
s. When the <Switch>
element renders, it will search through the child elements to render the one whose path
matches the current URL.
Secondly, a new <Route>
element was added which returns a <Checkout>
component with a path pointing to /checkout
.
Pass {...props}
cart={cart}
to ensure you have access to them in the checkout component. You will create this
component in the upcoming section. This is all the routing setup you will need for now until the remaining view
components are added later in the guide.
Lastly, the checkout page will need to be pushed into view from the cart checkout button created in the previous cart
guide. Go into Cart.js
to import the Link
component in, a navigation component, and refactor the checkout <button>
to a <Link>
element.
<div className="cart__footer">
<button className="cart__btn-empty" onClick={this.handleEmptyCart}>Empty cart</button>
<Link
className="cart__btn-checkout"
to="/checkout"
>
Checkout
</Link>
</div>
Now, if you click the link, you should be route to a blank page with the url ending in a /checkout
.
2. Generate token in the checkout component
Earlier in step 1 we created the appropriate route components to navigate to a checkout page. You will now create that
view page to render when the router points to the /checkout
path.
First, let's create a new folder src/pages
and a Checkout.js
page component. This page component is going to get
real hefty quite fast, but it will be broken it down in chunks throughout the rest of this guide.
The Checkout resource in Chec helps to handle one of the most complex moving
parts of an eCommerce application. The Checkout endpoint comes with the core commerce.checkout.generateToken()
and
commerce.checkout.capture()
methods along with Checkout
helpers - additional helper functions for a seamless
purchasing flow which will be touched on more later.
In the Checkout.js
page component, let's start first by initializing all the data you will need in this component to
build the checkout page and form.
Commerce.js provides a powerful method
commerce.checkout.generateToken()
to capture all the data
needed from the cart and initiate the checkout process simply by providing a cart ID, a product ID, or a product's
permalink as an argument. You'll use a cart ID since we've already built the cart.
Let's create a class component then initialize a checkoutToken
in the constructor's state.
class Checkout extends Component {
constructor(props) {
super(props);
this.state = {
checkoutToken: {},
}
};
render() {
return (
<div></div>
);
};
};
export default Checkout;
Next, create the helper function generateCheckoutToken()
that will generate the checkout token needed to capture the
checkout.
/**
* Generates a checkout token
* https://commercejs.com/docs/sdk/checkout#generate-token
*/
generateCheckoutToken() {
const { cart } = this.props;
if(cart.line_items.length) {
commerce.checkout.generateToken(cart.id, { type: 'cart' })
.then((token) => {
this.setState({ checkoutToken: token });
}).catch((error) => {
console.log('There was an error in generating a token', error);
});
}
}
The commerce.checkout.generateToken()
takes in our cart ID and the identifier type cart
. The type property is an
optional parameter you can pass in as an identifier, in this case cart
the type associated to this.cart.id
. First
check that line_items
in cart
exists with an if
statement before inserting the generateToken()
method. The
returned full token
object will be stored in the checkoutToken
state you created above after a successful request.
To call this function, include it in the componentDidMount
lifecyle hook to generate a token when the component
mounts.
componentDidMount() {
this.generateCheckoutToken();
}
When you click the checkout button from the cart page, you will be routed to /checkout
and in which case the
generateCheckoutToken()
function will run. Upon a successful request to generate the checkout token, you should
receive an abbreviated response like the below json data:
{
"id": "chkt_J5aYJ8zBG7dM95",
"cart_id": "cart_ywMy2OE8zO7Dbw",
"created": 1600411250,
"expires": 1601016050,
"analytics": {
"google": {
"settings": {
"tracking_id": null,
"linked_domains": null
}
}
},
"line_items": [
{
"id": "item_7RyWOwmK5nEa2V",
"product_id": "prod_NqKE50BR4wdgBL",
"name": "Kettle",
"image": "https://cdn.chec.io/merchants/18462/images/676785cedc85f69ab27c42c307af5dec30120ab75f03a9889ab29|u9 1.png",
"sku": null,
"description": "<p>Black stove-top kettle</p>",
"quantity": 1,
"price": {
"raw": 45.5,
"formatted": "45.50",
"formatted_with_symbol": "$45.50",
"formatted_with_code": "45.50 USD"
},
"subtotal": {
"raw": 45.5,
"formatted": "45.50",
"formatted_with_symbol": "$45.50",
"formatted_with_code": "45.50 USD"
},
"variants": [],
"conditionals": {
"is_active": true,
"is_free": false,
"is_tax_exempt": false,
"is_pay_what_you_want": false,
"is_quantity_limited": false,
"is_sold_out": false,
"has_digital_delivery": false,
"has_physical_delivery": false,
"has_images": true,
"has_video": false,
"has_rich_embed": false,
"collects_fullname": false,
"collects_shipping_address": false,
"collects_billing_address": false,
"collects_extrafields": false
},
}
],
"shipping_methods": [],
"live": {
"merchant_id": 18462,
"currency": {
"code": "USD",
"symbol": "$"
},
"line_items": [
{
"id": "item_7RyWOwmK5nEa2V",
"product_id": "prod_NqKE50BR4wdgBL",
"product_name": "Kettle",
"type": "standard",
"sku": null,
"quantity": 1,
"price": {
"raw": 45.5,
"formatted": "45.50",
"formatted_with_symbol": "$45.50",
"formatted_with_code": "45.50 USD"
},
"line_total": {
"raw": 45.5,
"formatted": "45.50",
"formatted_with_symbol": "$45.50",
"formatted_with_code": "45.50 USD"
},
"variants": [],
"tax": {
"is_taxable": false,
"taxable_amount": null,
"amount": null,
"breakdown": null
}
},
{
"id": "item_1ypbroE658n4ea",
"product_id": "prod_kpnNwAMNZwmXB3",
"product_name": "Book",
"type": "standard",
"sku": null,
"quantity": 1,
"price": {
"raw": 13.5,
"formatted": "13.50",
"formatted_with_symbol": "$13.50",
"formatted_with_code": "13.50 USD"
},
"line_total": {
"raw": 13.5,
"formatted": "13.50",
"formatted_with_symbol": "$13.50",
"formatted_with_code": "13.50 USD"
},
"variants": [],
"tax": {
"is_taxable": false,
"taxable_amount": null,
"amount": null,
"breakdown": null
}
}
],
"subtotal": {
"raw": 59,
"formatted": "59.00",
"formatted_with_symbol": "$59.00",
"formatted_with_code": "59.00 USD"
},
"discount": [],
"shipping": {
"available_options": [],
"price": {
"raw": 0,
"formatted": "0.00",
"formatted_with_symbol": "$0.00",
"formatted_with_code": "0.00 USD"
}
},
"tax": {
"amount": {
"raw": 0,
"formatted": "0.00",
"formatted_with_symbol": "$0.00",
"formatted_with_code": "0.00 USD"
}
},
"total": {
"raw": 59,
"formatted": "59.00",
"formatted_with_symbol": "$59.00",
"formatted_with_code": "59.00 USD"
},
"total_with_tax": {
"raw": 59,
"formatted": "59.00",
"formatted_with_symbol": "$59.00",
"formatted_with_code": "59.00 USD"
},
"giftcard": [],
"total_due": {
"raw": 59,
"formatted": "59.00",
"formatted_with_symbol": "$59.00",
"formatted_with_code": "59.00 USD"
},
"pay_what_you_want": {
"enabled": false,
"minimum": null,
"customer_set_price": null
},
"future_charges": []
}
}
3. Build out the checkout page
There are four core properties that are required to process an order using Commerce.js - customer
, shipping
,
fulfillment
, and payment
. Let's get back to your constructor's state and start to define the fields you need to
capture in the checkout form. The main property objects will all go under a form
object. You will then bind these
properties to each single field in the render function with the value
attribute. Note the values filled out in the
data, these are arbitrary values that will prefill the checkout form when it mounts.
class Checkout extends Component {
constructor(props) {
super(props);
this.state = {
checkoutToken: {},
form: {
customer: {
firstName: 'Jane',
lastName: 'Doe',
email: 'janedoe@email.com',
},
shipping: {
name: 'Jane Doe',
street: '123 Fake St',
city: 'San Francisco',
stateProvince: 'CA',
postalZipCode: '94107',
country: 'US',
},
payment: {
cardNum: '4242 4242 4242 4242',
expMonth: '01',
expYear: '2023',
ccv: '123',
billingPostalZipCode: '94107',
},
},
}
};
};
export default Checkout;
Since this page will contain a lot of markup to render a various components for field inputs and in turn can get quite
bloated, you will want to create a separate render function for the checkout form. This keeps your checkout component
file readable and organized. As mentioned above, bind the data to each of the value
attributes in the input elements
in the form element below. The inputs will be pre-filled with the state data created above.
renderCheckoutForm() {
return (
<form className="checkout__form">
<h4 className="checkout__subheading">Customer information</h4>
<label className="checkout__label" htmlFor="firstName">First name</label>
<input className="checkout__input" type="text" value={this.state.form.customer.firstName} name="firstName" placeholder="Enter your first name" required />
<label className="checkout__label" htmlFor="lastName">Last name</label>
<input className="checkout__input" type="text" value={this.state.form.customer.lastName}name="lastName" placeholder="Enter your last name" required />
<label className="checkout__label" htmlFor="email">Email</label>
<input className="checkout__input" type="text" value={this.state.form.customer.email} name="email" placeholder="Enter your email" required />
<h4 className="checkout__subheading">Shipping details</h4>
<label className="checkout__label" htmlFor="fullname">Full name</label>
<input className="checkout__input" type="text" value={this.state.form.shipping.name} name="name" placeholder="Enter your shipping full name" required />
<label className="checkout__label" htmlFor="street">Street address</label>
<input className="checkout__input" type="text" value={this.state.form.shipping.street} name="street" placeholder="Enter your street address" required />
<label className="checkout__label" htmlFor="city">City</label>
<input className="checkout__input" type="text" value={this.state.form.shipping.city} name="city" placeholder="Enter your city" required />
<label className="checkout__label" htmlFor="postalZipCode">Postal/Zip code</label>
<input className="checkout__input" type="text" value={this.state.form.shipping.postalZipCode} name="postalZipCode" placeholder="Enter your postal/zip code" required />
<h4 className="checkout__subheading">Payment information</h4>
<label className="checkout__label" htmlFor="cardNum">Credit card number</label>
<input className="checkout__input" type="text" name="cardNum" value={this.state.form.payment.cardNum} placeholder="Enter your card number" />
<label className="checkout__label" htmlFor="expMonth">Expiry month</label>
<input className="checkout__input" type="text" name="expMonth" value={this.state.form.payment.expMonth} placeholder="Card expiry month" />
<label className="checkout__label" htmlFor="expYear">Expiry year</label>
<input className="checkout__input" type="text" name="expYear" value={this.state.form.payment.expYear} placeholder="Card expiry year" />
<label className="checkout__label" htmlFor="ccv">CCV</label>
<input className="checkout__input" type="text" name="ccv" value={this.state.form.payment.ccv} placeholder="CCV (3 digits)" />
<button classNamea="checkout__btn-confirm">Confirm order</button>
</form>
);
};
The fields above contain all the customer details and payments inputs you will need to collect from the customer. The shipping method data is also required in order to ship the items to the customer. Chec and Commerce.js has verbose shipment and fulfillment methods to handle this process. In the Chec Dashboard, worldwide shipping zones can be added in Settings > Shipping and then enabled at the product level. For this demo merchant account, the international shipping for each product is enabled. In the next section, Commerce.js checkout helper functions will be touched on and these functions will easily fetch a full list of countries, states, provinces, and shipping options to populate the form fields for fulfillment data collection
3. Checkout helpers
Let's first get back to the constructor's state and initialize the empty objects and arrays that you will need to store
the responses from the checkout helper methods. Initialize
a shippingCountries
object, a shippingSubdivisions
object and a shippingOptions
array.
this.state = {
checkoutToken: {},
form: {
customer: {
firstName: 'Jane',
lastName: 'Doe',
email: 'janedoe@email.com',
},
shipping: {
name: 'Jane Doe',
street: '123 Fake St',
city: 'San Francisco',
stateProvince: 'CA',
postalZipCode: '94107',
country: 'US',
},
payment: {
cardNum: '4242 4242 4242 4242',
expMonth: '01',
expYear: '2023',
ccv: '123',
billingPostalZipCode: '94107',
},
},
shippingCountries: {},
shippingSubdivisions: {},
shippingOptions: [],
shippingOption: '',
}
With a created function fetchShippingCountries()
, use
commerce.services.localeListShipppingCountries()
at GET v1/services/locale/{checkout_token_id}/countries
to fetch and list all countries in the select options in the
form.
/**
* Fetches a list of countries available to ship to checkout token
* https://commercejs.com/docs/sdk/checkout#list-available-shipping-countries
*
* @param {string} checkoutTokenId
*/
fetchShippingCountries(checkoutTokenId) {
commerce.services.localeListShippingCountries(checkoutTokenId).then((countries) => {
this.setState({
shippingCountries: countries.countries
})
}).catch((error) => {
console.log('There was an error fetching a list of shipping countries', error);
});
}
The response will be stored in the shippingCountries
object you initialized earlier in our data object. You will then
be able to use this countries object to iterate and display a list of countries in a select element, which you will be
adding later. The fetchSubdivisions()
function below will walk through the same pattern as well.
A country code argument is required to make a request with
commerce.services.localeListSubdivisions()
to GET v1/services/locale/{country_code}/subdivisions
to get a list of all subdivisions for that particular country.
/**
* Fetches the subdivisions (provinces/states) in a country which
* can be shipped to for the current checkout
* https://commercejs.com/docs/sdk/checkout#list-subdivisions
*
* @param {string} countryCode
*/
fetchShippingSubdivisions(checkoutTokenId, countryCode) {
commerce.services.localeListSubdivisions(checkoutTokenId, countryCode).then((subdivisions) => {
this.setState({
shippingSubdivisions: subdivisions.subdivisions
})
}).catch((error) => {
console.log('There was an error fetching the subdivisions', error);
});
},
With a successful request, the response will be stored in the this.state.shippingSubdivions
array and will be used to
iterate and output onto a select element in your template later on.
For your next checkout helper function, fetch the current shipping options available in your merchant account. This
function will fetch all the shipping options that were registered in the dashboard and are applicable to the products in
your cart using the
commerce.checkout.getShippingOptions()
method. This
function takes in two required parameters - the checkoutTokenId
, the country code for the provide country
in our
data, and the region
is optional.
/**
* Fetches the available shipping methods for the current checkout
* https://commercejs.com/docs/sdk/checkout#get-shipping-methods
*
* @param {string} checkoutTokenId
* @param {string} country
* @param {string} stateProvince
*/
fetchShippingOptions(checkoutTokenId, country, stateProvince) {
commerce.checkout.getShippingOptions(checkoutTokenId,
{
country: country,
region: stateProvince
}).then((options) => {
const shippingOption = options[0] || null;
this.setState({
shippingOptions: options,
shippingOption: shippingOption,
})
}).catch((error) => {
console.log('There was an error fetching the shipping methods', error);
});
}
When the promise resolves, the response will be stored into this.state.shippingOptions
which you can then use to
render a list of shipping options in your template. Note that the shippingOption
is set to the first index option in
the array to have an option in the dropdown be displayed.
Alright, that wraps up all the checkout helper functions you'll want to create for the checkout page. Now it's time to execute and hook up the responses to your render function!
Start by chaining and calling the fetchShippingCountries()
in the generateCheckoutToken
function for ease of
execution.
/**
* Generates a checkout token
* https://commercejs.com/docs/sdk/checkout#generate-token
*/
generateCheckoutToken() {
const { cart } = this.props;
commerce.checkout.generateToken(cart.id, { type: 'cart' });
.then((token) => this.setState({ checkoutToken: token }));
.then(() => this.fetchShippingCountries(this.state.checkoutToken.id));
.catch((error) => {
console.log('There was an error in generating a token', error);
});
}
Next, check that this.state.form.shipping.country
exists then call the fetchSubdivisions()
function when the
component mounts. This ensures that your data is ready to be used when rendering your select options in your render
function.
componentDidMount() {
this.generateCheckoutToken();
if(this.state.form.shipping.country) {
this.fetchSubdivisions(this.state.form.shipping.country);
}
}
In the same vein, you'll need to call the fetchShippingOptions()
function to display the list of shipping options
available. Add this.fetchShippingOptions(this.state.checkoutToken.id, this.state.form.shipping.country, this.state.form.shipping.stateProvince)
into a lifecycle hook that checks whether the shipping country state has
changed.
componentDidUpdate(prevProps, prevState) {
if(this.state.form.shipping.country !== prevState.form.shipping.country) {
this.fetchShippingOptions(this.state.checkoutToken.id, this.state.form.shipping.country, this.state.form.shipping.stateProvince);
}
}
You will now need to bind all the data responses to the shipping form fields. In the shipping section of the JSX render you created earlier on, place all the markup underneath the Postal/Zip code input field:
<label className="checkout__label" htmlFor="country">Country</label>
<select
value={this.state.form.shipping.country}
name="country"
onChange={this.handleFormChanges}
className="checkout__select"
>
<option value="" disabled>Country</option>
{
Object.keys(shippingCountries).map((index) => {
return (
<option value={index} key={index}>{shippingCountries[index]}</option>
)
})
}
</select>
<label className="checkout__label" htmlFor="stateProvince">State/province</label>
<select
value={this.state.form.shipping.stateProvince}
name="stateProvince"
onChange={this.handleFormChanges}
className="checkout__select"
>
<option className="checkout__option" value="" disabled>State/province</option>
{
Object.keys(shippingSubdivisions).map((index) => {
return (
<option value={index} key={index}>{shippingSubdivisions[index]}</option>
)
})
}
</select>
<label className="checkout__label" htmlFor="shippingOption">Shipping method</label>
<select
value={this.state.shippingOption.id}
name="shippingOption"
onChange={this.handleFormChanges}
className="checkout__select"
>
<option className="checkout__select-option" value="" disabled>Select a shipping method</option>
{
shippingOptions.map((method, index) => {
return (
<option className="checkout__select-option" value={method.id} key={index}>{`${method.description} - $${method.price.formatted_with_code}` }</option>
)
})
}
</select>
The three fields you just added:
- Binds the
this.state.form.shipping.country
as the selected country and loops through theshippingCountries
array to render as options - Binds the
this.state.form.shipping.stateProvince
as the selected state/province and iterates through theshippingSubivisions
object to display the available list of countries - Binds the
this.state.shippingOption
and loops through theshippingOptions
array to render as options in the Shipping method field.
Once all the data is bound to the field you are then able to collect the necessary data to convert the checkout into an order object.
4. Capture order
With all the data collected you now need to associate it to each of the order properties in an appropriate data structure so you can confirm the order.
Create a handleCaptureCheckout()
handler function and structure your returned data. Have a look at the expected
structure here to send an order request.
handleCaptureCheckout(e) {
e.preventDefault();
const orderData = {
line_items: this.state.checkoutToken.live.line_items,
customer: {
firstname: this.state.form.customer.firstName,
lastname: this.state.form.customer.lastName,
email: this.state.form.customer.email
},
shipping: {
name: this.state.form.shipping.name,
street: this.state.form.shipping.street,
town_city: this.state.form.shipping.city,
county_state: this.state.form.shipping.stateProvince,
postal_zip_code: this.state.form.shipping.postalZipCode,
country: this.state.form.shipping.country,
},
fulfillment: {
shipping_method: this.state..shippingOption
},
payment: {
gateway: "test_gateway",
card: {
number: this.state.form.payment.cardNum,
expiry_month: this.state.form.payment.expMonth,
expiry_year: this.state.form.payment.expYear,
cvc: this.state.form.payment.ccv,
postal_zip_code: this.state.form.payment.billingPostalZipCode
}
}
};
this.props.onCaptureCheckout(this.state.checkoutToken.id, orderData);
};
Follow the exact structure of the data you intend to send and attach a callback function onCaptureCheckout
to the
handler and pass the order data object along with the required checkoutToken.id
.
You need a button to handle the clicking of order confirmation, let's add that right now as the last element before the
closing </form>
tag:
<button onClick={this.handleCaptureCheckout} className="checkout__btn-confirm">Confirm order</button>
Go back to your App.js
to initialize an order
data as an empty object where you store your returned order object.
this.state = {
merchant: {},
products: [],
cart: {},
isCartVisible: false,
order: {},
};
Before creating an event handler to deal with your order capture, use another Commerce.js method called
commerce.checkout.refreshCart()
. When you call this function, it
will refresh the cart in your state/session when the order is confirmed.
/**
* Refreshes to a new cart
* https://commercejs.com/docs/sdk/cart#refresh-cart
*/
refreshCart() {
commerce.cart.refresh().then((newCart) => {
this.setState({
cart: newCart
});
}).catch((error) => {
console.log('There was an error refreshing your cart', error);
});
};
Now create a helper function which will capture your order with the method
commerce.checkout.capture()
. It takes in the
checkoutTokenId
and the newOrder
parameters. Upon the promise resolution, refresh the cart, store the order
into
the this.order
property, and lastly use the router to push to a confirmation
page which will be created in the last
step.
/**
* Captures the checkout
* https://commercejs.com/docs/sdk/checkout#capture-order
*
* @param {string} checkoutTokenId The ID of the checkout token
* @param {object} newOrder The new order object data
*/
handleCaptureCheckout(checkoutTokenId, newOrder) {
commerce.checkout.capture(checkoutTokenId, newOrder).then((order) => {
this.setState({
order: order
});
this.refreshCart();
window.sessionStorage.setItem('order_receipt', JSON.stringify(order));
}).catch((error) => {
console.log('There was an error confirming your order', error);
});
};
Now make sure you update and bind the necessary props and event handlers to a <Route>
component passing in the
<Checkout>
component in the render:
<Route
path="/checkout"
exact
render={(props) => {
return (
<Checkout
{...props}
cart={cart}
onCaptureCheckout={this.handleCaptureCheckout}
/>
);
}}
/>
Lastly, create a simple confirmation view to display a successful order page.
5. Order confirmation
Under src/pages
, create a new page component and name it Confirmation.js
. Define an order prop for the parent
component App.js
to pass the order object down. Next, create your render function to output a simple UI for the
confirmation screen.
class Confirmation extends Component {
render() {
const { order } = this.props;
return (
<div className="confirmation">
<div className="confirmation__wrapper">
<div className="confirmation__wrapper-message">
<h4>Thank you for your purchase, {order.customer.firstname} {order.customer.lastname}!</h4>
<p className="confirmation__wrapper-reference">
<span>Order ref:</span> {order.customer_reference}
</p>
</div>
<Link
className="confirmation__wrapper-back"
type="button"
to="/"
>
<FontAwesomeIcon
size="1x"
icon="arrow-left"
color="#292B83"
/>
<span>Back to home</span>
</Link>
</div>
</div>
);
};
};
export default Confirmation;
The JSX will render a message containing the customer's name and an order reference.
In your App.js
again, attach your order prop to your <Route>
<Checkout>
component instance:
<Route
path="/confirmation"
exact
render={(props) => {
if(!this.state.order) {
return props.history.push('/')
};
return (
<Confirmation
{...props}
order={order}
/>
)
}}
/>
That's it!
You have now wrapped up the full series of the Commerce.js and React demo store guides! You can find the full finished code in GitHub here!