Implementing a New Shipping Engine¶
This tutorial will show you how to add a custom shipping engine by implementing the CommerceShippingEngine interface.
Shipping engines process shipping options to determine which of the available options will be shown to the user, for what price, and so on. Liferay Commerce provides three shipping engines out-of-the-box: a flat rate engine, a variable rate engine, and the FedEx engine.
The FedEx shipping engine is only available for Commerce Enterprise Subscribers.
Deploy an Example¶
In this section, we will get an example shipping engine up and running on your instance of Liferay Commerce. Follow these steps:
Start Liferay Commerce.
docker run -it -p 8080:8080 liferay/portal:7.4.3.22-ga22
Download and unzip the Acme Commerce Shipping Engine
curl https://learn.liferay.com/commerce/latest/en/developer-guide/sales/liferay-j6x8.zip -O
unzip liferay-j6x8.zip
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.Confirm the deployment in the Docker container console.
STARTED com.acme.j6x8.impl_1.0.0
Verify that the example shipping engine was added. Open your browser to
https://localhost:8080
. Then click the Applications Menu () and navigate to Commerce → Channels. In the Shipping Methods section, the new shipping method (“Discounted Rate”) representing our shipping engine will be present.
Note
In Commerce 2.1 and earlier, find the shipping methods by navigating to Site Administration → Commerce → Settings → Shipping Methods.
Congratulations, you’ve successfully built and deployed a new shipping engine that implements CommerceShippingEngine
.
Next, let’s dive deeper to learn more.
Walk Through the Example¶
In this section, we will review the example we deployed. First, we will annotate the class for OSGi registration. Second, we will review the CommerceShippingEngine
interface. And third, we will complete our implementation of CommerceShippingEngine
.
Annotate the Class for OSGi Registration¶
@Component(
property = "commerce.shipping.engine.key=j6x8",
service = CommerceShippingEngine.class
)
public class J6X8CommerceShippingEngine implements CommerceShippingEngine {
It is important to provide a distinct key for the shipping engine so that Liferay Commerce can distinguish the new engine from others in the shipping engine registry. Reusing a key that is already in use will override the existing associated engine.
Review the CommerceShippingEngine
Interface¶
Implement the following methods:
public String getCommerceShippingOptionLabel(String name, Locale locale);
This method returns a text label used for shipping options. See the implementation in J6X8CommerceShippingEngine.java for a reference in retrieving the description with a language key.
See Localizing Your Application for more information.
public List<CommerceShippingOption> getCommerceShippingOptions(
CommerceContext commerceContext, CommerceOrder commerceOrder,
Locale locale)
throws CommerceShippingEngineException;
This will be where we add the business logic for our custom shipping engine. It must fetch a list of available options, then perform the processing necessary to present them to the customer.
public String getDescription(Locale locale);
This returns a brief description of our shipping engine. It works similarly to the
getCommerceShippingOptionLabel
method.
public String getName(Locale locale);
This returns the name of our shipping engine to display in the UI. It also works similarly to the
getCommerceShippingOptionLabel
andgetDescription
methods.
Complete the Shipping Engine¶
The shipping engine method getCommerceShippingOptions
returns the list of shipping options to show to the customer.
@Override
public List<CommerceShippingOption> getCommerceShippingOptions(
CommerceContext commerceContext, CommerceOrder commerceOrder,
Locale locale)
throws CommerceShippingEngineException {
try {
CommerceShippingMethod commerceShippingMethod =
_commerceShippingMethodLocalService.fetchCommerceShippingMethod(
commerceOrder.getScopeGroupId(), "j6x8");
if (commerceShippingMethod == null) {
return Collections.emptyList();
}
List<CommerceShippingOption> commerceShippingOptions =
new ArrayList<>();
List<CommerceShippingFixedOption> commerceShippingFixedOptions =
_commerceShippingFixedOptionLocalService.
getCommerceShippingFixedOptions(
commerceShippingMethod.getCommerceShippingMethodId(),
QueryUtil.ALL_POS, QueryUtil.ALL_POS);
for (CommerceShippingFixedOption commerceShippingFixedOption :
commerceShippingFixedOptions) {
CommerceAddress commerceAddress =
commerceOrder.getShippingAddress();
if (_commerceAddressRestrictionLocalService.
isCommerceShippingMethodRestricted(
commerceShippingFixedOption.
getCommerceShippingMethodId(),
commerceAddress.getCommerceCountryId())) {
continue;
}
String name = commerceShippingFixedOption.getName(locale);
if (_commerceShippingHelper.isFreeShipping(commerceOrder)) {
commerceShippingOptions.add(
new CommerceShippingOption(
name, name, BigDecimal.ZERO));
}
BigDecimal amount = commerceShippingFixedOption.getAmount();
amount = amount.multiply(BigDecimal.valueOf(0.75));
commerceShippingOptions.add(
new CommerceShippingOption(name, name, amount));
}
return commerceShippingOptions;
}
catch (Exception exception) {
throw new CommerceShippingEngineException(exception);
}
}
The method starts by getting the available shipping options and looping over them to process each one.
CommerceShippingMethod commerceShippingMethod =
_commerceShippingMethodLocalService.fetchCommerceShippingMethod(
commerceOrder.getScopeGroupId(), "j6x8");
if (commerceShippingMethod == null) {
return Collections.emptyList();
}
List<CommerceShippingOption> commerceShippingOptions =
new ArrayList<>();
List<CommerceShippingFixedOption> commerceShippingFixedOptions =
_commerceShippingFixedOptionLocalService.
getCommerceShippingFixedOptions(
commerceShippingMethod.getCommerceShippingMethodId(),
QueryUtil.ALL_POS, QueryUtil.ALL_POS);
for (CommerceShippingFixedOption commerceShippingFixedOption :
commerceShippingFixedOptions) {
//The shipping option processing logic goes here.
First, use CommerceShippingMethodLocalService to get the “shipping method” (representing our shipping engine), and then use CommerceShippingFixedOptionLocalService to get the available options.
Then loop over the shipping options to process them.
Here are the shipping option processing steps:
Liferay Commerce’s fixed rate shipping engine is a good reference to see what processing steps are a good baseline to start with. Our example method uses similar steps.
Implement Address Restriction Checking¶
private boolean _shippingOptionIsAddressRestricted(
CommerceOrder commerceOrder,
CommerceShippingFixedOption commerceShippingFixedOption)
throws PortalException {
CommerceAddress commerceAddress = commerceOrder.getShippingAddress();
return _commerceAddressRestrictionLocalService.
isCommerceShippingMethodRestricted(
commerceShippingFixedOption.getCommerceShippingMethodId(),
commerceAddress.getCommerceCountryId());
}
The next step determines whether a particular shipping option is restricted for the order’s shipping address. A restricted option will not presented as an option to choose from.
Use CommerceAddressRestrictionLocalService to determine if the option is restricted for the order’s address. Use
CommerceOrder
to get the address information; theCommerceOrder
object represents all kinds of information about the order being shipped. See CommerceOrder.java and CommerceOrderModel.java to find more methods you can use with aCommerceOrder
.
Check for Free Shipping¶
String name = commerceShippingFixedOption.getName(locale);
if (_commerceShippingHelper.isFreeShipping(commerceOrder)) {
commerceShippingOptions.add(
new CommerceShippingOption(
name, name, BigDecimal.ZERO));
}
Use the CommerceShippingHelper to more easily determine if the order should be free.
Add Custom Shipping Option Logic¶
BigDecimal amount = commerceShippingFixedOption.getAmount();
amount = amount.multiply(BigDecimal.valueOf(0.75));
Here is where you can add more shipping option logic. The example shipping option reduces the standard fixed shipping option amount by 25%.
Add the Processed Shipping Option¶
commerceShippingOptions.add(
new CommerceShippingOption(name, name, amount));
} // end of shipping option processing loop
return commerceShippingOptions;
Apply the amount to the shipping option and add the shipping option to the list. Then close the for loop and return the shipping options list.
Add the Language Keys to Language.properties
¶
Add the language keys and their values to a Language.properties file within our module:
discounted-rate=Discounted Rate
ship-for-a-discounted-price=Ship for a discounted price.
See Localizing Your Application for more information.
Conclusion¶
Congratulations! You now know the basics for implementing the CommerceShippingEngine
interface, and have added a new shipping engine to Liferay Commerce.