Skip to content
This repository has been archived by the owner on Nov 1, 2019. It is now read-only.

Custom order validator #48

Merged
merged 5 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# Implementing a Custom Order Validator

This tutorial will show you how to add ... by implementing the `Foo` interface.
This tutorial will show you how to add a custom order validator by implementing the `CommerceOrderValidator` interface.

Liferay Commerce provides ...

(Establishing Image Placeholder)
An order validator is a class that validates items in a customer's cart when proceeding through checkout. Liferay Commerce provides multiple out of the box order validators, including a [default](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-service/src/main/java/com/liferay/commerce/internal/order/DefaultCommerceOrderValidatorImpl.java), as well as validators to check [item versions](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-service/src/main/java/com/liferay/commerce/internal/order/VersionCommerceOrderValidatorImpl.java) and [recurring items (subscriptions)](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-service/src/main/java/com/liferay/commerce/internal/order/SubscriptionCommerceOrderValidatorImpl.java).

## Overview

Expand All @@ -14,28 +12,28 @@ Liferay Commerce provides ...

## Deploy an Example

In this section, we will get an example ... up and running on your instance of Liferay Commerce. Follow these steps:
In this section, we will get an example order validator up and running on your instance of Liferay Commerce. Follow these steps:

1. Start Liferay Commerce.

```bash
docker run -it -p 8080:8080 liferay/commerce:2.0.2
```

1. Download and unzip the [Acme Commerce ...]() to your project directory.
1. Download and unzip the [Acme Commerce Order Validator]() to your project directory.

```bash
curl liferay-xxxx.zip
curl liferay-n9b2.zip
```

```bash
unzip liferay-xxxx.zip
unzip liferay-n9b2.zip
```

1. Go to `xxxx-impl`.
1. Go to `n9b2-impl`.

```bash
cd xxxx-impl
cd n9b2-impl
```

1. Build and deploy the example.
Expand All @@ -44,32 +42,185 @@ In this section, we will get an example ... up and running on your instance of L
./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.
> Note: This command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.

1. Confirm the deployment in the Liferay Docker container console.

```bash
STARTED com.acme.xxxx.internal.commerce.foo.method_1.0.0
STARTED com.acme.n9b2.internal.commerce.order.validator_1.0.0
```

1. Verify that the example ... was added. Open your browser to `https://localhost:8080` and navigate to ...
1. Verify that the example order validator was added by viewing the failure message. Open your browser to `https://localhost:8080` and navigate to a catalog with at least one item priced over $100. If one does not exist, you may need to add it yourself; see [Products](https://commerce.liferay.dev/user-guide/-/knowledge_base/user/products) for more information on this.

Once in the catalog, search for the item with this price, then click "Add to Cart". Increase the quantity to 11 or more, then click the arrow to continue. The error message that appears shows that the custom order validator successfully rejected adding the item.

(Deployed Sample Image Placeholder)
![New order validation error message](./images/01.png "New order validation error message")

Next, let's dive deeper to learn more.

## Walk Through the Example

In this section, we will take a more in-depth review of the example we deployed. First, we will annotate the class for OSGi registration; second we will implement the `Foo` interface; and third, we will implement the ... logic.
In this section, we will take a more in-depth review of the example we deployed. First, we will annotate the class for OSGi registration; second we will implement the `CommerceOrderValidator` interface; and third, we will implement the custom validation logic.

### Annotate Your Class for OSGi Registration

### Implement the `Foo` Interface
```java
@Component(
immediate = true,
property = {
"commerce.order.validator.key=" + N9B2CommerceOrderValidator.KEY,
"commerce.order.validator.priority:Integer=9"
},
service = CommerceOrderValidator.class
)
public class N9B2CommerceOrderValidator implements CommerceOrderValidator {

public static final String KEY = "example";
```

> It is important to provide a distinct key for your order validator so that Liferay Commerce can distinguish your new order validator from others in the [order validator registry](https://github.com/liferay/com-liferay-commerce/blob/7.1.x/commerce-service/src/main/java/com/liferay/commerce/internal/order/CommerceOrderValidatorRegistryImpl.java). Reusing a key that is already in use will override the existing associated validator.

### Implement the `CommerceOrderValidator` Interface

The following three methods are required:

```java
public String getKey();
```

```java
public CommerceOrderValidatorResult validate(Locale locale, CommerceOrder commerceOrder, CPInstance cpInstance, int quantity) throws PortalException;
```

```java
public CommerceOrderValidatorResult validate(Locale locale, CommerceOrderItem commerceOrderItem) throws PortalException;
```

To better understand each of the required methods mentioned above, let's look at [N9B2CommerceOrderValidator.java](./liferay-n9b2.zip/n9b2-impl/src/main/java/com/acme/n9b2/internal/commerce/order/validator/N9B2CommerceOrderValidator.java). We will review the implementation of each required method in sequence.

1. `public String getKey();`

```java
@Override
public String getKey() {
return KEY;
}
```

> This method provides a unique identifier for the order validator in the registry. The key can be used to fetch the validator from the registry programmatically if necessary. Reusing a key that is already in use will override the existing associated validator.

1. `public CommerceOrderValidatorResult validate(Locale locale, CommerceOrder commerceOrder, CPInstance cpInstance, int quantity) throws PortalException;`

```java
@Override
public CommerceOrderValidatorResult validate(
Locale locale, CommerceOrder commerceOrder, CPInstance cpInstance,
int quantity)
throws PortalException {

if (cpInstance == null) {
return new CommerceOrderValidatorResult(false);
}

return new CommerceOrderValidatorResult(true);
}
```

> This is one of the two validation methods where we will add our custom validation logic. This method is called whenever a customer adds an item to their cart. It does this by returning a `CommerceOrderValidatorResult`, which uses a boolean to determine whether or not the result passes validation; see this class at [CommerceOrderValidatorResult.java](https://github.com/liferay/com-liferay-commerce/blob/7.1.x/commerce-api/src/main/java/com/liferay/commerce/order/CommerceOrderValidatorResult.java).
>
> Note that this dummy implementation only has a simple check for a null value, which is a standard first step for this method.

1. `public CommerceOrderValidatorResult validate(Locale locale, CommerceOrderItem commerceOrderItem) throws PortalException;`

```java
@Override
public CommerceOrderValidatorResult validate(
Locale locale, CommerceOrderItem commerceOrderItem)
throws PortalException {

return new CommerceOrderValidatorResult(true);
}
```

> This is the second validation method where we can add custom validation logic. This method is called for items already in the cart, whenever a customer proceeds to a new step in checkout.

### Create Your Validation Steps

The two `validate()` methods are where we define the custom validation logic for our order validator. In our simple example, we will add logic to reject orders with more than ten of an item over a certain price.

### Create Your Method
1. `public CommerceOrderValidatorResult validate(Locale locale, CommerceOrder commerceOrder, CPInstance cpInstance, int quantity)`

```java
@Override
public CommerceOrderValidatorResult validate(
Locale locale, CommerceOrder commerceOrder, CPInstance cpInstance,
int quantity)
throws PortalException {

if (cpInstance == null) {
return new CommerceOrderValidatorResult(false);
}

BigDecimal price = cpInstance.getPrice();

if (price.doubleValue() > 100.0 && (quantity > 10)) {
ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
"content.Language", locale, getClass());

return new CommerceOrderValidatorResult(
false, LanguageUtil.get(
resourceBundle, "this-item-has-a-maximum-quantity-of-10"));
}

return new CommerceOrderValidatorResult(true);
}
```

> After a standard null check for this method, the main validation check for our example is to check if both the price (stored as a [BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html)) is more than $100, and the quantity is greater than ten. We get the price information from the CPInstance, which contains information about the order the customer has added; to find more methods you can use with a `CPInstance`, see [CPInstance](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-product-api/src/main/java/com/liferay/commerce/product/model/CPInstance.java) and [CPInstanceModel](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-product-api/src/main/java/com/liferay/commerce/product/model/CPInstanceModel.java).
>
> Note that, for our main validation checks, it is best practice to include a localized message explaining why the validation failed. For this to work correctly using `LanguageUtil`, we will need to add the language key ourselves. For more information, see [Localizing Your Application](https://help.liferay.com/hc/en-us/articles/360018168251-Localizing-Your-Application).

1. `public CommerceOrderValidatorResult validate(Locale locale, CommerceOrderItem commerceOrderItem)`

```java
@Override
public CommerceOrderValidatorResult validate(
Locale locale, CommerceOrderItem commerceOrderItem)
throws PortalException {

BigDecimal price = commerceOrderItem.getUnitPrice();

if (price.doubleValue() > 100.0 &&
commerceOrderItem.getQuantity() > 10) {

ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
"content.Language", locale, getClass());

return new CommerceOrderValidatorResult(
false, LanguageUtil.get(
resourceBundle,
"expensive-items-have-a-maximum-quantity-of-10"));
}

return new CommerceOrderValidatorResult(true);
}
```

> We can add the same validation logic to this method, since it will be called for the items in the customer's cart. The main difference in this case is we get the information from a `CommerceOrderItem` object; to find more methods you can use with a `CommerceOrderItem`, see [CommerceOrderItem](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-api/src/main/java/com/liferay/commerce/model/CommerceOrderItem.java) and [CommerceOrderItemModel](https://raw.githubusercontent.com/liferay/com-liferay-commerce/7.1.x/commerce-api/src/main/java/com/liferay/commerce/model/CommerceOrderItemModel.java).

We will also need to add the language keys for our validator's error messages. Add the keys and their values to a Language.properties file within our module:

```java
expensive-items-have-a-maximum-quantity-of-10=Expensive items have a maximum quantity of 10.
this-item-has-a-maximum-quantity-of-10=This item has a maximum quantity of 10.
```

## Conclusion

Congratulations! You now know the basics for implementing the `Foo` interface. basic payment method to Liferay Commerce.
Congratulations! You now know the basics for implementing the `CommerceOrderValidator` interface, and have added a new order validator to Liferay Commerce.

## Additional Information

[Localizing Your Application](https://help.liferay.com/hc/en-us/articles/360018168251-Localizing-Your-Application)

[Products](https://commerce.liferay.dev/user-guide/-/knowledge_base/user/products)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.defaults", version: "latest.release"
}

repositories {
mavenLocal()

maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}

apply plugin: "com.liferay.root.defaults.plugin"
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading