Skip to content

Instantly share code, notes, and snippets.

@deepu105
Created July 7, 2020 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deepu105/c8bf502f45c3ed66953ab27970d05f0e to your computer and use it in GitHub Desktop.
Save deepu105/c8bf502f45c3ed66953ab27970d05f0e to your computer and use it in GitHub Desktop.
MdToHtml.html
<p>E-commerce applications are the backbone of today&#39;s online shopping world. In this post, we will see how to build an e-commerce application easily using Java, JHipster, Spring Boot, and React. Since we will be scaffolding the application, the post will focus on how to build a shopping cart and payment integration rather than how to build a Java web application.</p>
<h2 id="tools-and-technology-we-will-use">Tools and technology we will use</h2>
<p>We will use the below tools and technology to build this application:</p>
<ul>
<li><a href="https://www.jhipster.tech/"><strong>JHipster</strong></a>: JHipster is a rapid application development platform. It can quickly create web applications and microservices with production-grade code. Head over to the <a href="https://www.jhipster.tech/installation/">installation instructions</a> to set it up. JHipster can scaffold applications with a wide variety of languages, frameworks, and configurations. For this tutorial, we will stick with the following major options. You don&#39;t have to install anything for this, as JHipster will manage these for you.<ul>
<li><a href="https://spring.io/"><strong>Spring Framework</strong></a>: Spring is an application framework in Java that comes with all the bells and whistles required for enterprise-grade Java application development. It comes with Spring Boot which makes development faster and convenient. This lets us focus more on our business needs rather than spending time setting up technical integrations.</li>
<li><a href="https://reactjs.org/"><strong>React</strong></a>: A popular JavaScript UI library that helps build modern scalable front ends. We will be writing React code using TypeScript. We will also be using a few other components like Redux and React Router from the React ecosystem.</li>
<li><a href="https://getbootstrap.com/"><strong>Bootstrap</strong></a>: An UI framework for web applications with a variety of themes and customizations.</li>
<li><a href="https://gradle.org/"><strong>Gradle</strong></a>: Gradle is a Java build orchestration tool that provides a highly customizable and easy-to-use domain-specific language (DSL).</li>
<li><a href="https://webpack.js.org/"><strong>Webpack</strong></a>: A front-end build tool for modern web applications</li>
</ul>
</li>
<li><a href="https://www.adyen.com/"><strong>Adyen Payments Platform</strong></a>: Adyen is one of the leading payment platforms for medium to large scale businesses. It provides a plethora of payment options and provides SDKs for easy integrations. And I also happen to work for Adyen 😄</li>
<li><a href="https://www.docker.com/"><strong>Docker</strong></a>: A containerization technology, which we will use it to quickly run our database. Make sure you have Docker and Docker compose installed. If you can run a local MySQL setup, you won&#39;t need Docker.</li>
<li><a href="https://git-scm.com/"><strong>Git</strong></a>: Distributed version control system for source code management. Make sure you have Git installed.</li>
</ul>
<h3 id="prerequisite">Prerequisite</h3>
<p>To follow this tutorial effectively you would need to be familiar with at least the below tools and technology</p>
<ul>
<li>Java</li>
<li>Spring Framework</li>
<li>React</li>
<li>Redux</li>
<li>Bootstrap</li>
</ul>
<blockquote>
<p>We have a <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example">sample application</a> built to accompany this post. Each section here is points to a particular <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commits/master">commit</a> in the sample app to help give you a better picture of what is being changed.</p>
</blockquote>
<h2 id="designing-the-entity-model">Designing the entity model</h2>
<p>Since we are going to scaffold our application, it is important to make sure that we have the correct entity model for the e-commerce application. We will use the <a href="https://www.jhipster.tech/jdl/">JHipster Domain Language(JDL)</a> to do this. Below is the JDL model for an e-commerce application:</p>
<pre><code class="lang-groovy"><span class="hljs-comment">/** Product sold by the Online store */</span>
entity Product {
name String <span class="hljs-keyword">required</span>
description String
price BigDecimal <span class="hljs-keyword">required</span> min(<span class="hljs-number">0</span>)
size Size <span class="hljs-keyword">required</span>
image ImageBlob
}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Size</span> </span>{
S, M, L, XL, XXL
}
<span class="hljs-comment">/** Product categories to group products */</span>
entity ProductCategory {
name String <span class="hljs-keyword">required</span>
description String
}
<span class="hljs-comment">/** Additional details for users as we can't modify built-in user entity vis JDL */</span>
entity CustomerDetails {
gender Gender <span class="hljs-keyword">required</span>
phone String <span class="hljs-keyword">required</span>
addressLine1 String <span class="hljs-keyword">required</span>
addressLine2 String
city String <span class="hljs-keyword">required</span>
country String <span class="hljs-keyword">required</span>
}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Gender</span> </span>{
MALE, FEMALE, OTHER
}
<span class="hljs-comment">/** Shopping cart to hold users orders */</span>
entity ShoppingCart {
placedDate Instant <span class="hljs-keyword">required</span>
status OrderStatus <span class="hljs-keyword">required</span>
totalPrice BigDecimal <span class="hljs-keyword">required</span> min(<span class="hljs-number">0</span>)
paymentMethod PaymentMethod <span class="hljs-keyword">required</span>
}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">OrderStatus</span> </span>{
COMPLETED, PAID, PENDING, CANCELLED
}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">PaymentMethod</span> </span>{
CREDIT_CARD, IDEAL
}
<span class="hljs-comment">/** Product order keeps track of orders */</span>
entity ProductOrder {
quantity Integer <span class="hljs-keyword">required</span> min(<span class="hljs-number">0</span>)
totalPrice BigDecimal <span class="hljs-keyword">required</span> min(<span class="hljs-number">0</span>)
}
<span class="hljs-comment">// Every user will have a customer detail</span>
relationship OneToOne {
CustomerDetails{user(login) <span class="hljs-keyword">required</span>} to User
}
<span class="hljs-comment">// Many product orders can be tracked back to a product</span>
relationship ManyToOne {
ProductOrder{product(name) <span class="hljs-keyword">required</span>} to Product
}
relationship OneToMany {
<span class="hljs-comment">// Every customer can have many shopping carts</span>
CustomerDetails{cart} to ShoppingCart{customerDetails <span class="hljs-keyword">required</span>},
<span class="hljs-comment">// Every shopping cart can have many product orders</span>
ShoppingCart{order} to ProductOrder{cart <span class="hljs-keyword">required</span>},
<span class="hljs-comment">// Every product category can have many products</span>
ProductCategory{product} to Product{productCategory(name) <span class="hljs-keyword">required</span>}
}
<span class="hljs-class"><span class="hljs-keyword">service</span> * <span class="hljs-title">with</span> serviceClass
paginate Product, CustomerDetails, ProductCategory with pagination</span>
</code></pre>
<p>The <code>User</code> entity is built-in from JHipster and hence we don&#39;t have to define it in JDL. However, we can define their relationships. Here is a UML visualization of the same:</p>
<p><img src="https://i.imgur.com/p403lEc.png" alt="Entity model for e-commerce"></p>
<p>Head over to <a href="https://start.jhipster.tech/jdl-studio/">JDL Studio</a> if you want to visualize the model and make any changes.</p>
<p>Next, create a new folder and save the above to a file named <code>app.jdl</code> within that folder.</p>
<h2 id="scaffolding-the-application">Scaffolding the application</h2>
<p>Now that we have our model in place, we can go ahead and scaffold a Java web application using JHipster. First, let&#39;s define our application. Add the below snippet to the file (<code>app.jdl</code>) we created earlier.</p>
<pre><code class="lang-groovy"><span class="hljs-section">application</span> {
<span class="hljs-section">config</span> {
<span class="hljs-attribute">baseName</span> store
packageName com.adyen.demo.store
authenticationType jwt
prodDatabaseType mysql
buildTool gradle
clientFramework react
useSass <span class="hljs-literal">true</span>
enableTranslation <span class="hljs-literal">false</span>
}
entities *
}
</code></pre>
<p>We just defined an application named <strong>store</strong> that uses JSON Web Token (JWT) as the authentication mechanism, MySQL as the production database, Gradle as the build tool and React as the client-side framework. You can see all the options supported by JHipster <a href="https://www.jhipster.tech/jdl/applications">here</a>. We also defined that the application uses all the entities we defined with <code>entities *</code>.</p>
<p>Now, let&#39;s invoke JHipster to scaffold the application. Run the below command inside the folder where we created <code>app.jdl</code>:</p>
<pre><code class="lang-shell"><span class="hljs-title">jhipster</span> <span class="hljs-keyword">import</span>-jdl app.jdl
</code></pre>
<p>This will create our application, install all necessary dependencies, and initialize &amp; commit everything to Git. Make sure you have Git installed on your system.</p>
<p>Let&#39;s check out the application. Run the below command to run the application in development mode:</p>
<pre><code class="lang-shell">./gradlew
</code></pre>
<p>After running the application, visit <a href="https://localhost:8080">https://localhost:8080</a> and use the default users mentioned on the home page to log in and explore the application. You can find the <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/1833f6d96de51ed1838f159c404bee24533fcf4f">commit</a> in the sample application.</p>
<p>You can also run the generated unit and integration tests with this command:</p>
<pre><code class="lang-shell">./gradlew npm_test <span class="hljs-built_in">test</span> integrationTest
</code></pre>
<p>So far, the generated application doesn&#39;t have any specific business logic or custom screens. It is just a CRUD application for the model we defined. If you are familiar with Spring Framework and React you should be able to navigate the source code created easily. The Spring/React application created by JHipster is not the focus of this post, and for that I recommend you head over to documentation provided by JHipster, Spring, and React.</p>
<h2 id="building-a-products-landing-page">Building a products landing page</h2>
<p>Now that our application and all the CRUD APIs are ready, let us build a product landing page that lists all the products offered by the store.</p>
<p>We will convert <code>src/main/webapp/app/modules/home/home.tsx</code> to be our product landing page. This involves updating the JSX to show the products list and using the product redux reducer to fetch the data from product API. <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/8bef6c1737a2fdc4feaedd0751f5dd0b5044f536#diff-718161e5814fcf204ef9e82fdc962d7d">Here</a> is the complete diff for <code>home.tsx</code> and <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/8bef6c1737a2fdc4feaedd0751f5dd0b5044f536">here</a> is the entire changelog for this step.</p>
<p><img src="https://i.imgur.com/kxNLwS8.png" alt="Product listing home page"></p>
<p>Start the application client-side in dev mode to speed up development. Keep the application running in a terminal using <code>./gradlew</code> if it not already running from the previous step. In a new terminal, run <code>npm start</code> and it will start a development server for the client-side, which proxies API calls to the backend and open up a new browser window pointing to <a href="https://localhost:9000">https://localhost:9000</a>.</p>
<p>At this point, the front-end and back-end are running in development mode with hot reload functionality. This means the entire application will automatically reload when we make any changes (the browser will reload as well). For backend changes, the reload will happen when you compile using your IDE or by running <code>./gradlew compileJava</code>.</p>
<p>Update <code>home.tsx</code> according to the <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/8bef6c1737a2fdc4feaedd0751f5dd0b5044f536">changelog</a> and see the changes reflected on the home page.</p>
<h2 id="building-the-shopping-cart">Building the shopping cart</h2>
<p>Now let us build a persistent shopping cart page, where we can list all the items added to the cart by the user. The user can also start checkout from this page. The shopping cart will hold the items added until the payment is complete even if the user logs out or uses the application in a different machine as the state is persisted automatically using the generated CRUD API:</p>
<p><img src="https://i.imgur.com/vLcJ7Ly.png" alt="Shopping cart"></p>
<p>For this feature, we also add/update the below on the server-side:</p>
<ul>
<li><a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-ff9a3a62938424f5f25aa23632e0130b">security configurations</a> to ensure that a user can update only his/her shopping cart when logged in. Only administrators will be able to see the shopping cart of other users and manage all entities.</li>
<li><a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-d193fb13f737cee08eebe3d91ad724ef">New REST endpoints</a> to add and remove products to and from a shopping cart.</li>
<li><a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-5ba862ad08c22a3dbadff2c8aaf7ee31">Service methods</a></li>
<li><a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-67890efa54cdc134785c6d61ae09dec5">Database operations</a>.</li>
</ul>
<p>These updates are quite straightforward due to the framework provided by JHipster and Spring.</p>
<p>On the client-side, we will update:</p>
<ul>
<li><a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-a3e50072c5d00cf79e3b620f3c5103c0">shopping-cart reducer</a> to talk to the new endpoints.</li>
<li>Add a new route and <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c#diff-ea8adcc088ab63d7cf36005231489d6e">module</a> to show the shopping cart.</li>
</ul>
<p>The shopping cart React page uses the below snippet. Note that the listing content is quite similar to the product listing page.</p>
<pre><code class="lang-tsx">//import ...;
export interface ICartProp extends StateProps, DispatchProps {}
export const Cart = (props: ICartProp) =&gt; {
useEffect(() =&gt; {
props.getEntityForCurrentUser();
}, []);
const remove = id =&gt; () =&gt; {
props.removeOrder(id);
};
const { isAuthenticated, cart, loading } = props;
return (
<span class="hljs-tag">&lt;<span class="hljs-name">Row</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex justify-content-center"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">lg</span>=<span class="hljs-string">"9"</span> <span class="hljs-attr">md</span>=<span class="hljs-string">"12"</span>&gt;</span>
{isAuthenticated ? (
<span class="hljs-tag">&lt;&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Your shopping cart<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"lead"</span>&gt;</span>You have {cart?.orders?.length} items in your cart<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{cart.orders &amp;&amp; cart.orders.length &gt; 0 ? (
<span class="hljs-tag">&lt;&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"list-group"</span>&gt;</span>
{cart.orders.map((order, i) =&gt; (
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"list-group-item list-group-item-action flex-column align-items-start"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"row"</span>&gt;</span>{/*... list content */}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
))}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex justify-content-between py-4"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>
Total price: <span class="hljs-tag">&lt;<span class="hljs-name">TextFormat</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{cart.totalPrice</span> <span class="hljs-attr">as</span> <span class="hljs-attr">any</span>} <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">format</span>=<span class="hljs-string">{</span>'$ <span class="hljs-attr">0</span>,<span class="hljs-attr">0.00</span>'} /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">checkout</span>`} <span class="hljs-attr">color</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"lg"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">FontAwesomeIcon</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">"cart-arrow-down"</span> /&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-none d-md-inline"</span>&gt;</span>Checkout<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/&gt;</span>
) : (
!loading &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"alert alert-warning"</span>&gt;</span>No items found<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
)}
<span class="hljs-tag">&lt;/&gt;</span>
) : (
<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Alert</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"warning"</span>&gt;</span>Not authorized. Please log in first<span class="hljs-tag">&lt;/<span class="hljs-name">Alert</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
)}
<span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span>
);
};
const mapStateToProps = ({ authentication, shoppingCart }: IRootState) =&gt; ({
isAuthenticated: authentication.isAuthenticated,
cart: shoppingCart.entity,
loading: shoppingCart.loading
});
const mapDispatchToProps = {
getEntityForCurrentUser,
removeOrder
};
type StateProps = ReturnType<span class="hljs-tag">&lt;<span class="hljs-name">typeof</span> <span class="hljs-attr">mapStateToProps</span>&gt;</span>;
type DispatchProps = typeof mapDispatchToProps;
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
</code></pre>
<p>Here is the entire <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/b23f455e8105e32a2154ca9ee9544231355cf57c">changelog</a> for this feature. Make the changes to the application and see the changes reflected on the shopping cart page.</p>
<p>Please note that I also made some improvements to the fake data generated by JHipster in <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/8dacb111116517b0cebfefb10c7a8680960dcea0">this commit</a> and made improvements to product and cart pages in <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/91f49fc6708b78862c9ea003e317aea7040e7335">this commit</a>. I also fixed the tests in <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/0787f9b5a868799073a602b2f5245e82dda1f3cc">this commit</a>. Update your application according to these changelogs as well.</p>
<h2 id="payments-integration">Payments integration</h2>
<p>Now that our shopping cart is ready, we can integrate the <a href="https://docs.adyen.com/checkout">Adyen checkout API</a> to make payments. First, make sure you <a href="https://www.adyen.com/signup">sign up</a> for an Adyen test account. Follow <a href="https://docs.adyen.com/checkout/get-started">this guide</a> to get your API keys and Merchant Account. You will also need to <a href="https://docs.adyen.com/user-management/how-to-get-an-origin-key">generate an origin key</a> per domain you use to collect payments. In our case for development use, we need to create an origin key for <code>http://localhost:9000</code> and <code>http://localhost:8080</code>.</p>
<p><img src="https://i.imgur.com/79mZfzr.png" alt="Payment page"></p>
<p>We will use the <a href="https://github.com/Adyen/adyen-java-api-library">Adyen Java API library</a> to make API calls. We will add the dependency to our <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-c197962302397baf3a4cc36463dce5ea">Gradle build</a>.</p>
<pre><code class="lang-groovy">implementation <span class="hljs-string">group:</span> <span class="hljs-string">"com.adyen"</span>, <span class="hljs-string">name:</span> <span class="hljs-string">"adyen-java-api-library"</span>, <span class="hljs-string">version:</span> <span class="hljs-string">"5.0.0"</span>
</code></pre>
<p>We also need to exclude the Adyen domain in the <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-ff9a3a62938424f5f25aa23632e0130b">content security policy</a> defined in <code>src/main/java/com/adyen/demo/store/config/SecurityConfiguration.java</code>.</p>
<p>We will create a new Spring REST controller that will use the Adyen Java library and make payment API calls for us. <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-a8a69c0ec7bcbfd1141d8a56cfd4d9e3">Here</a> is the <code>src/main/java/com/adyen/demo/store/web/rest/CheckoutResource.java</code> class. Here is a method from this class.</p>
<pre><code class="lang-java">@PostMapping(<span class="hljs-string">"/checkout/payment-methods"</span>)
public ResponseEntity&lt;PaymentMethodsResponse&gt; paymentMethods() throws EntityNotFoundException, IOException, ApiException {
PaymentMethodsRequest paymentMethodsRequest = new PaymentMethodsRequest()<span class="hljs-comment">;</span>
paymentMethodsRequest.setMerchantAccount(merchantAccount)<span class="hljs-comment">;</span>
paymentMethodsRequest.setCountryCode(<span class="hljs-string">"NL"</span>)<span class="hljs-comment">;</span>
paymentMethodsRequest.setShopperLocale(<span class="hljs-string">"nl-NL"</span>)<span class="hljs-comment">;</span>
paymentMethodsRequest.setChannel(PaymentMethodsRequest.ChannelEnum.WEB)<span class="hljs-comment">;</span>
Amount amount = getAmountFromCart()<span class="hljs-comment">;</span>
paymentMethodsRequest.setAmount(amount)<span class="hljs-comment">;</span>
log.debug(<span class="hljs-string">"REST request to get Adyen payment methods {}"</span>, paymentMethodsRequest)<span class="hljs-comment">;</span>
PaymentMethodsResponse response = checkout.paymentMethods(paymentMethodsRequest)<span class="hljs-comment">;</span>
return ResponseEntity.ok()
.<span class="hljs-keyword">body(response);
</span>}
</code></pre>
<p>The controller ensures that all actions are done against the active shopping cart of the user logged into the session. This ensures that security issues like man-in-the-middle attacks and request spoofing do not happen. When payment is completed successfully, we close the active shopping cart, ensuring every user has only one active shopping cart at a time.</p>
<p>On the client-side, we will create a React page to show the <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-3723704a01d50dc81a81a4a3e56936eb">payment options</a> and payment <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-8e5b7843a18c84ef4f242a352d2572fe">result status</a>, a <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-6d93eb93377c66204b2e844472d95860">redux reducer</a> to talk to the new API endpoints. We will also download and add the Adyen client-side resources to our <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e#diff-747e7a37d03935942014106202566830">index.html</a> file.</p>
<p>Here are the important bits of the checkout page since this is where we handle the Adyen javascript integration from within React.</p>
<pre><code class="lang-tsx"><span class="hljs-comment">//import ...;</span>
<span class="hljs-keyword">export</span> interface ICheckoutProp extends StateProps, DispatchProps {}
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CheckoutContainer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span>&lt;<span class="hljs-title">ICheckoutProp</span>&gt; </span>{
private paymentContainer = React.createRef&lt;HTMLDivElement&gt;();
<span class="hljs-comment">//...</span>
componentDidMount() {
<span class="hljs-keyword">this</span>.props.getEntityForCurrentUser();
<span class="hljs-keyword">this</span>.props.getAdyenConfig();
<span class="hljs-keyword">this</span>.props.getPaymentMethods();
}
componentDidUpdate(prevProps: ICheckoutProp) {
<span class="hljs-keyword">const</span> { paymentMethodsRes, config, paymentRes, paymentDetailsRes, errorMessage } = <span class="hljs-keyword">this</span>.props;
<span class="hljs-keyword">if</span> (errorMessage &amp;&amp; errorMessage !== prevProps.errorMessage) {
<span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`/status/error?reason=<span class="hljs-subst">${errorMessage}</span>`</span>;
<span class="hljs-keyword">return</span>;
}
<span class="hljs-keyword">if</span> (paymentMethodsRes &amp;&amp; config &amp;&amp; (paymentMethodsRes !== prevProps.paymentMethodsRes || config !== prevProps.config)) {
<span class="hljs-keyword">this</span>.checkout = <span class="hljs-keyword">new</span> AdyenCheckout({
...config,
<span class="hljs-attr">paymentMethodsResponse</span>: <span class="hljs-keyword">this</span>.removeNilFields(paymentMethodsRes),
<span class="hljs-attr">onAdditionalDetails</span>: <span class="hljs-keyword">this</span>.onAdditionalDetails,
<span class="hljs-attr">onSubmit</span>: <span class="hljs-keyword">this</span>.onSubmit
});
}
<span class="hljs-keyword">if</span> (paymentRes &amp;&amp; paymentRes !== prevProps.paymentRes) {
<span class="hljs-keyword">this</span>.processPaymentResponse(paymentRes);
}
<span class="hljs-keyword">if</span> (paymentRes &amp;&amp; paymentDetailsRes !== prevProps.paymentDetailsRes) {
<span class="hljs-keyword">this</span>.processPaymentResponse(paymentDetailsRes);
}
}
removeNilFields = <span class="hljs-function"><span class="hljs-params">obj</span> =&gt;</span> {
<span class="hljs-comment">//...</span>
};
processPaymentResponse = <span class="hljs-function"><span class="hljs-params">paymentRes</span> =&gt;</span> {
<span class="hljs-keyword">if</span> (paymentRes.action) {
<span class="hljs-keyword">this</span>.paymentComponent.handleAction(paymentRes.action);
} <span class="hljs-keyword">else</span> {
<span class="hljs-comment">//...</span>
<span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`/checkout/status/<span class="hljs-subst">${urlPart}</span>?reason=<span class="hljs-subst">${paymentRes.resultCode}</span>&amp;paymentType=unknown`</span>;
}
};
onSubmit = <span class="hljs-function">(<span class="hljs-params">state, component</span>) =&gt;</span> {
<span class="hljs-keyword">if</span> (state.isValid) {
<span class="hljs-keyword">this</span>.props.initiatePayment({
...state.data,
<span class="hljs-attr">origin</span>: <span class="hljs-built_in">window</span>.location.origin
});
<span class="hljs-keyword">this</span>.paymentComponent = component;
}
};
onAdditionalDetails = <span class="hljs-function">(<span class="hljs-params">state, component</span>) =&gt;</span> {
<span class="hljs-keyword">this</span>.props.submitAdditionalDetails(state.data);
<span class="hljs-keyword">this</span>.paymentComponent = component;
};
handlePaymentSelect = <span class="hljs-function">(<span class="hljs-params">type: string</span>) =&gt;</span> () =&gt; {
<span class="hljs-keyword">this</span>.checkout.create(type).mount(<span class="hljs-keyword">this</span>.paymentContainer?.current);
};
render() {
<span class="hljs-keyword">const</span> { cart } = <span class="hljs-keyword">this</span>.props;
<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Row</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex justify-content-center"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">minHeight:</span> '<span class="hljs-attr">80vh</span>' }}&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">lg</span>=<span class="hljs-string">"9"</span> <span class="hljs-attr">md</span>=<span class="hljs-string">"12"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Make payment<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"lead"</span>&gt;</span>You are paying total of € {cart.totalPrice}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Row</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-4"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">md</span>=<span class="hljs-string">"4"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex flex-column"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Choose a payment type<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonGroup</span> <span class="hljs-attr">vertical</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.handlePaymentSelect(</span>'<span class="hljs-attr">card</span>')}&gt;</span>Credit Card<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.handlePaymentSelect(</span>'<span class="hljs-attr">ideal</span>')}&gt;</span>iDEAL<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ButtonGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">md</span>=<span class="hljs-string">"8"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{this.paymentContainer}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"payment"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span></span>
);
}
}
<span class="hljs-keyword">const</span> mapStateToProps = <span class="hljs-function">(<span class="hljs-params">{ checkout, shoppingCart }: IRootState</span>) =&gt;</span> ({
<span class="hljs-comment">//...</span>
});
<span class="hljs-keyword">const</span> mapDispatchToProps = {
<span class="hljs-comment">//...</span>
};
type StateProps = ReturnType&lt;<span class="hljs-keyword">typeof</span> mapStateToProps&gt;;
type DispatchProps = <span class="hljs-keyword">typeof</span> mapDispatchToProps;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer);
</code></pre>
<p>Here is the entire <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/30899562564be5f2b6dd1291af812c3f4ff8e22e">changelog</a> for this feature. Make the changes to the application accordingly and see the changes reflected on the shopping cart page. Make sure to set the following environment variables first.</p>
<pre><code class="lang-shell"><span class="hljs-keyword">export</span> ADYEN_API_KEY=yourAdyenApiKey
<span class="hljs-keyword">export</span> ADYEN_MERCHANT_ACCOUNT=yourAdyenMerchantAccount
<span class="hljs-keyword">export</span> ADYEN_ORIGIN_KEY=yourAdyenOriginKeyForCorrectDomain
</code></pre>
<h2 id="running-the-app-in-production">Running the app in production</h2>
<p>Now that we have made all the required changes, let us compile and run our app in production mode.</p>
<p>First, let us run the generated unit and integration tests to ensure we haven&#39;t broken anything:</p>
<pre><code class="lang-shell">./gradlew npm_test <span class="hljs-built_in">test</span> integrationTest
</code></pre>
<p>Now, let&#39;s start a MySQL database as our application uses an in-memory H2 database for development and MySQL for production, this makes development easier. We will be using Docker compose to run the DB. You can also manually run a MySQL DB if you prefer.</p>
<pre><code class="lang-shell">docker-compose <span class="hljs-_">-f</span> src/main/docker/mysql.yml up <span class="hljs-_">-d</span>
</code></pre>
<p>The above command will start up MySQL DB from the included Docker compose file. Now, run the below command to run the application in production mode:</p>
<pre><code class="lang-shell">./gradlew -Pprod
</code></pre>
<p>You can also package the application using the command <code>./gradlew -Pprod clean bootJar</code> and then run the JAR using <code>java -jar build/libs/*.jar</code></p>
<p>Now, visit <a href="https://localhost:8080">https://localhost:8080</a> and use the default users mentioned on the home page to log in and explore the application. You can use the <a href="https://docs.adyen.com/development-resources/test-cards/test-card-numbers">test cards</a> from Adyen to simulate payments</p>
<h2 id="conclusion">Conclusion</h2>
<p>That&#39;s it. We have successfully built an e-commerce application complete with a product checkout and payment flow that can accept multiple forms of payment. I highly recommend that you check out the <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example">sample application</a> to get a better context of what has been built.</p>
<p>The example application also has a <a href="https://github.com/adyen-examples/adyen-java-react-ecommerce-example/commit/172eb995a83dea1dcd45140732aaf2906fb15afd">user registration flow</a> that you can checkout</p>
<p>I hope this helps anyone trying to build shopping carts and payment flows for their Java E-Commerce application.</p>
<hr>
<p>If you like this article, please leave a like or a comment. Let us know if you would like to see more of similar content on our blogs.</p>
<p>Cover image credit: Photo by <a href="https://unsplash.com/@servuspaul?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Paul Felberbauer</a> on <a href="https://unsplash.com/s/photos/online-payment?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment