Using the Payment Integration Client Extension
Liferay DXP 2024.Q1+/Portal GA112+
You can use a client extension to integrate with a new payment method in Liferay. This client extension from the sample workspace consists of a standalone Spring Boot application that communicates with Liferay using OAuth 2. See Configuring Payment Methods to read more about the payment methods available out-of-the-box with Liferay.
Prerequisites
-
Install a supported version of Java.
NoteSee JVM Configuration for recommended JVM settings.
-
Download and unzip the sample workspace:
curl -o com.liferay.sample.workspace-latest.zip https://repository.liferay.com/nexus/service/local/artifact/maven/content\?r\=liferay-public-releases\&g\=com.liferay.workspace\&a\=com.liferay.sample.workspace\&\v\=LATEST\&p\=zipunzip -d liferay-sample-workspace com.liferay.sample.workspace-latest.zip
Now you have the tools to start and deploy the client extension(s) to Liferay.
Examine the Payment Integration Client Extension
The client-extensions/liferay-sample-commerce-payment-integration/client-extension.yaml file defines the payment integration client extension in the sample workspace. There are three important blocks in the .yaml file that you must understand:
assemble:
- fromTask: bootJar
The assemble block specifies that the standalone application/microservice is created with the bootJar command. This is available from the Spring Boot Gradle Plugin. The application JAR must be included in the LUFFA for deployment in Liferay SaaS.
liferay-sample-commerce-payment-integration:
key: liferay-sample-commerce-payment-integration
name: Liferay Sample Commerce Payment Integration
oAuth2ApplicationExternalReferenceCode: liferay-sample-commerce-payment-integration-oauth-application-user-agent
paymentIntegrationType: 3
paymentIntegrationTypeSettings:
key1: value1
key2: value2
key3: value3
key4: value4
key5: value5
type: commercePaymentIntegration
The liferay-sample-commerce-payment-integration block contains the key configurations required for a payment integration client extension. See Payment Integration Client Extension YAML Reference for more information on each field.
liferay-sample-commerce-payment-integration-oauth-application-user-agent:
.serviceAddress: localhost:58081
.serviceScheme: http
name: Liferay Sample Commerce Payment Integration OAuth Application User Agent
scopes:
- Liferay.Headless.Admin.Workflow.everything
type: oAuthApplicationUserAgent
Another important part of the client-extension.yaml is in the liferay-sample-commerce-payment-integration-oauth-application-user-agent definition. The serviceAddress parameter defines where the service runs locally, and the serviceScheme parameter defines the protocol. The name field defines the name of the OAuth application user agent. The scopes field defines the access given to the headless API. This section sets up Liferay as the authorization server, so that the payment integration you deploy next can invoke the resource server’s secure endpoints and send payloads. See OAuth User Agent YAML Configuration Reference for more information.
Deploy the Payment Integration Client Extension
-
Go to the sample workspace.
-
Run
./gradlew initBundleThis downloads a bundle inside the workspace’s
/bundlesfolder. -
Go to the
/bundles/tomcat/binfolder. Run./catalina run -
Go back to the sample workspace’s
/client-extensions/liferay-sample-commerce-payment-integrationfolder. -
Run
../../gradlew clean createClientExtensionConfig deploy -
In Liferay’s log, confirm that the client extension deployed and started:
2024-03-05 11:22:48.192 INFO [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:212] Processing liferay-sample-commerce-payment-integration.zip 2024-03-05 11:22:57.527 INFO [fileinstall-directory-watcher][BundleStartStopLogger:68] STARTED liferaysamplecommercepaymentintegration_7.4.13 [1499]In addition, messages about the OAuth user agent are logged.
2024-03-05 11:22:57.679 INFO [CM Event Dispatcher (Fire ConfigurationEvent: pid=com.liferay.oauth2.provider.configuration.OAuth2ProviderApplicationUserAgentConfiguration~liferay-sample-commerce-payment-integration-oauth-application-user-agent)][InterpolationConfigurationPlugin:135] Replaced value of configuration property 'homePageURL' for PID com.liferay.oauth2.provider.configuration.OAuth2ProviderApplicationUserAgentConfiguration~liferay-sample-commerce-payment-integration-oauth-application-user-agent 2024-03-05 11:22:57.712 INFO [CM Event Dispatcher (Fire ConfigurationEvent: pid=com.liferay.oauth2.provider.configuration.OAuth2ProviderApplicationUserAgentConfiguration~liferay-sample-commerce-payment-integration-oauth-application-user-agent)][OAuth2ProviderApplicationUserAgentConfigurationFactory:170] OAuth 2 application with external reference code liferay-sample-commerce-payment-integration-oauth-application-user-agent and company ID 93285384307986 has client ID id-5433f036-5672-de77-ea7b-a82110957aca -
Verify that the OAuth Application User Agent was added to Liferay. Go to Control Panel → OAuth2 Administration.

The Liferay Sample Commerce Payment Integration OAuth Application User Agent provides the OAuth 2 authorization needed so that Liferay can access the Spring Boot application’s data through its protected endpoint. All that is needed for Liferay to authorize the application in this case is declaring the external reference code in the application-default.properties:
liferay.oauth.application.external.reference.codes=liferay-sample-commerce-payment-integration-oauth-application-user-agent
Start the Microservice
From the client-extensions/liferay-sample-commerce-payment-integration folder, run
../../gradlew bootRun
The Spring Boot application starts and prints messages in the log:
...
2024-03-05 11:22:58.893 INFO 3534 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 58081 (http) with context path ''
2024-03-05 11:22:58.900 INFO 3534 --- [ main] c.l.sample.SampleSpringBootApplication : Started SampleSpringBootApplication in 21.133 seconds (JVM running for 21.397)
<==========---> 80% EXECUTING [7m 43s]
> :client-extensions:liferay-sample-commerce-payment-integration:bootRun
Verifying the Addition of the Payment Integration
-
Log in as an administrator, open the Global Menu (
), and go to Control Panel → Sites. -
Add a new Minium site.
-
Open the Global Menu (
) and go to Commerce → Channels. -
Select Minium Portal and scroll down to the Payment Methods section. Verify the addition of the new payment integration here. It is inactive by default.

-
Select the new payment integration and activate it using the Active toggle.
-
Click Save. Two new tabs, Eligibility and Configuration, appear for the payment integration.
The configuration tab contains an input field. You can enter information required by the client extension here instead of hard coding values in the client extension itself. The example contains sample key-value pairs.
You can go to the Eligibility tab to select specific order types or payment terms to be eligible for the payment integration. By default, it is eligible for all order types and payment terms.
-
Click Save.
-
Open the site and use the account selector to create a new account.
-
Add a few items to your cart.
-
Open the mini cart and click Submit. This starts the checkout flow.
-
Continue checking out until you finish placing the order. Open the Global Menu (
) and go to Commerce → Payments to verify the payment’s completion.

Examining the Code
To create a payment integration in Liferay, you must implement controllers for the following payment statuses.
- Set up Payment
- Authorize
- Capture
- Cancel
- Refund
The provided sample client extension contains these controllers invoked by the Spring Boot application to create a new payment integration in Liferay. The key and name fields in the client-extension.yaml file specifies the key and name for the payment integration. You should use a unique key that doesn’t conflict with any of the existing payment methods.
Examining SetUpPaymentRestController.java
@PostMapping
public ResponseEntity<String> post(
@AuthenticationPrincipal Jwt jwt, @RequestBody String json) {
log(jwt, _log, json);
return new ResponseEntity<>(
new JSONObject(
).put(
"paymentStatus", 18
).toString(),
HttpStatus.OK);
}
The SetUpPaymentRestController contains a single post method that has two parameters: the JSON Web Token (JWT) and the request body. The token authenticates HTTP calls, and the request body contains data as a string in JSON format. After logging the request body, it uses a JSONObject() constructor to set the payment status to 18 and returns it as a response entity along with the HTTP status.
Examining AuthorizeRestController.java
@PostMapping
public ResponseEntity<String> post(
@AuthenticationPrincipal Jwt jwt, @RequestBody String json) {
log(jwt, _log, json);
return new ResponseEntity<>(
new JSONObject(
).put(
"paymentStatus", 2
).put(
"transactionCode", UUID.randomUUID()
).toString(),
HttpStatus.OK);
}
The AuthorizeRestController contains a single post method that has two parameters: the JSON Web Token (JWT) and the request body. The token authenticates HTTP calls, and the request body contains data as a string in JSON format. After logging the request body, it uses a JSONObject() constructor to set the payment status to 2 and returns it as a response entity along with the HTTP status.
Examining CaptureRestController.java
@PostMapping
public ResponseEntity<String> post(
@AuthenticationPrincipal Jwt jwt, @RequestBody String json) {
log(jwt, _log, json);
return new ResponseEntity<>(
new JSONObject(
).put(
"paymentStatus", 0
).toString(),
HttpStatus.OK);
}
The CaptureRestController contains a single post method that has two parameters: the JSON Web Token (JWT) and the request body. The token authenticates HTTP calls, and the request body contains data as a string in JSON format. After logging the request body, it uses a JSONObject() constructor to set the payment status to 0 and returns it as a response entity along with the HTTP status.
Examining CancelRestController.java
@PostMapping
public ResponseEntity<String> post(
@AuthenticationPrincipal Jwt jwt, @RequestBody String json) {
log(jwt, _log, json);
return new ResponseEntity<>(
new JSONObject(
).put(
"paymentStatus", 8
).toString(),
HttpStatus.OK);
}
The CancelRestController contains a single post method that has two parameters: the JSON Web Token (JWT) and the request body. The token authenticates HTTP calls, and the request body contains data as a string in JSON format. After logging the request body, it uses a JSONObject() constructor to set the payment status to 8 and returns it as a response entity along with the HTTP status.
Examining RefundRestController.java
@PostMapping
public ResponseEntity<String> post(
@AuthenticationPrincipal Jwt jwt, @RequestBody String json) {
log(jwt, _log, json);
return new ResponseEntity<>(
new JSONObject(
).put(
"paymentStatus", 17
).toString(),
HttpStatus.OK);
}
The RefundRestController contains a single post method that has two parameters: the JSON Web Token (JWT) and the request body. The token authenticates HTTP calls, and the request body contains data as a string in JSON format. After logging the request body, it uses a JSONObject() constructor to set the payment status to 17 and returns it as a response entity along with the HTTP status.