An error occurred while processing the template.
Java method "com.liferay.portal.json.JSONFactoryImpl.createJSONObject(String)" threw an exception when invoked on com.liferay.portal.json.JSONFactoryImpl object "com.liferay.portal.json.JSONFactoryImpl@6b638fb3"; see cause exception in the Java stack trace.

----
FTL stack trace ("~" means nesting-related):
	- Failed at: navigationJSONObject = jsonFactoryUti...  [in template "8911408109993434201#23484949#LEARN-ARTICLE-NAV" at line 4, column 9]
----
1<#assign 
2	groupFriendlyURL = themeDisplay.getScopeGroup().getFriendlyURL() 
3	groupPathFriendlyURLPublic = themeDisplay.getPathFriendlyURLPublic() + groupFriendlyURL 
4	navigationJSONObject = jsonFactoryUtil.createJSONObject(navigation.getData()) 
5	navigationMenuItems = 
6
7			"Analytics Cloud": { 
8				"image": "/documents/d${groupFriendlyURL}/analytics_c-svg", 
9				"title": "Analytics Cloud", 
10				"url": "analytics-cloud" 
11			}, 
12			"Commerce": { 
13				"image": "/documents/d${groupFriendlyURL}/commerce_product-svg", 
14				"title": "Commerce", 
15				"url": "commerce" 
16			}, 
17			"DXP": { 
18				"image": "/documents/d${groupFriendlyURL}/dxp_p-svg", 
19				"title": "DXP / Portal", 
20				"url": "dxp" 
21			}, 
22			"Liferay Cloud": { 
23				"image": "/documents/d${groupFriendlyURL}/dxp_c-svg", 
24				"title": "Liferay Cloud", 
25				"url": "liferay-cloud" 
26			}, 
27			"Reference": { 
28				"image": "/documents/d${groupFriendlyURL}/reference-svg", 
29				"title": "Reference", 
30				"url": "reference" 
31
32
33 
34	breadcrumbJSONArray = navigationJSONObject.getJSONArray("breadcrumb") 
35	childrenJSONArray = navigationJSONObject.getJSONArray("children") 
36	parentJSONObject = navigationJSONObject.getJSONObject("parent") 
37	productJSONObject = breadcrumbJSONArray.getJSONObject(breadcrumbJSONArray.length()-1)!navigationJSONObject.getJSONObject("self") 
38	siblingsJSONArray = navigationJSONObject.getJSONArray("siblings") 
39/> 
40 
41<div class="learn-article-nav"> 
42	<#if productJSONObject?has_content && productJSONObject.getString("title")?has_content && navigationMenuItems[productJSONObject.getString("title")]?has_content && navigationMenuItems[productJSONObject.getString("title")].title?has_content> 
43		<div 
44			class="dropdown learn-article-nav-root learn-dropdown" 
45
46			<div class="learn-article-nav-item"> 
47				<div class="d-flex"> 
48					<div class="learn-article-nav-image"> 
49						<img 
50							class="lexicon-icon lexicon-icon-caret-bottom product-icon" 
51							role="presentation" 
52							src='${navigationMenuItems[productJSONObject.getString("title")].image}' 
53							viewBox="0 0 512 512" 
54						/> 
55					</div> 
56 
57					<span class="learn-article-nav-text">${navigationMenuItems[productJSONObject.getString("title")].title}</span> 
58				</div> 
59 
60				<div id="dropdown-icon"> 
61					<svg 
62						class="lexicon-icon lexicon-icon-caret-bottom" 
63						role="presentation" 
64						viewBox="0 0 512 512" 
65
66						<use xlink:href="/o/admin-theme/images/clay/icons.svg#caret-bottom"></use> 
67					</svg> 
68				</div> 
69			</div> 
70 
71			<ul class="dropdown-menu learn-dropdown-menu"> 
72				<#list navigationMenuItems as key, value> 
73					<li> 
74						<a 
75							class="dropdown-item learn-article-nav-item" 
76							href="/w/${navigationMenuItems[key].url}/index" 
77							tabindex="4" 
78
79							<span class="d-flex"> 
80								<span class="learn-article-nav-image"> 
81									<img 
82										class="lexicon-icon lexicon-icon-caret-bottom product-icon mt-0 mr-2" 
83										role="presentation" 
84										src="${value.image}"height: 25px; margin-left: 5px; max-width: none; width: 25px; 
85										viewBox="0 0 512 512" 
86									/> 
87								</span> 
88								<span class="learn-article-nav-text">${value.title}</span> 
89							</span> 
90 
91							<#if navigationMenuItems[productJSONObject.getString("title")].url == value.url> 
92								<span> 
93									<@clay["icon"] symbol="check" /> 
94								</span> 
95							</#if> 
96						</a> 
97					</li> 
98				</#list> 
99			</ul> 
100		</div> 
101	</#if> 
102 
103	<div class="learn-article-nav-content"> 
104		<#if parentJSONObject?has_content && parentJSONObject.getString("url")?has_content> 
105			<div class="learn-article-nav-item learn-article-nav-parent liferay-nav-item p-2"> 
106				<div class="mr-2"> 
107					<a 
108						href='${parentJSONObject.getString("url")}' 
109
110						<svg 
111							class="lexicon-icon lexicon-icon-angle-left" 
112							role="presentation" 
113							viewBox="0 0 512 512" 
114
115							<use xlink:href="/o/admin-theme/images/clay/icons.svg#angle-left"></use> 
116						</svg> 
117					</a> 
118				</div> 
119 
120				<span>${parentJSONObject.getString("title")}</span> 
121			</div> 
122		</#if> 
123 
124		<#if childrenJSONArray.length() gt 0> 
125			<ul class="m-0 p-2"> 
126				<#list 0..childrenJSONArray.length()-1 as i> 
127					<li class="learn-article-nav-item"> 
128						<a 
129							class='liferay-nav-item ${(navigationJSONObject.getJSONObject("self").url == childrenJSONArray.getJSONObject(i).url)?then("selected", "")}' 
130							href="${childrenJSONArray.getJSONObject(i).url}" 
131
132							<span>${childrenJSONArray.getJSONObject(i).getString("title")}</span> 
133						</a> 
134					</li> 
135				</#list> 
136			</ul> 
137		<#elseif siblingsJSONArray.length() gt 0> 
138			<ul class="m-0 p-2"> 
139				<#list 0..siblingsJSONArray.length()-1 as i> 
140					<li class="learn-article-nav-item"> 
141						<a 
142							class='liferay-nav-item ${(navigationJSONObject.getJSONObject("self").url == siblingsJSONArray.getJSONObject(i).url)?then("selected", "")}' 
143							href="${siblingsJSONArray.getJSONObject(i).url}" 
144
145							<span>${siblingsJSONArray.getJSONObject(i).getString("title")}</span> 
146						</a> 
147					</li> 
148				</#list> 
149			</ul> 
150		</#if> 
151	</div> 
152</div> 

Implementing a New Tax Engine

This tutorial shows you how to add a new tax engine by implementing the CommerceTaxEngine interface.

A tax engine performs the calculation for taxes when a transaction is made. Liferay Commerce provides two default tax engines: FixedCommerceTaxEngine for fixed rates, and ByAddressCommerceTaxEngine for calculating taxes by address.

Out-of-the-box tax engines

Overview

  1. Deploy an Example
  2. Walk Through the Example

Deploy an Example

In this section, get an example tax engine up and running on your instance of Liferay Commerce.

Start a new Liferay instance by running

docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.132-ga132

Sign in to Liferay at http://localhost:8080. Use the email address test@liferay.com and the password test. When prompted, change the password to learn.

Then, follow these steps:

  1. Download and unzip the Acme Commerce Tax Engine.

    curl https://resources.learn.liferay.com/commerce/latest/en/developer-guide/sales/liferay-q4b9.zip -O
    
    unzip liferay-q4b9.zip
    
  2. Build and deploy the example.

    ./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
    Note

    This command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.

  3. Confirm the deployment in the Docker container console.

    STARTED com.acme.q4b9.impl_1.0.0
    
  4. Verify that the example tax engine was added. Open your browser to https://localhost:8080. Then click the Applications Menu (Applications Menu) and navigate to CommerceChannels. Edit a channel. The new tax engine (“Q4B9 Commerce Tax Engine”) appears in the list.

Note

In Commerce 2.1 and earlier, find the tax engines by navigating to Site AdministrationCommerceSettingsTaxesTax Calculations.

New tax engine

Congratulations, you’ve successfully built and deployed a new tax engine that implements CommerceTaxEngine.

Next, let’s dive deeper to learn more.

Walk Through the Example

In this section, review the example we deployed. First, annotate the class for OSGi registration. Second, review the CommerceTaxEngine interface. And third, complete our implementation of CommerceTaxEngine.

Annotate the Class for OSGi Registration

@Component(
   property = "commerce.tax.engine.key=q4b9", service = CommerceTaxEngine.class
)

It is important to provide a distinct key for the tax engine so that Liferay Commerce can distinguish the new engine from others in the tax engine registry. Reusing a key that is already in use overrides the existing associated tax engine.

Review the CommerceTaxEngine Interface

Implement the following methods:

public CommerceTaxValue getCommerceTaxValue(
      CommerceTaxCalculateRequest commerceTaxCalculateRequest)
   throws CommerceTaxEngineException;

This method is where the business logic is implemented for our tax engine. See CommerceTaxValue for more information.

public String getDescription(Locale locale);

This returns a brief description of our tax engine. See the Q4B9CommerceTaxEngine.java class in liferay-q4b9.zip/q4b9-impl/src/main/java/com/acme/q4b9/internal/commerce/tax for a reference in retrieving the description with a language key.

public String getName(Locale locale);

This returns the name of our tax engine. It works similarly to the getDescription method.

Complete the Tax Engine

The tax engine is comprised of logic to perform the tax calculation. Do the following:

Add Business Logic to getCommerceTaxValue

@Override
public CommerceTaxValue getCommerceTaxValue(
      CommerceTaxCalculateRequest commerceTaxCalculateRequest)
   throws CommerceTaxEngineException {

   BigDecimal flatTaxValue = _ONE_POINT_FIVE_ZERO;

   if (commerceTaxCalculateRequest.isPercentage()) {
      flatTaxValue = _ONE_POINT_FIVE_ZERO.divide(new BigDecimal(100.0));

      flatTaxValue = flatTaxValue.multiply(
         commerceTaxCalculateRequest.getPrice());
   }

   return new CommerceTaxValue(
      "q4b9", "q4b9-commerce-tax-engine", flatTaxValue);
}

private static final BigDecimal _ONE_POINT_FIVE_ZERO = new BigDecimal(
   "1.50");

The CommerceTaxCalculateRequest parameter contains information needed for making our calculation. For this example, we use the price from the CommerceTaxCalculateRequest, as well as a value indicating whether to apply the rate as a percentage. See CommerceTaxCalculateRequest.java to find more methods you can use with a CommerceTaxCalculateRequest.

Add the Language Keys to Language.properties

Add the language keys and their values to a Language.properties file in liferay-q4b9.zip/q4b9-impl/src/main/resources/content within our module:

q4b9-commerce-tax-engine=Q4B9 Commerce Tax Engine
this-tax-engine-serves-a-fixed-x-percent-flat-tax-rate=This tax engine serves a fixed {0} percent flat tax rate.

See Localizing Your Application for more information.

Conclusion

Congratulations! You now know the basics for implementing the CommerceTaxEngine interface, and have added a new tax engine to Liferay Commerce.