Building an eCommerce site using Commercetools
Ivo Bronsveld
2015-01-07
UPDATE: Bloomreach created an out-of-the-box way of integrating with commercetools.
Many of my conversations with clients and prospects deal with integrations, often about eCommerce. As the importance of valuable content in online commerce increases, the demand for a solution that can do both increases as well. In the last months, I have been working closely with the people at Commercetools to build that combined solution that provides the best of both worlds.
As a part of this, I created this lab, where I will show you how to integrate the commercetools APIs in your Hippo site to create a full content driven eCommerce experience.
So, what is commercetools?
Commercetools is a really powerful eCommerce solution that provides APIs to interface with their systems. This makes it an ideal solution to use in this case. You can read more about the product over at http://www.commercetools.com
In this post, we can not cover every single aspect of the integration. However, we will cover two major parts of the integration:
1. Dynamic Pricing & Product Data
Using commercetools to provide price and product information to our visitor
2. Add To Cart : Implement an Add to Cart feature to store the product in the cart.
Before we start, remember that there are many different approaches to integrating both Hippo and commercetools. In this post, I will show the most simple, straightforward way the integration can be done. Please note, that it does not necessarily reflect the best practice of doing this integration.
Step 1: Setting up commercetools
Set up your commercetools environment by following the instructions on : http://dev.sphere.io/getting-started.html which has the following steps:
- Sign up for an account
- Create a first project
- Select your preferred language
- Create sample data
- Retrieve API Keys
- Launch a first sample application
Step 2: Setting up the Hippo project
We could start from scratch, building a new Hippo CMS project, or use the 2nd zip file available on the Building a website tutorial and import this project in the IDE of your choice.
Build and run the project using the (probably) familiar Maven commands:
mvn clean install mvn –Pcargo.run
When it is finished building and the application is up and running, open the browser and visit the site on http://localhost:8080/site.
As you can see, it is a fully functioning site, complete with a product section.
Currently, the data is provided through Hippo documents. For some parts of the data this is fine, but for the price this definitely is not. So, let's change this.
Step 3: Setting up the SDK
In order to get the price from commercetools, we will need to connect to the commercetools API. Luckily, commercetools provides a JVM SDK for this.
We will install the SDK according to the instructions here:
https://github.com/sphereio/sphere-jvm-sdk/blob/master/README.md
First, we will add the references in the site pom.xml
<dependency> <groupId>io.sphere.sdk.jvm</groupId> <artifactId>sphere-models</artifactId> <version>1.0.0-M16</version> </dependency> <dependency> <groupId>io.sphere.sdk.jvm</groupId> <artifactId>sphere-java-client</artifactId> <version>1.0.0-M16</version> </dependency>
This will enable our project to use the SDK of commercetools, making it easy to get and send data to the environment.
Note: If you are using the Hippo CMS 10.X version, you will have to override some other dependencies as well:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-parameter-names</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.6.0</version> </dependency>
Step 4: Dynamic Pricing
As you can see when you open one of the products in the java CMS, the price is stored as a field in the document itself. For demo purposes this is fine, but in real life this is usually not the case.
Both Hippo Java CMS and commercetools are really flexible when it comes to 'data' and the structure of that data. For this example, we will separate between 'display' content, such as the description, stored in Hippo CMS and 'commerce' content, such as the price, stored in commercetools.
Note:
There are many scenarios where it makes sense to have one system or the other hold certain pieces of information. This depends, most of all, on the process the customer would like to use.
In order to get the price from commercetools, we will use the API provided by commercetools to get the details of the product. The documentation here shows us a very promising method we could use to get the data from the API.
As we can see, the method will return a nice JSON structure containing the price of the product. But, we need to provide it with a Product ID. Currently, our data model does not have this information at all, so we will need to expand the data model with an ID field.
To do this, we will open the Document Type Editor in the CMS. Open the product document type and click Edit. Add a string field, name it productID and add the correct name and hints if you will. Click Done and Type Actions, Commit to write the changes.
While we are still in the java CMS, be sure to open the product documents and add a ID to the field. For now, just use any of the SKUs of the demo products in your store.
The next step is to recreate the beans. We will use the Bean Writer for this. Open the browser and go the Essentials Setup tool (http://localhost:8080/essentials/). Go to Tools, Beanwriter and use this to create our new field in our beans so we could use this from our code.
Getting the price from commercetools
In order to get the price from commercetools, we will need to add the code to connect, authenticate and get the product info from commercetools. We will start by adding this to the detail page of the product. Currently, the component that handles the business logic of the product detail page is the EssentialsContentComponent (for more info, check out this page. In order to add our own business logic, we will create a custom component.
In your IDE, create a package for our component (something like org.example.components). In this package, create a new class called ProductDetailComponent. Extend this from the EssentialsContentComponent. Create an override for the doBeforeRender method, but make sure you have it call the parent class as well.
package org.example.components; import org.hippoecm.hst.core.component.HstRequest; import org.hippoecm.hst.core.component.HstResponse; import org.onehippo.cms7.essentials.components.EssentialsContentComponent; public class ProductDetailComponent extends EssentialsContentComponent { @Override public void doBeforeRender(HstRequest request, HstResponse response) { // Ensure the component loads the content and makes it available to the template super.doBeforeRender(request, response); } }
Because we trigger the method on our parent class, we have the data that is loaded by this component. In this case, the data is stored in the document attribute on the request. We can use this data and get our product ID from the product, like this:
// Load the product from the request attribute "document" Product product = (Product) request.getAttribute("document"); // TODO: Get data from commercetools
We can now use the SDK to create a request to the actual store API.
The method we will use is this, so replace the TODO with the following code:
final SphereClientFactory factory = SphereClientFactory.of(SphereAsyncHttpClientFactory::create); final SphereClient client = factory.createClient( "KEY", //replace with your project key "client", //replace with your client id "secret"); //replace with your client secret final ProductQuery query = ProductQuery.of(); // Get the SKU from the product document in Hippo CMS String sku = product.getProductID(); if (sku != null) { // Use the current, not the preview version ProductQuery q = query.bySku(sku, ProductProjectionType.CURRENT); try { PagedQueryResult<io.sphere.sdk.products.Product> pagedQueryResult = client.execute(q).toCompletableFuture().get(); // Price is saved in masterdata in the first price object List<Price> prices = pagedQueryResult.getResults().get(0).getMasterData().getCurrent().getMasterVariant().getPrices(); // We will use the first one Price price = prices.get(0); request.setAttribute("commerceprice", price); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
Note: this way of creating the client is *NOT* recommended in production scenarios, as this will quickly result in issues. Normally, you would create the client once and not every request.
Let's update the component in the page that is used to render the product details. In the console, open the gogreen/hst:pages/productpage/main/left node. Change the component there to: org.example.components.ProductDetailComponent
We have stored the product pricing details in a variable called commerceprice, which we will use in the template.
Open the productpage-main.ftl file and look for the lines at the bottom where the price is rendered. Replace the lines with the following:
<#if commerceprice??> <span class="price"> <@fmt.formatNumber value="${commerceprice.value.number}" type="currency" currencyCode="${commerceprice.value.currency}" /> </span> </#if>
Did it work?
We have created a component, added the client logic and we updated the template and Hippo configuration. Let's see if this works by rebuilding the system.
mvn clean install mvn -Pcargo.run
The image shows the price, just as it is in the commercetools admin environment.
Right now we do this with just the price, but obviously more data could be provided in a similar fashion.
Step 5: Add an "Add to cart" button
In order to add a product to the cart, we will have to respond to user interaction. With Hippo, this is easy to do. Just make the component respond a POST request as well.
Open the productpage-main.ftl file and look for the lines at the bottom where we changed how the price is rendered. Add the following lines below it:
<form method="post" action="<@hst.actionURL />"> <input name="sku" type="hidden" value="${document.productID}"/> <button class="btn btn-lg pull-right" type="submit"> ADD TO CART </button> </form>
This will make sure the template can post the data to our component, but we did not implement any logic in our component that will actually handle the POST action. Open the component and add the following:
@Override public void doAction(HstRequest request, HstResponse response) throws HstComponentException { super.doAction(request, response); // Load the data from the form FormMap map = new FormMap(request, new String[]{"sku"}); // Load the SKU from the field String sku = map.getField("sku").getValue(); final SphereClientFactory factory = SphereClientFactory.of(SphereAsyncHttpClientFactory::create); final SphereClient client = factory.createClient( "KEY", //replace with your project key "client", //replace with your client id "secret"); //replace with your client secret // Set currency and country CurrencyUnit eur = Monetary.getCurrency("EUR"); CountryCode nl = CountryCode.getByCode("NL"); final CartDraft cartDraft = CartDraft.of(eur).withCountry(nl); // Right now, we will create a cart (even if it exists) CartCreateCommand cartCreateCommand = CartCreateCommand.of(cartDraft); // Execute creation CompletionStage<Cart> cartCompletionStage = client.execute(cartCreateCommand); // Set the right values Cart cart = null; try { cart = cartCompletionStage.toCompletableFuture().get(); map.addMessage("result", "Product added to the cart!"); } catch (InterruptedException e) { map.addMessage("result", "Error occurred: " + e.getMessage()); e.printStackTrace(); } catch (ExecutionException e) { map.addMessage("result", "Error occurred: " + e.getMessage()); e.printStackTrace(); } // Persist the result to the component FormUtils.persistFormMap(request, response, map, null); }
Hippo will trigger this method when the form is posted. This is done using the PRG pattern. Read more about this here. After the doAction, Hippo will redirect the page again. This means that we will need to persist the form data (which includes our results) in order to display a result.
By adding the following in the doBeforeRender method, we will use the result:
// Check if there is a form available FormMap map = new FormMap(); FormUtils.populate(request, map); // Get the result String result = map.getMessage().get("result"); if (result != null) { request.setAttribute("result", result); }
Finally, we will make sure our template displays the result as well. Place this at just below the first div:
<#if result??> <#if !result?starts_with("Error")> <div class="alert alert-success"> <#else> <div class="alert alert-danger"> </#if> ${result} </div>
Rebuild the solution and voila:
Conclusion
In this very straight forward example I hope to have shown the potential power of the commercetools - Hippo CMS combination.