Nowadays it’s essential to have an online presence when running a business. A lot more shopping is done online than in previous years. Having an e-commerce store allows shop owners to open up other streams of revenue they couldn’t take advantage of with just a brick and mortar store. Other shop owners however, run their businesses online entirely without a physical presence. This makes having an online store crucial.
Sites such as Etsy, Shopify and Amazon make it easy to set up a store pretty quickly without having to worry about developing a site. However, there may be instances where shop owners may want a personalized experience or maybe save on the cost of owning a store on some of these platforms.
Headless e-commerce API platforms provide backends that store sites can interface with. They manage all processes and data related to the store like customer, orders, shipments, payments, and so on. All that’s needed is a frontend to interact with this information. This gives owners a lot of flexibility when it comes to deciding how their customers will experience their online store and how they choose to run it.
In this article, we will cover how to build an e-commerce store using Angular 11. We shall use Commerce Layer as our headless e-commerce API. Although there may be tonnes of ways to process payments, we’ll demonstrate how to use just one, Paypal.
Prerequisites #
Before building the app, you need to have Angular CLI installed. We shall use it to initialize and scaffold the app. If you don’t have it installed yet, you can get it through npm.
You’ll also need a Commerce Layer developer account. Using the developer account, you will need to create a test organization and seed it with test data. Seeding makes it easier to develop the app first without worrying about what data you’ll have to use. You can create an account at this link and an organization here.
Lastly, you will need a Paypal Sandbox account. Having this type of account will allow us to test transactions between businesses and users without risking actual money. You can create one here. A sandbox account has a test business and test personal account already created for it.
Commerce Layer And Paypal Config #
To make Paypal Sandbox payments possible on Commerce Layer, you’ll need to set up API keys. Head on over to the accounts overview of your Paypal developer account. Select a business account and under the API credentials tab of the account details, you will find the Default Application under REST Apps.
To associate your Paypal business account with your Commerce Layer organization, go to your organization’s dashboard. Here you will add a Paypal payment gateway and a Paypal payment method for your various markets. Under Settings > Payments, select Payment Gateways > Paypal and add your Paypal client Id and secret.
After creating the gateway, you will need to create a Paypal payment method for each market you are targeting to make Paypal available as an option. You’ll do this under Settings > Payments > Payment Methods > New Payment Method.
A Note About Routes Used #
Commerce Layer provides a route for authentication and another different set of routes for their API. Their /oauth/token
authentication route exchanges credentials for a token. This token is
required to access their API. The rest of the API routes take the
pattern /api/:resource
.
The scope of this article only covers the frontend portion of this app. I opted to store the tokens server side, use sessions to track ownership, and provide http-only cookies with a session id to the client. This will not be covered here as it is outside the scope of this article. However, the routes remain the same and exactly correspond to the Commerce Layer API. Although, there are a couple of custom routes not available from the Commerce Layer API that we’ll use. These mainly deal with session management. I’ll point these out as we get to them and describe how you can achieve a similar result.
Another inconsistency you may notice is that the request bodies differ from what the Commerce Layer API requires. Since the requests are passed on to another server to get populated with a token, I structured the bodies differently. This was to make it easier to send requests. Whenever there are any inconsistencies in the request bodies, these will be pointed out in the services.
Since this is out of scope, you will have to decide how to store tokens securely. You’ll also need to slightly modify request bodies to match exactly what the Commerce Layer API requires. When there is an inconsistency, I will link to the API reference and guides detailing how to correctly structure the body.
App Structure #
To organize the app, we will break it down into four main parts. A better description of what each of the modules does is given under their corresponding sections:
- the core module,
- the data module,
- the shared module,
- the feature modules.
The feature modules will group related pages and components together. There will be four feature modules:
- the auth module,
- the product module,
- the cart module,
- the checkout module.
As we get to each module, I’ll explain what its purpose is and break down its contents.
Below is a tree of the src/app
folder and where each module resides.
Generating The App And Adding Dependencies #
We’ll begin by generating the app. Our organization will be called The LIme Brand and will have test data already seeded by Commerce Layer.
We’ll need a couple of dependencies. Mainly Angular Material and Until Destroy. Angular Material will provide components and styling. Until Destroy automatically unsubscribes from observables when components are destroyed. To install them run:
Assets #
When adding addresses to Commerce Layer, an alpha-2 country code
needs to be used. We’ll add a json file containing these codes to the assets
folder at assets/json/country-codes.json
. You can find this file linked here.
Styles #
The components we’ll create share some global styling. We shall place them in styles.css
which can be found at this link.
Environment #
Our configuration will consist of two fields. The apiUrl
which should point to the Commerce Layer API. apiUrl
is used by the services we will create to fetch data. The clientUrl
should be the domain the app is running on. We use this when setting redirect URLs for Paypal. You can find this file at this link.
Shared Module #
The shared module will contain services, pipes, and components shared across the other modules.
It consists of three components, one pipe, and two services. Here’s what that will look like.
We
shall also use the shared module to export some commonly used Angular
Material components. This makes it easier to use them out of the box
instead of importing each component across various modules. Here’s what shared.module.ts
will contain.
Components #
Item Quantity Component #
This component sets the quantity of items when adding them to the cart. It will be used in the cart and products modules. A material selector would have been an easy choice for this purpose. However, the style of the material select didn’t match the material inputs used in all the other forms. A material menu looked very similar to the material inputs used. So I decided to create a select component with it instead.
The component will have three input properties and one output property. quantity
sets the initial quantity of items, maxValue
indicates the maximum number of items that can be selected in one go, and disabled
indicates whether the component should be disabled or not. The setQuantityEvent
is triggered when a quantity is selected.
When the component is initialized, we’ll set the values that appear on the material menu. There also exists a method called setQuantity
that will emit setQuantityEvent
events.
This is the component file.
This is its template.
Here is its styling.
Title Component #
This component doubles as a stepper title as well as a plain title on some simpler pages. Although Angular Material provides a stepper component, it wasn’t the best fit for a rather long checkout process, wasn’t as responsive on smaller displays, and required a lot more time to implement. A simpler title however could be repurposed as a stepper indicator and be useful across multiple pages.
The component has four input properties: a title
, a subtitle
, a number (no
), and centerText
, to indicate whether to center the text of the component.
Below is its template. You can find its styling linked here.
Simple Page Component #
There are multiple instances where a title, an icon, and a button
were all that were needed for a page. These include a 404 page, an empty
cart page, an error page, a payment page, and an order placement page.
That’s the purpose the simple page component will serve. When the button
on the page is clicked, it will either redirect to a route or perform
some action in response to a buttonEvent
.
To make it:
This is its component file.
And its template contains:
It’s styling can be found here.
Pipes #
Word Wrap Pipe #
Some products’ names and other types of information displayed on the site are really long. In some instances, getting these long sentences to wrap in material components is challenging. So we’ll use this pipe to cut the sentences down to a specified length and add ellipses to the end of the result.
To create it run:
It will contain:
Services #
HTTP Error Handler Service #
There are quite a number of http services in this project. Creating an error handler for each method is repetitive. So creating one single handler that can be used by all methods makes sense. The error handler can be used to format an error and also pass on the errors to other external logging platforms.
Generate it by running:
This service will contain only one method. The method will format the error message to be displayed depending on whether it’s a client or server error. However, there is room to improve it further.
Local Storage Service #
We shall use local storage to keep track of the number of items in a cart. It’s also useful to store the Id of an order here. An order corresponds to a cart on Commerce Layer.
To generate the local storage service run:
The service will contain four methods to add, delete, and get items from local storage and another to clear it.
Data Module #
This module is responsible for data retrieval and management. It’s what we’ll use to get the data our app consumes. Below is its structure:
To generate the module run:
Models #
The models define how the data we consume from the API is structured. We’ll have 16 interface declarations. To create them run:
The following table links to each file and gives a description of what each interface is.
Interface | Description |
---|---|
Address | Represents a general address. |
Cart | Client side version of an order tracking the number of products a customer intends to purchase. |
Country | Alpha-2 country code. |
Customer Address | An address associated with a customer. |
Customer | A registered user. |
Delivery Lead Time | Represents the amount of time it will take to delivery a shipment. |
Line Item | An itemized product added to the cart. |
Order | A shopping cart or collection of line items. |
Payment Method | A payment type made available to an order. |
Payment Source | A payment associated with an order. |
Paypal Payment | A payment made through Paypal |
Price | Price associated with an SKU. |
Shipment | Collection of items shipped together. |
Shipping Method | Method through which a package is shipped. |
SKU | A unique stock-keeping unit. |
Stock Location | Location that contains SKU inventory. |
Services #
This folder contains the services that create, retrieve, and manipulate app data. We’ll create 11 services here.
Address Service #
This service creates and retrieves addresses. It’s important when creating and assigning shipping and billing addresses to orders. It has two methods. One to create an address and another to retrieve one.
The route used here is /api/addresses
. If you’re going to use the Commerce Layer API directly, make sure to structure the data as demonstrated in this example.
Cart Service #
The cart is responsible for maintaining the quantity of items added and the order Id. Making API calls to get the number of items in an order everytime a new line item is created can be expensive. Instead, we could just use local storage to maintain the count on the client. This eliminates the need to make unnecessary order fetches every time an item is added to the cart.
We also use this service to store the order Id. A cart corresponds to an order on Commerce Layer. Once the first item is added to the cart, an order is created. We need to preserve this order Id so we can fetch it during the checkout process.
Additionally, we need a way to communicate to the header that an item
has been added to the cart. The header contains the cart button and
displays the amount of items in it. We’ll use an observable of a BehaviorSubject
with the current value of the cart. The header can subscribe to this and track changes in the cart value.
Lastly, once an order has been completed the cart value needs to be cleared. This ensures that there’s no confusion when creating subsequent newer orders. The values that were stored are cleared once the current order is marked as placed.
We’ll accomplish all this using the local storage service created earlier.
Country Service #
When adding addresses on Commerce Layer, the country code has to be an alpha 2 code. This service reads a json file containing these codes for every country and returns it in its getCountries
method.
Customer Address Service #
This service is used to associate addresses with customers. It also
fetches a specific or all addresses related to a customer. It is used
when the customer adds their shipping and billing addresses to their
order. The createCustomer
method creates a customer, getCustomerAddresses
gets all of a customer’s addresses, and getCustomerAddress
gets a specific one.
When creating a customer address, be sure to structure the post body according to this example.
Customer Service #
Customers are created and their information retrieved using this
service. When a user signs up, they become a customer and are created
using the createCustomerMethod
. getCustomer
returns the customer associated with a specific Id. getCurrentCustomer
returns the customer currently logged in.
When creating a customer, structure the data like this. You can add their first and last names to the metadata, as shown in its attributes.
The route /api/customers/current
is not available on Commerce Layer. So you’ll need to figure out how to get the currently logged in customer.
Delivery Lead Time Service #
This service returns information about shipping timelines from various stock locations.
Line Item Service #
Items added to the cart are managed by this service. With it, you can create an item the moment it is added to the cart. An item’s information can also be fetched. The item may also be updated when its quantity changes or deleted when removed from the cart.
When creating items or updating them, structure the request body as shown in this example.
Order Service #
Similar to the line item service, the order service allows you to
create, update, delete, or get an order. Additionally, you may choose to
get the shipments associated with an order separately using the getOrderShipments
method. This service is used heavily throughout the checkout process.
There are different kinds of information about an order that are
required throughout checkout. Since it may be expensive to fetch a whole
order and its relations, we specify what we want to get from an order
using GetOrderParams
. The equivalent of this on the CL API is the include query parameter where you list the order relationships to be included. You can check what fields need to be included for the cart summary here and for the various checkout stages here.
In the same manner, when updating an order, we use UpdateOrderParams
to specify update fields. This is because in the server that populates
the token, some extra operations are performed depending on what field
is being updated. However, if you’re making direct requests to the CL
API, you do not need to specify this. You can do away with it since the
CL API doesn’t require you to specify them. Although, the request body
should resemble this example.
Paypal Payment Service #
This service is responsible for creating and updating Paypal payments for orders. Additionally, we can get a Paypal payment given its id. The post body should have a structure similar to this example when creating a Paypal payment.
Shipment Service #
This service gets a shipment or updates it given its id. The request body of a shipment update should look similar to this example.
SKU Service #
The SKU service gets products from the store. If multiple products are being retrieved, they can be paginated and have a page size set. Page size and page number should be set as query params like in this example if you’re making direct requests to the API. A single product can also be retrieved given its id.
Core Module #
The core module contains everything central to and common across the application. These include components like the header and pages like the 404 page. Services responsible for authentication and session management also fall here, as well as app-wide interceptors and guards.
The core module tree will look like this.
To generate the module and its contents run:
The core module file should like this. Note that routes have been registered for the NotFoundComponent
and ErrorComponent
.
Services #
The services folder holds the authentication, session, and header services.
Authentication Service #
The AuthenticationService
allows you to acquire client and customer tokens.
These tokens are used to access the rest of the API’s routes. Customer
tokens are returned when a user exchanges an email and password for it
and have a wider range of permissions. Client tokens are issued without
needing credentials and have narrower permissions.
getClientSession
gets a client token. login
gets a customer token. Both methods also create a session. The body of a client token request should look like this and that of a customer token like this.
Session Service #
The SessionService
is responsible for session management. The service will contain an observable from a BehaviorSubject
called loggedInStatus
to communicate whether a user is logged in. setLoggedInStatus
sets the value of this subject, true
for logged in, and false
for not logged in. isCustomerLoggedIn
makes a request to the server to check if the user has an existing session. logout
destroys the session on the server. The last two methods access routes
that are unique to the server that populates the request with a token.
They are not available from Commerce Layer. You’ll have to figure out
how to implement them.
Header Service #
The HeaderService
is used to communicate whether the
cart, login, and logout buttons should be shown in the header. These
buttons are hidden on the login and signup pages but present on all
other pages to prevent confusion. We’ll use an observable from a BehaviourSubject
called showHeaderButtons
that shares this. We’ll also have a setHeaderButtonsVisibility
method to set this value.
Components #
Error Component #
This component is used as an error page. It is useful in instances when server requests fail and absolutely no data is displayed on a page. Instead of showing a blank page, we let the user know that a problem occurred. Below is it’s template.
This is what the component will look like.
Not Found Component #
This is a 404 page that the user gets redirected to when they request a route not available on the router. Only its template is modified.
Header Component #
The HeaderComponent is basically the header displayed at the top of a page. It will contain the app title, the cart, login, and logout buttons.
When this component is initialized, a request is made to check
whether the user has a current session. This happens when subscribing to
this.session.isCustomerLoggedIn()
. We subscribe to this.session.loggedInStatus
to check if the user logs out throughout the life of the app. The this.header.showHeaderButtons
subscription decides whether to show all the buttons on the header or hide them. this.cart.cartValue$
gets the count of items in the cart.
There exists a logout
method that destroys a user’s
session and assigns them a client token. A client token is assigned
because the session maintaining their customer token is destroyed and a
token is still required for each API request. A material snackbar
communicates to the user whether their session was successfully
destroyed or not.
We use the @UntilDestroy({ checkProperties: true })
decorator to indicate that all subscriptions should be automatically unsubscribed from when the component is destroyed.
Below is the header template and linked here is its styling.
Guards #
Empty Cart Guard #
This guard prevents users from accessing routes relating to checkout and billing if their cart is empty. This is because to proceed with checkout, there needs to be a valid order. An order corresponds to a cart with items in it. If there are items in the cart, the user can proceed to a guarded page. However, if the cart is empty, the user is redirected to an empty-cart page.
Interceptors #
Options Interceptor #
This interceptor intercepts all outgoing HTTP requests and adds two options to the request. These are a Content-Type
header and a withCredentials
property. withCredentials
specifies whether a request should be sent with outgoing credentials like the http-only cookies that we use. We use Content-Type
to indicate that we are sending json resources to the server.
Feature Modules #
This section contains the main features of the app. As mentioned earlier, the features are grouped in four modules: auth, product, cart, and checkout modules.
Products Module #
The products module contains pages that display products on sale. These include the product page and the product list page. It’s structured as shown below.
To generate it and its components:
This is the module file:
Product List Component #
This component displays a paginated list of available products for sale. It is the first page that is loaded when the app starts.
The products are displayed in a grid. Material grid list is the best
component for this. To make the grid responsive, the number of grid
columns will change depending on the screen size. The BreakpointObserver
service allows us to determine the size of the screen and assign the columns during initialization.
To get the products, we call the getProducts
method of the SkuService
. It returns the products if successful and assigns them to the grid. If not, we route the user to the error page.
Since the products displayed are paginated, we will have a getNextPage
method to get the additional products.
The template is shown below and its styling can be found here.
The page will look like this.
Product Component #
Once a product is selected from the product list page, this component displays its details. These include the product’s full name, price, and description. There’s also a button to add the item to the product cart.
On initialization, we get the id of the product from the route parameters. Using the id, we fetch the product from the SkuService
.
When the user adds an item to the cart, the addItemToCart
method is called. In it, we check if an order has already been created for the cart. If not, a new one is made using the OrderService
.
Afterwhich, a line item is created in the order that corresponds to the
product. If an order already exists for the cart, just the line item is
created. Depending on the status of the requests, a snackbar message is
displayed to the user.
The ProductComponent
template is as follows and its styling is linked here.
The page will look like this.
Auth Module #
The Auth module contains pages responsible for authentication. These include the login and signup pages. It‘s structured as follows.
To generate it and its components:
This is its module file.
Signup Component #
A user signs up for an account using this component. A first name,
last name, email, and password are required for the process. The user
also needs to confirm their password. The input fields will be created
with the FormBuilder
service. Validation is added to
require that all the inputs have values. Additional validation is added
to the password field to ensure a minimum length of eight characters. A
custom matchPasswords
validator ensures that the confirmed password matches the initial password.
When the component is initialized, the cart, login, and logout
buttons in the header are hidden.This is communicated to the header
using the HeaderService
.
After all the fields are marked as valid, the user can then sign up. In the signup
method, the createCustomer
method of the CustomerService
receives this input. If the signup is successful, the user is informed
that their account was successfully created using a snackbar. They are
then rerouted to the home page.
Below is the template for the SignupComponent
.
The component will turn out as follows.
Login Component #
A registered user logs into their account with this component. An email and password need to be entered. Their corresponding input fields would have validation that makes them required.
Similar to the SignupComponent
, the cart, login, and logout buttons in the header are hidden. Their visibility is set using the HeaderService
during component initialization.
To login, the credentials are passed to the AuthenticationService
. If successful, the login status of the user is set using the SessionService
.
The user is then routed back to the page they were on. If unsuccessful,
a snackbar is displayed with an error and the password field is reset.
Below is the LoginComponent
template.
Here is a screenshot of the page.
Cart Module #
The cart module contains all pages related to the cart. These include the order summary page, a coupon and gift card code page, and an empty cart page. It’s structured as follows.
To generate it, run:
This is the module file.
Codes Component #
As mentioned earlier, this component is used to add any coupon or gift card codes to an order. This allows the user to apply discounts to the total of their order before proceeding to checkout.
There will be two input fields. One for coupons and another for gift card codes.
The codes are added by updating the order. The updateOrder
method of the OrderService
updates the order with the codes. Afterwhich, both fields are reset and
the user is informed of the success of the operation with a snackbar. A
snackbar is also shown when an error occurs. Both the addCoupon
and addGiftCard
methods call the updateOrder
method.
The template is shown below and its styling can be found at this link.
Here is a screenshot of the page.
Empty Component #
It should not be possible to check out with an empty cart. There
needs to be a guard that prevents users from accessing checkout module
pages with empty carts. This has already been covered as part of the CoreModule
. The guard redirects requests to checkout pages with an empty cart to the EmptyCartComponent
.
It’s a very simple component that has some text indicating to the
user that their cart is empty. It also has a button that the user can
click to go to the homepage to add things to their cart. So we’ll use
the SimplePageComponent
to display it. Here is the template.
Here is a screenshot of the page.
Summary Component #
This component summarizes the cart/order. It lists all the items in the cart, their names, quantities, and pictures. It additionally breaks down the cost of the order including taxes, shipping, and discounts. The user should be able to view this and decide whether they are satisfied with the items and cost before proceeding to checkout.
On initialization, the order and its line items are fetched using the OrderService
. A user should be able to modify the line items or even remove them from the order. Items are removed when the deleteLineItem
method is called. In it the deleteLineItem
method of the LineItemService
receives the id of the line item to be deleted. If a deletion is successful, we update the item count in the cart using the CartService
.
The user is then routed to the customer page where they begin the process of checking out. The checkout
method does the routing.
Below is the template and its styling is linked here.
Here is a screenshot of the page.
Checkout Module #
This module is responsible for the checkout process. Checkout involves providing a billing and shipping address, a customer email, and selecting a shipping and payment method. The last step of this process is placement and confirmation of the order. The structure of the module is as follows.
This module is the biggest by far and contains 3 components and 7 pages. To generate it and its components run:
This is the module file.
Components #
Country Select Component
This component lets a user select a country as part of an address. The material select component has a pretty different appearance when compared to the input fields in the address form. So for the sake of uniformity, a material menu component is used instead.
When the component is initialized, the country code data is fetched using the CountryService
. The countries
property holds the values returned by the service. These values will be added to the menu in the template.
The component has one output property, setCountryEvent
. When a country is selected, this event emits the alpha-2 code of the country.
Below is its template and linked here is its styling.
Address Component
This is a form for capturing addresses. It is used by both the shipping and billing address pages. A valid Commerce Layer address should contain a first and last name, an address line, a city, zip code, state code, country code, and phone number.
The FormBuilder
service will create the form group.
Since this component is used by multiple pages, it has a number of input
and output properties. The input properties include the button text,
title displayed, and text for a checkbox. The output properties will be
event emitters for when the button is clicked to create the address and
another for when the checkbox value changes.
When the button is clicked, the addAddress
method is called and the createAddress
event emits the complete address. Similarly, when the checkbox is checked, the isCheckboxChecked
event emits the checkbox value.
This is its template and its styling is linked here.
Address List Component
When a customer logs in, they can access their existing addresses.
Instead of having them re-enter an address, they can pick from an
address list. This is the purpose of this component. On initialization,
all the customer’s addresses are fetched using the CustomerAddressService
if they are logged in. We will check their login status using the SessionService
.
This component has a setAddressEvent
output property. When an address is selected, setAddressEvent
emits its id to the parent component.
Here is its template. You can find its styling here.
Pages #
Customer Component
An order needs to be associated with an email address. This component
is a form that captures the customer email address. When the component
is initialized, the current customer’s email address is fetched if they
are logged in. We get the customer from the CustomerService
. If they do not wish to change their email address, this email will be the default value.
If the email is changed or a customer is not logged in, the order is updated with the inputted email. We use the OrderService
to update the order with the new email address. If successful, we route the customer to the billing address page.
Here is the component template and linked here is its styling.
Here is a screenshot of the customer page.
Billing Address Component
The billing address component lets a customer either add a new billing address or pick from their existing addresses. Users who are not logged in have to input a new address. Those who have logged in get an option to pick between new or existing addresses.
The showAddress
property indicates whether existing addresses should be shown on the component. sameShippingAddressAsBilling
indicates whether the shipping address should be the same as what the
billing address is set. When a customer selects an existing address,
then its id is assigned to selectedCustomerAddressId
.
When the component is initialized, we use the SessionService
to check if the current user is logged in. If they are logged in, we will display their existing addresses if they have any.
As mentioned earlier, if a user is logged in, they can pick an existing address as their billing address. In the updateBillingAddress
method, if they are logged in, the address they select is cloned and
set as the order’s billing address. We do this by updating the order
using the updateOrder
method of the OrderService
and supplying the address Id.
If they are not logged in, the user has to provide an address. Once provided, the address is created using the createAddress
method. In it, the AddressService
takes the input and makes the new address. After which, the order is
updated using the id of the newly created address. If there is an error
or either operation is successful, we show a snackbar.
If the same address is selected as a shipping address, the user is routed to the shipping methods page. If they’d like to provide an alternate shipping address, they are directed to the shipping address page.
Here is the template. This link points to its styling.
This is what the billing address page will look like.
Shipping Address Component
The shipping address component behaves a lot like the billing address
component. However, there are a couple of differences. For one, the
text displayed on the template is different. The other key differences
are in how the order is updated using the OrderService
once an address is created or selected. The fields that the order updates are shippingAddressCloneId
for selected addresses and shippingAddress
for new addresses. If a user chooses to change the billing address, to be the same as the shipping address, the billingAddressSameAsShipping
field is updated.
After a shipping address is selected and the order is updated, the user is routed to the shipping methods page.
Here is the template and its styling can be found here.
The shipping address page will look like this.
Shipping Methods Component
This component displays the number of shipments required for an order to be fulfilled, the available shipping methods, and their associated costs. The customer can then select a shipping method they prefer for each shipment.
The shipments
property contains all the shipments of the order. The shipmentsForm
is the form within which the shipping method selections will be made.
When the component is initialized, the order is fetched and will
contain both its line items and shipments. At the same time, we get the
delivery lead times for the various shipping methods. We use the OrderService
to get the order and the DeliveryLeadTimeService
for the lead times. Once both sets of information are returned, they
are combined into an array of shipments and assigned to the shipments
property. Each shipment will contain its items, the shipping methods available, and the corresponding cost.
After the user has selected a shipping method for each shipment, the selected shipping method is updated for each in setShipmentMethods
. If successful, the user is routed to the payments page.
Here is the template and you can find the styling at this link.
This is a screenshot of the shipping methods page.
Payments Component
In this component, the user clicks the payment button if they wish to proceed to pay for their order with Paypal. The approvalUrl
is the Paypal link that the user is directed to when they click the button.
During initialization, we get the order with the payment source included using the OrderService
. If a payment source is set, we get its id and retrieve the corresponding Paypal payment from the PaypalPaymentService
.
The Paypal payment will contain the approval url. If no payment source
has been set, we update the order with Paypal as the preferred payment
method. We then proceed to create a new Paypal payment for the order
using the PaypalPaymentService
. From here, we can get the approval url from the newly created order.
Lastly, when the user clicks the button, they are redirected to Paypal where they can approve the purchase.
Here is its template.
Here’s what the payments page will look like.
Cancel Payment Component
Paypal requires a cancel payment page. This component serves this purpose. This is its template.
Here’s a screenshot of the page.
Place Order Component
This is the last step in the checkout process. Here the user confirms that they indeed want to place the order and begin its processing. When the user approves the Paypal payment, this is the page they are redirected to. Paypal adds a payer id query parameter to the url. This is the user’s Paypal Id.
When the component is initialized, we get the payerId
query parameter from the url. The order is then retrieved using the OrderService
with the payment source included. The id of the included payment source
is used to update the Paypal payment with the payer id, using the PaypalPayment
service. If any of these fail, the user is redirected to the error page. We use the disableButton
property to prevent the user from placing the order until the payer Id is set.
When they click the place-order button, the order is updated with a placed
status. Afterwhich the cart is cleared, a successful snack bar is displayed, and the user is redirected to the home page.
Here is the template and its associated styling.
Here is a screenshot of the page.
App Module #
All requests made to Commerce Layer, other than for authentication,
need to contain a token. So the moment the app is initialized, a token
is fetched from the /oauth/token
route on the server and a session is initialized. We’ll use the APP_INITIALIZER
token to provide an initialization function in which the token is retrieved. Additionally, we’ll use the HTTP_INTERCEPTORS
token to provide the OptionsInterceptor
we created earlier. Once all the modules are added the app module file should look something like this.
App Component #
We’ll modify the app component template and its styling which you can find here.
Conclusion #
In this article, we’ve covered how you could create an e-commerce Angular 11 app with Commerce Layer and Paypal. We’ve also touched on how to structure the app and how you could interface with an e-commerce API.
Although this app allows a customer to make a complete order, it is not by any means finished. There is so much you could add to improve it. For one, you may choose to enable item quantity changes in the cart, link cart items to their product pages, optimize the address components, add additional guards for checkout pages like the place-order page, and so on. This is just the starting point.
If you’d like to understand more about the process of making an order from start to finish, you could check out the Commerce Layer guides and API. You can view the code for this project at this repository.
No comments:
Post a Comment