Originally published on JJIT. Translated from Polish.
At first glance, it might seem like projects created for an e-commerce client are very similar. Of course, this is true to some extent, and this common context is reflected in platforms such as Optimizely or Znode. They provide support for the entire sales process. Nonetheless, specific customer requirements generate the need to extend and adapt the platform to those requirements.
Here are some examples of what an e-commerce developer's job entails:
Often when working with e-commerce projects, we may need to customize the project more in terms of the website itself than the commerce part. In these cases, we can take advantage of the system’s built-in capabilities, such as global attributes in Znode.
Recently, for a client in the United States, we implemented the ability to update the privacy policy, and a mechanism to ensure that the user accepts the latest privacy policy before using the site.
To achieve the above, we used three global attributes added via the "global attributes" section in the admin panel. First, the PrivacyPolicyDate and PrivacyPolicyText attributes were added, representing, respectively, the date the privacy policy content was last updated and just the content. The new fields were grouped into the PrivacyPolicy group and added to the "Default Store Family" attribute family representing store-level settings. These settings can be found in the admin panel under "Manage Store" -> "Additional Attributes".
Next, a UserPrivacyPolicy attribute group containing the PrivacyPolicyAcceptDate field was added at the user level. It is visible from the Admin Panel in the "Manage User" tab. -> "Additional Attributes".
The idea behind this solution is simple. When the page loads, we check the date the privacy policy was updated. If the user's privacy policy acceptance date is blank or older than the privacy policy content update date, then we show the user a box with the latest privacy policy to accept. When the user accepts the latest content, then we automatically save the acceptance date assigned to that person.
To retrieve the privacy policy content update date in code, we can use the internal API as follows:
On the other hand, to manipulate the date on which the user accepted the privacy policy, we can use the IGlobalAttributeGroupEntityService interface. Using the GetEntityAttributeDetails (int entityId, string entityType) and SaveEntityAttributeDetails (EntityAttributeModel model) methods. An example of retrieving a value from a user-level property:
Integrations with external systems are an integral part of the work in e-commerce projects, including for the purpose of storing information about products, orders, availability of individual items. Often, the need to store additional product information is generated as a result. On one of the client's projects, such a need arose recently.
When integrating with Microsoft Dynamics 365, we needed information about the type of product in order to retrieve its correct availability from the warehouse. To do this, we used Znode's built-in "Product Attributes" functionality. We added a CustomProductType attribute allowing to select the product type from a predefined list. At the moment, the current list allows you to select "Standard" and "Kit".
Depending on whether the product represents a single item or a set, we send two different REST API requests to get stock feedback. In the case of a set, we first query the API returning information about the products included in the bundle, and then send a stock query for the individual products to determine the number of possible sets that can be completed.
We can use the helper method to retrieve the newly added product type:
Where product can be both PublishProductModel and ShoppingCartItemModel types, which in practice means that the attributes in the code are available both in the context of product management and during shopping cart processing.
The backbone of any online store is its product assortment. This part of e-commerce systems is basically always being extended and customized to meet the needs of a given seller. Optimizely provides a very convenient architecture from a programmer's perspective in this regard.
Let's assume that our store trades in office supplies and we want to describe these products on our site as best as possible. For example, we want the product page for a notepad to include information about how many sheets the variant has. We use the tenets of polymorphism to do this, implementing our own class representing notepad that inherits from the base class Optimizely: VariationContent.
This solution allows us to use the base class wherever we retrieve a list of variants or products. On the other hand, after checking the type of a particular object, we can use conditionally the extended attributes of a particular implementation of the class, for example, just to display the number of pages if the product is a notebook.
After adding a new product property in the code, editors will be able to set how many pages each notebook variant has. In addition, a decorator in the form of attributes enclosed in square brackets allows you to determine in which tab in the admin panel the new product property will be visible, to specify the name of the new field and the order in which these fields will be displayed.
Expanding from the previous section - we may also come across a situation where we need to create a product property that should allow selection from a predefined list. A good example here would be the type of medium in a book store.
Let's imagine that our store sells paper books, audiobooks and e-books. In order to clearly define the type of books sold, we need to create an attribute that allows us to choose from the three different types mentioned earlier. For this, we can create a custom selection pool for the book type as follows:
After adding the above option factory, we can use it in our class representing the book variant:
The above implementation will allow editors to select a book type from a list of three available options when editing a variant. Identification of the book type will not only allow this information to be displayed to the end user, but also ensure correct identification during processes such as sales and delivery. For example, the delivery of a paper book will involve physical shipment while an e-book and audiobook can be delivered to the recipient digitally.
Another part of e-commerce that usually needs to be customized for a specific project is delivery methods. In this regard, using Optimizely we have two levels of options. First, we can add a new delivery method from the admin panel and fill in data such as name, language, currency, and type of delivery cost calculation. There are two types of delivery cost calculation: Generic Gateway and Weight/Jurisdiction Gateway.
The first type is based on a fixed delivery price, while the second has a base price to which an additional fee is added based on the delivery location and the weight of the package. Usually, adding a delivery method with a fixed price is sufficient, as most couriers have predetermined prices, or alternatively, prices are explicitly based on weight. Sometimes, however, we need to customize delivery options to a greater extent, in which case we can use our own delivery gateway.
The second level of customization of delivery methods is the ability to implement a custom gate (a type of cost calculation). To do this, we can implement the IShippingGateway or IShippingPlugin interface, of which Optimizely recommends implementing the latter, since the main purpose of the former is backward compatibility. The interface contains only one method and it is responsible for calculating the cost of delivery.
Inside this method we can perform all the calculations involved in determining the shipping price. This method returns an object of class ShippingRate containing the following fields:
Id - the identifier of the associated shipping method,
Money - the price and currency of the shipment,
Name - the name of the associated shipping method.
One of the most used elements of a website representing a store is the global search engine. Like many of the previously mentioned areas, it too can be customized for specific needs. If we use the default search functionality of Optimizely Search & Navigation, we can easily manipulate which product or variant fields are to be indexed and available for search. If we want to exclude a property so that the results cannot be searched by it, we can use the [JsonIgnore] decorator over the given attribute:
We can also use the search convention configuration:
If, on the other hand, we want to add the ability to search for an item through a field that is not directly part of the class representing that item, we also have that option. Referring to the example in the previous sections, let's imagine that we have a book represented by the BookProduct class:
and its BookVariant variants:
Let's assume that we want the search results after the author's name to include all his books, both paper versions and e-books and audiobooks. To do this, we want a field not directly belonging to that object - Author - to be indexed for each variant.
For this example, we can configure the search client to index the selected attribute as specified:
Where the Author() method is an extension method for the BookVariant class:
Thanks to the above solution, after typing an author's name in the search bar, the end user will see all books by a given author in all available media types.
Working with e-commerce systems is a very diverse specialty in the IT industry. Customers' needs directly influence design solutions and all kinds of modifications to default functions. Each store has its own character, its own industry, its own challenges and its own capabilities.
As a .NET developer, this is precisely the sector I enjoy working in the most. It's impossible to hit on an identical project, every time we hit on something new. It gives a wide range of development opportunities and it's just hard to get bored with it.
The above article was intended to illustrate the diverse range of experiences that go with working with e-commerce systems, but of course it does not fully exhaust the topic of their extensibility. For example, it is worth mentioning that we can, for example, create fully customizable promotions and new types of promotions, or even extend the calculation of the price of a shopping cart and an order. For example, I once had the opportunity to modify the default price calculator to increase the final price by the plastic tax for adequate products. Such examples could be mentioned almost endlessly.
Modifications, extending functionality, customization - this is our daily life, and an extremely important daily life. These aspects are part and parcel of working with e-commerce systems, and this is as legitimate as possible. Meeting requirements and differences is crucial in our industry, especially in this sector. Good and effective e-commerce is commerce tailored to both the needs of the seller and the expectations of the buyer. Therefore, in keeping with the spirit of this article, my farewell advice to every e-commerce developer: Expand, customize, modify, hit the mark!