This is an automated email from the ASF dual-hosted git repository.
mridulpathak pushed a commit to branch release24.09
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git
The following commit(s) were added to refs/heads/release24.09 by this push:
new fcddaf12a Fixed: First Data config form non-functional and Java
integration quality issues (OFBIZ-11837) (#313)
fcddaf12a is described below
commit fcddaf12ad3a62f8d5f287ed6e36540709847ef2
Author: Mridul Pathak <[email protected]>
AuthorDate: Fri Jul 3 16:41:54 2026 +0530
Fixed: First Data config form non-functional and Java integration quality
issues (OFBIZ-11837) (#313)
Fixed: First Data config form non-functional and Java integration
quality issues
(OFBIZ-11837)
UI & Configuration
- Fixed duplicate "Main" nav item inherited from CommonAppBarMenu
- Added create and delete entity-auto services for full CRUD; fixed form
with alt-target routing and "Add" button so it works without a
pre-existing config record
- Added controller request-maps for create and delete actions
- Removed non-existent appId from FirstDataDemoData.xml
- Replaced firstdata.properties with FirstDataSystemPropertyData.xml
(SystemProperty seed data)
Java Integration
- Fixed resource leaks: HTTP client and response now closed via
try-with-resources
- Fixed NPEs on null paymentGatewayResponse and null processor map
across all service methods
- Removed static fdProperties cache; config loaded fresh per call
(thread-safe, reflects DB changes)
- Fixed ccRefund missing orderPaymentPreferenceId filter in gateway
response query
- Removed redundant condition in ccAuth, unused orderId variable, and
dead FirstDataSaleTemplate.json.ftl
---
.../config/firstdata.properties | 23 --
firstdatapaymentgateway/data/FirstDataDemoData.xml | 2 +-
.../data/FirstDataSystemPropertyData.xml | 33 ++
firstdatapaymentgateway/ofbiz-component.xml | 1 +
firstdatapaymentgateway/servicedef/services.xml | 11 +-
.../FirstDataPaymentServices.java | 439 ++++++++++-----------
.../template/FirstDataSaleTemplate.json.ftl | 37 --
.../webapp/firstdata/WEB-INF/controller.xml | 12 +
firstdatapaymentgateway/widget/FirstDataForms.xml | 2 +
firstdatapaymentgateway/widget/FirstDataMenus.xml | 1 -
10 files changed, 278 insertions(+), 283 deletions(-)
diff --git a/firstdatapaymentgateway/config/firstdata.properties
b/firstdatapaymentgateway/config/firstdata.properties
deleted file mode 100644
index 82dc3d2d3..000000000
--- a/firstdatapaymentgateway/config/firstdata.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-###############################################################################
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-###############################################################################
-
-paymentgateway.firstdata.template.preauth.location=component://firstdatapaymentgateway/template/FirstDataPreAuthTemplate.json.ftl
-paymentgateway.firstdata.template.postauth.location=component://firstdatapaymentgateway/template/FirstDataPostAuthTemplate.json.ftl
-paymentgateway.firstdata.template.refund.location=component://firstdatapaymentgateway/template/FirstDataRefundTemplate.json.ftl
-paymentgateway.firstdata.template.release.location=component://firstdatapaymentgateway/template/FirstDataReleaseTemplate.json.ftl
\ No newline at end of file
diff --git a/firstdatapaymentgateway/data/FirstDataDemoData.xml
b/firstdatapaymentgateway/data/FirstDataDemoData.xml
index e73b58ac5..02c9ec68b 100644
--- a/firstdatapaymentgateway/data/FirstDataDemoData.xml
+++ b/firstdatapaymentgateway/data/FirstDataDemoData.xml
@@ -20,7 +20,7 @@ under the License.
<entity-engine-xml>
<PaymentGatewayConfig paymentGatewayConfigId="FIRST_DATA_CONFIG"
paymentGatewayConfigTypeId="PAY_GATWY_FIRSTDATA" />
- <PaymentGatewayFirstData paymentGatewayConfigId="FIRST_DATA_CONFIG"
appName="[AppName]" appId="[AppID]" apiKey="[ApiKey]" apiSecret="[ApiSecret]"
transactionUrl="[TransactionUrl]" enableDataVault="N" />
+ <PaymentGatewayFirstData paymentGatewayConfigId="FIRST_DATA_CONFIG"
appName="[AppName]" apiKey="[ApiKey]" apiSecret="[ApiSecret]"
transactionUrl="[TransactionUrl]" enableDataVault="N" />
<ProductStorePaymentSetting productStoreId="9000"
paymentMethodTypeId="CREDIT_CARD" paymentServiceTypeEnumId="PRDS_PAY_AUTH"
paymentService="" paymentCustomMethodId="CC_AUTH_FD"
paymentGatewayConfigId="FIRST_DATA_CONFIG"/>
<ProductStorePaymentSetting productStoreId="9000"
paymentMethodTypeId="CREDIT_CARD" paymentServiceTypeEnumId="PRDS_PAY_CAPTURE"
paymentService="" paymentCustomMethodId="CC_CAPTURE_FD"
paymentGatewayConfigId="FIRST_DATA_CONFIG"/>
diff --git a/firstdatapaymentgateway/data/FirstDataSystemPropertyData.xml
b/firstdatapaymentgateway/data/FirstDataSystemPropertyData.xml
new file mode 100644
index 000000000..52afd0454
--- /dev/null
+++ b/firstdatapaymentgateway/data/FirstDataSystemPropertyData.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<entity-engine-xml>
+ <SystemProperty systemResourceId="firstdata"
systemPropertyId="paymentgateway.firstdata.template.preauth.location"
+
systemPropertyValue="component://firstdatapaymentgateway/template/FirstDataPreAuthTemplate.json.ftl"
+ description="FreeMarker template location for First Data
pre-authorization (authorize) requests"/>
+ <SystemProperty systemResourceId="firstdata"
systemPropertyId="paymentgateway.firstdata.template.postauth.location"
+
systemPropertyValue="component://firstdatapaymentgateway/template/FirstDataPostAuthTemplate.json.ftl"
+ description="FreeMarker template location for First Data
post-authorization (capture) requests"/>
+ <SystemProperty systemResourceId="firstdata"
systemPropertyId="paymentgateway.firstdata.template.refund.location"
+
systemPropertyValue="component://firstdatapaymentgateway/template/FirstDataRefundTemplate.json.ftl"
+ description="FreeMarker template location for First Data refund
requests"/>
+ <SystemProperty systemResourceId="firstdata"
systemPropertyId="paymentgateway.firstdata.template.release.location"
+
systemPropertyValue="component://firstdatapaymentgateway/template/FirstDataReleaseTemplate.json.ftl"
+ description="FreeMarker template location for First Data release
(void) requests"/>
+</entity-engine-xml>
diff --git a/firstdatapaymentgateway/ofbiz-component.xml
b/firstdatapaymentgateway/ofbiz-component.xml
index 430514224..1eca98680 100644
--- a/firstdatapaymentgateway/ofbiz-component.xml
+++ b/firstdatapaymentgateway/ofbiz-component.xml
@@ -29,6 +29,7 @@ under the License.
<!-- entity resources: model(s), eca(s), group, and data definitions -->
<entity-resource type="model" reader-name="main" loader="main"
location="entitydef/entitymodel.xml"/>
<!-- <entity-resource type="eca" reader-name="main" loader="main"
location="entitydef/eecas.xml"/> -->
+ <entity-resource type="data" reader-name="seed" loader="main"
location="data/FirstDataSystemPropertyData.xml"/>
<entity-resource type="data" reader-name="seed" loader="main"
location="data/FirstDataTypeData.xml"/>
<entity-resource type="data" reader-name="seed" loader="main"
location="data/FirstDataSecurityPermissionSeedData.xml"/>
<entity-resource type="data" reader-name="demo" loader="main"
location="data/FirstDataSecurityGroupDemoData.xml"/>
diff --git a/firstdatapaymentgateway/servicedef/services.xml
b/firstdatapaymentgateway/servicedef/services.xml
index 850af88fb..2c6060609 100644
--- a/firstdatapaymentgateway/servicedef/services.xml
+++ b/firstdatapaymentgateway/servicedef/services.xml
@@ -24,11 +24,20 @@ under the License.
<vendor></vendor>
<version>1.0</version>
+ <service name="createPaymentGatewayFirstData"
default-entity-name="PaymentGatewayFirstData" engine="entity-auto"
invoke="create" auth="true">
+ <description>Create a PaymentGatewayFirstData</description>
+ <auto-attributes include="pk" mode="INOUT" optional="true"/>
+ <auto-attributes include="nonpk" mode="IN" optional="true"/>
+ </service>
<service name="updatePaymentGatewayFirstData"
default-entity-name="PaymentGatewayFirstData" engine="entity-auto"
invoke="update" auth="true">
- <description>Update a PaymentGatwayFirstData</description>
+ <description>Update a PaymentGatewayFirstData</description>
<auto-attributes include="pk" mode="IN" optional="false"/>
<auto-attributes include="nonpk" mode="IN" optional="true"/>
</service>
+ <service name="deletePaymentGatewayFirstData"
default-entity-name="PaymentGatewayFirstData" engine="entity-auto"
invoke="delete" auth="true">
+ <description>Delete a PaymentGatewayFirstData</description>
+ <auto-attributes include="pk" mode="IN" optional="false"/>
+ </service>
<!-- First Data Implementations -->
<service name="firstDataCCAuth" engine="java"
diff --git
a/firstdatapaymentgateway/src/main/java/org/apache/ofbiz/firstdatapaymentgateway/FirstDataPaymentServices.java
b/firstdatapaymentgateway/src/main/java/org/apache/ofbiz/firstdatapaymentgateway/FirstDataPaymentServices.java
index dc117a45d..757e139b7 100644
---
a/firstdatapaymentgateway/src/main/java/org/apache/ofbiz/firstdatapaymentgateway/FirstDataPaymentServices.java
+++
b/firstdatapaymentgateway/src/main/java/org/apache/ofbiz/firstdatapaymentgateway/FirstDataPaymentServices.java
@@ -49,7 +49,6 @@ import org.apache.ofbiz.entity.GenericValue;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
import org.apache.ofbiz.service.DispatchContext;
-import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.service.ServiceUtil;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -60,12 +59,9 @@ import freemarker.template.TemplateException;
public class FirstDataPaymentServices {
private static final String MODULE =
FirstDataPaymentServices.class.getName();
- private static Properties fdProperties = null;
-
public static Map<String, Object> ccAuth(DispatchContext ctx, Map<String,
Object> context) {
Delegator delegator = ctx.getDelegator();
BigDecimal processAmount = (BigDecimal) context.get("processAmount");
- String orderId = (String) context.get("orderId");
String currency = (String) context.get("currency");
String cardSecurityCode = (String) context.get("cardSecurityCode");
GenericValue creditCard = (GenericValue) context.get("creditCard");
@@ -77,6 +73,7 @@ public class FirstDataPaymentServices {
String cardNumber = creditCard.getString("cardNumber");
try {
+ Properties fdProperties =
buildFDProperties(paymentGatewayConfigId, delegator);
String clientRequestId = UUID.randomUUID().toString();
String epochTime = String.valueOf(System.currentTimeMillis());
Date expireDate = new
SimpleDateFormat("MM/yyyy").parse(creditCard.getString("expireDate"));
@@ -85,7 +82,7 @@ public class FirstDataPaymentServices {
df = new SimpleDateFormat("yy");
String strYear = df.format(expireDate);
- Map<String, Object> ccAuthReqContext = new HashMap<String,
Object>();
+ Map<String, Object> ccAuthReqContext = new HashMap<>();
ccAuthReqContext.put("amount", processAmount);
ccAuthReqContext.put("currency", currency);
ccAuthReqContext.put("cardSecurityCode", cardSecurityCode);
@@ -99,52 +96,54 @@ public class FirstDataPaymentServices {
FreeMarkerWorker.renderTemplate(firstDataPreAuthTemplate,
ccAuthReqContext, outWriter);
String requestBody = outWriter.toString();
- String messageSignature =
buildMessageSignature(paymentGatewayConfigId, requestBody, clientRequestId,
epochTime, delegator);
-
- CloseableHttpClient httpClient = HttpClients.createDefault();
- StringEntity stringEntity = new StringEntity(requestBody);
- HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments");
- httpPost.setEntity(stringEntity);
- httpPost.setHeader("Client-Request-Id", clientRequestId);
- httpPost.setHeader("Api-Key", fdProperties.getProperty("apiKey"));
- httpPost.setHeader("Timestamp", epochTime);
- httpPost.setHeader("Message-Signature", messageSignature);
- httpPost.setHeader("Content-Type", "application/json");
-
- CloseableHttpResponse response = httpClient.execute(httpPost);
-
- HttpEntity entity = response.getEntity();
- String responseString = EntityUtils.toString(entity);
-
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
- });
-
- String transactionStatus = (String)
convertedMap.get("transactionStatus");
- String transactionId = (String)
convertedMap.get("ipgTransactionId");
- String fdOrderId = (String) convertedMap.get("orderId");
- result.put("authRefNum", transactionId);
- result.put("authAltRefNum", fdOrderId);
- if ("APPROVED".equalsIgnoreCase(transactionStatus) ||
"WAITING".equalsIgnoreCase(transactionStatus)) {
- Map<String, Object> processor =
objectMapper.convertValue(convertedMap.get("processor"), new
TypeReference<Map<String, Object>>() {
- });
- gatewayMessage = (String) processor.get("responseMessage");
- int statusCode = response.getStatusLine().getStatusCode();
- result.put("authCode", String.valueOf(statusCode));
- result.put("authMessage", gatewayMessage);
- if (UtilValidate.isNotEmpty(transactionId)) {
+ String messageSignature = buildMessageSignature(fdProperties,
requestBody, clientRequestId, epochTime);
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
+ StringEntity stringEntity = new StringEntity(requestBody);
+ HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments");
+ httpPost.setEntity(stringEntity);
+ httpPost.setHeader("Client-Request-Id", clientRequestId);
+ httpPost.setHeader("Api-Key",
fdProperties.getProperty("apiKey"));
+ httpPost.setHeader("Timestamp", epochTime);
+ httpPost.setHeader("Message-Signature", messageSignature);
+ httpPost.setHeader("Content-Type", "application/json");
+
+ try (CloseableHttpResponse response =
httpClient.execute(httpPost)) {
+ HttpEntity entity = response.getEntity();
+ String responseString = EntityUtils.toString(entity);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
+ });
+
+ String transactionStatus = (String)
convertedMap.get("transactionStatus");
+ String transactionId = (String)
convertedMap.get("ipgTransactionId");
+ String fdOrderId = (String) convertedMap.get("orderId");
+ result.put("authRefNum", transactionId);
+ result.put("authAltRefNum", fdOrderId);
if ("APPROVED".equalsIgnoreCase(transactionStatus) ||
"WAITING".equalsIgnoreCase(transactionStatus)) {
- isSuccess = Boolean.TRUE;
+ Object processorObj = convertedMap.get("processor");
+ if (processorObj != null) {
+ Map<String, Object> processor =
objectMapper.convertValue(processorObj, new TypeReference<Map<String,
Object>>() {
+ });
+ gatewayMessage = (String)
processor.get("responseMessage");
+ }
+ int statusCode =
response.getStatusLine().getStatusCode();
+ result.put("authCode", String.valueOf(statusCode));
+ result.put("authMessage", gatewayMessage);
+ if (UtilValidate.isNotEmpty(transactionId)) {
+ isSuccess = Boolean.TRUE;
+ }
+ }
+ if (!isSuccess) {
+ String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId
+ + " Transaction Status: " + transactionStatus;
+ gatewayMessage =
UtilValidate.isNotEmpty(gatewayMessage) ? gatewayMessage : "";
+ errorMessage = errorMessage + " Gateway Message: " +
gatewayMessage;
+ result.put("authMessage", errorMessage);
}
}
}
- if (!isSuccess) {
- String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId + " "
- + "Transaction Status: " + transactionStatus;
- gatewayMessage = UtilValidate.isNotEmpty(gatewayMessage) ?
gatewayMessage : "";
- errorMessage = errorMessage + " Gateway Message: " +
gatewayMessage;
- result.put(ModelService.ERROR_MESSAGE, errorMessage);
- }
} catch (ParseException | TemplateException | IOException e) {
Debug.logError(e, "Could not complete First Data transaction: " +
e.toString(), MODULE);
}
@@ -164,74 +163,80 @@ public class FirstDataPaymentServices {
Boolean isSuccess = Boolean.FALSE;
try {
+ Properties fdProperties =
buildFDProperties(paymentGatewayConfigId, delegator);
String clientRequestId = UUID.randomUUID().toString();
String epochTime = String.valueOf(System.currentTimeMillis());
- Map<String, Object> ccPostAuthReqContext = new HashMap<String,
Object>();
+ Map<String, Object> ccPostAuthReqContext = new HashMap<>();
ccPostAuthReqContext.put("amount", captureAmount);
ccPostAuthReqContext.put("currency", currency);
StringWriter outWriter = new StringWriter();
- String firstDataPreAuthTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.postauth"
+ String firstDataPostAuthTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.postauth"
+ ".location", delegator);
- FreeMarkerWorker.renderTemplate(firstDataPreAuthTemplate,
ccPostAuthReqContext, outWriter);
+ FreeMarkerWorker.renderTemplate(firstDataPostAuthTemplate,
ccPostAuthReqContext, outWriter);
String requestBody = outWriter.toString();
- String messageSignature =
buildMessageSignature(paymentGatewayConfigId, requestBody, clientRequestId,
epochTime, delegator);
+ String messageSignature = buildMessageSignature(fdProperties,
requestBody, clientRequestId, epochTime);
GenericValue paymentGatewayResponse =
EntityQuery.use(delegator).from("PaymentGatewayResponse")
.where("orderPaymentPreferenceId",
orderPaymentPreference.getString("orderPaymentPreferenceId"), "paymentMethodId",
orderPaymentPreference.getString("paymentMethodId"), "transCodeEnumId",
"PGT_AUTHORIZE", "paymentServiceTypeEnumId",
"PRDS_PAY_AUTH")
.queryFirst();
- String authTransactionId = null;
- if
(UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))) {
- authTransactionId =
paymentGatewayResponse.getString("referenceNum");
- } else {
- authTransactionId =
paymentGatewayResponse.getString("altReference");
+ if (paymentGatewayResponse == null) {
+ return ServiceUtil.returnError("No authorization payment
gateway response found for orderPaymentPreferenceId: "
+ +
orderPaymentPreference.getString("orderPaymentPreferenceId"));
}
-
- CloseableHttpClient httpClient = HttpClients.createDefault();
- StringEntity stringEntity = new StringEntity(requestBody);
- HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
authTransactionId);
- httpPost.setEntity(stringEntity);
- httpPost.setHeader("Client-Request-Id", clientRequestId);
- httpPost.setHeader("Api-Key", fdProperties.getProperty("apiKey"));
- httpPost.setHeader("Timestamp", epochTime);
- httpPost.setHeader("Message-Signature", messageSignature);
- httpPost.setHeader("Content-Type", "application/json");
-
- CloseableHttpResponse response = httpClient.execute(httpPost);
-
- HttpEntity entity = response.getEntity();
- String responseString = EntityUtils.toString(entity);
-
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
- });
-
- String transactionStatus = (String)
convertedMap.get("transactionStatus");
- String transactionId = (String)
convertedMap.get("ipgTransactionId");
- String fdOrderId = (String) convertedMap.get("orderId");
- Map<String, Object> processor =
objectMapper.convertValue(convertedMap.get("processor"), new
TypeReference<Map<String, Object>>() {
- });
- String gatewayMessage = (String) processor.get("responseMessage");
- int statusCode = response.getStatusLine().getStatusCode();
- result.put("captureCode", String.valueOf(statusCode));
- result.put("captureMessage", gatewayMessage);
- if (UtilValidate.isNotEmpty(transactionId)) {
- result.put("captureRefNum", transactionId);
- result.put("captureAltRefNum", fdOrderId);
- if ("approved".equalsIgnoreCase(transactionStatus)) {
- isSuccess = Boolean.TRUE;
+ String authTransactionId =
UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))
+ ? paymentGatewayResponse.getString("referenceNum")
+ : paymentGatewayResponse.getString("altReference");
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
+ StringEntity stringEntity = new StringEntity(requestBody);
+ HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
authTransactionId);
+ httpPost.setEntity(stringEntity);
+ httpPost.setHeader("Client-Request-Id", clientRequestId);
+ httpPost.setHeader("Api-Key",
fdProperties.getProperty("apiKey"));
+ httpPost.setHeader("Timestamp", epochTime);
+ httpPost.setHeader("Message-Signature", messageSignature);
+ httpPost.setHeader("Content-Type", "application/json");
+
+ try (CloseableHttpResponse response =
httpClient.execute(httpPost)) {
+ HttpEntity entity = response.getEntity();
+ String responseString = EntityUtils.toString(entity);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
+ });
+
+ String transactionStatus = (String)
convertedMap.get("transactionStatus");
+ String transactionId = (String)
convertedMap.get("ipgTransactionId");
+ String fdOrderId = (String) convertedMap.get("orderId");
+ int statusCode = response.getStatusLine().getStatusCode();
+ String gatewayMessage = "";
+ Object processorObj = convertedMap.get("processor");
+ if (processorObj != null) {
+ Map<String, Object> processor =
objectMapper.convertValue(processorObj, new TypeReference<Map<String,
Object>>() {
+ });
+ gatewayMessage = (String)
processor.get("responseMessage");
+ }
+ result.put("captureCode", String.valueOf(statusCode));
+ result.put("captureMessage", gatewayMessage);
+ if (UtilValidate.isNotEmpty(transactionId)) {
+ result.put("captureRefNum", transactionId);
+ result.put("captureAltRefNum", fdOrderId);
+ if ("APPROVED".equalsIgnoreCase(transactionStatus)) {
+ isSuccess = Boolean.TRUE;
+ }
+ }
+ if (!isSuccess) {
+ String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId
+ + " Transaction Status: " + transactionStatus
+ " Message: " + statusCode + "-" + gatewayMessage;
+ result.put("captureMessage", errorMessage);
+ }
}
}
- if (!isSuccess) {
- String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId + " "
- + "Transaction Status: " + transactionStatus;
- errorMessage = errorMessage + " Message: " + statusCode + "-"
+ gatewayMessage;
- result.put(ModelService.ERROR_MESSAGE, errorMessage);
- }
} catch (TemplateException | IOException | GenericEntityException e) {
Debug.logError(e, "Could not complete First Data transaction: " +
e.toString(), MODULE);
}
@@ -251,74 +256,81 @@ public class FirstDataPaymentServices {
Boolean isSuccess = Boolean.FALSE;
try {
+ Properties fdProperties =
buildFDProperties(paymentGatewayConfigId, delegator);
String clientRequestId = UUID.randomUUID().toString();
String epochTime = String.valueOf(System.currentTimeMillis());
- Map<String, Object> ccRefundReqContext = new HashMap<String,
Object>();
+ Map<String, Object> ccRefundReqContext = new HashMap<>();
ccRefundReqContext.put("amount", refundAmount);
ccRefundReqContext.put("currency", currency);
StringWriter outWriter = new StringWriter();
- String firstDataPreAuthTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.refund"
+ String firstDataRefundTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.refund"
+ ".location", delegator);
- FreeMarkerWorker.renderTemplate(firstDataPreAuthTemplate,
ccRefundReqContext, outWriter);
+ FreeMarkerWorker.renderTemplate(firstDataRefundTemplate,
ccRefundReqContext, outWriter);
String requestBody = outWriter.toString();
- String messageSignature =
buildMessageSignature(paymentGatewayConfigId, requestBody, clientRequestId,
epochTime, delegator);
+ String messageSignature = buildMessageSignature(fdProperties,
requestBody, clientRequestId, epochTime);
GenericValue paymentGatewayResponse = EntityQuery.use(delegator)
.from("PaymentGatewayResponse")
- .where("paymentMethodId",
orderPaymentPreference.getString("paymentMethodId"), "transCodeEnumId",
"PGT_CAPTURE",
+ .where("orderPaymentPreferenceId",
orderPaymentPreference.getString("orderPaymentPreferenceId"), "paymentMethodId",
+
orderPaymentPreference.getString("paymentMethodId"), "transCodeEnumId",
"PGT_CAPTURE",
"paymentServiceTypeEnumId", "PRDS_PAY_CAPTURE")
.queryFirst();
- String captureTransactionId = null;
- if
(UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))) {
- captureTransactionId =
paymentGatewayResponse.getString("referenceNum");
- } else {
- captureTransactionId =
paymentGatewayResponse.getString("altReference");
+ if (paymentGatewayResponse == null) {
+ return ServiceUtil.returnError("No capture payment gateway
response found for orderPaymentPreferenceId: "
+ +
orderPaymentPreference.getString("orderPaymentPreferenceId"));
}
-
- CloseableHttpClient httpClient = HttpClients.createDefault();
- StringEntity stringEntity = new StringEntity(requestBody);
- HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
captureTransactionId);
- httpPost.setEntity(stringEntity);
- httpPost.setHeader("Client-Request-Id", clientRequestId);
- httpPost.setHeader("Api-Key", fdProperties.getProperty("apiKey"));
- httpPost.setHeader("Timestamp", epochTime);
- httpPost.setHeader("Message-Signature", messageSignature);
- httpPost.setHeader("Content-Type", "application/json");
-
- CloseableHttpResponse response = httpClient.execute(httpPost);
-
- HttpEntity entity = response.getEntity();
- String responseString = EntityUtils.toString(entity);
-
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
- });
-
- String transactionStatus = (String)
convertedMap.get("transactionStatus");
- String transactionId = (String)
convertedMap.get("ipgTransactionId");
- String fdOrderId = (String) convertedMap.get("orderId");
- Map<String, Object> processor =
objectMapper.convertValue(convertedMap.get("processor"), new
TypeReference<Map<String, Object>>() {
- });
- String gatewayMessage = (String) processor.get("responseMessage");
- int statusCode = response.getStatusLine().getStatusCode();
- result.put("refundCode", String.valueOf(statusCode));
- result.put("refundMessage", gatewayMessage);
- if (UtilValidate.isNotEmpty(transactionId)) {
- result.put("refundRefNum", transactionId);
- result.put("refundAltRefNum", fdOrderId);
- if ("approved".equalsIgnoreCase(transactionStatus)) {
- isSuccess = Boolean.TRUE;
+ String captureTransactionId =
UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))
+ ? paymentGatewayResponse.getString("referenceNum")
+ : paymentGatewayResponse.getString("altReference");
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
+ StringEntity stringEntity = new StringEntity(requestBody);
+ HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
captureTransactionId);
+ httpPost.setEntity(stringEntity);
+ httpPost.setHeader("Client-Request-Id", clientRequestId);
+ httpPost.setHeader("Api-Key",
fdProperties.getProperty("apiKey"));
+ httpPost.setHeader("Timestamp", epochTime);
+ httpPost.setHeader("Message-Signature", messageSignature);
+ httpPost.setHeader("Content-Type", "application/json");
+
+ try (CloseableHttpResponse response =
httpClient.execute(httpPost)) {
+ HttpEntity entity = response.getEntity();
+ String responseString = EntityUtils.toString(entity);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
+ });
+
+ String transactionStatus = (String)
convertedMap.get("transactionStatus");
+ String transactionId = (String)
convertedMap.get("ipgTransactionId");
+ String fdOrderId = (String) convertedMap.get("orderId");
+ int statusCode = response.getStatusLine().getStatusCode();
+ String gatewayMessage = "";
+ Object processorObj = convertedMap.get("processor");
+ if (processorObj != null) {
+ Map<String, Object> processor =
objectMapper.convertValue(processorObj, new TypeReference<Map<String,
Object>>() {
+ });
+ gatewayMessage = (String)
processor.get("responseMessage");
+ }
+ result.put("refundCode", String.valueOf(statusCode));
+ result.put("refundMessage", gatewayMessage);
+ if (UtilValidate.isNotEmpty(transactionId)) {
+ result.put("refundRefNum", transactionId);
+ result.put("refundAltRefNum", fdOrderId);
+ if ("APPROVED".equalsIgnoreCase(transactionStatus)) {
+ isSuccess = Boolean.TRUE;
+ }
+ }
+ if (!isSuccess) {
+ String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId
+ + " Transaction Status: " + transactionStatus
+ " Message: " + statusCode + "-" + gatewayMessage;
+ result.put("refundMessage", errorMessage);
+ }
}
}
- if (!isSuccess) {
- String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId + " "
- + "Transaction Status: " + transactionStatus;
- errorMessage = errorMessage + " Message: " + statusCode + "-"
+ gatewayMessage;
- result.put(ModelService.ERROR_MESSAGE, errorMessage);
- }
} catch (TemplateException | IOException | GenericEntityException e) {
Debug.logError(e, "Could not complete First Data transaction: " +
e.toString(), MODULE);
}
@@ -338,75 +350,81 @@ public class FirstDataPaymentServices {
Boolean isSuccess = Boolean.FALSE;
try {
+ Properties fdProperties =
buildFDProperties(paymentGatewayConfigId, delegator);
String clientRequestId = UUID.randomUUID().toString();
String epochTime = String.valueOf(System.currentTimeMillis());
- Map<String, Object> ccReleaseReqContext = new HashMap<String,
Object>();
+ Map<String, Object> ccReleaseReqContext = new HashMap<>();
ccReleaseReqContext.put("comments",
"The amount " + currency + " " + releaseAmount + " against
OrderPaymentPreferenceId " + orderPaymentPreference.getString(
"orderPaymentPreferenceId") + " is released.");
StringWriter outWriter = new StringWriter();
- String firstDataPreAuthTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.release"
+ String firstDataReleaseTemplate =
EntityUtilProperties.getPropertyValue("firstdata",
"paymentgateway.firstdata.template.release"
+ ".location", delegator);
- FreeMarkerWorker.renderTemplate(firstDataPreAuthTemplate,
ccReleaseReqContext, outWriter);
+ FreeMarkerWorker.renderTemplate(firstDataReleaseTemplate,
ccReleaseReqContext, outWriter);
String requestBody = outWriter.toString();
- String messageSignature =
buildMessageSignature(paymentGatewayConfigId, requestBody, clientRequestId,
epochTime, delegator);
+ String messageSignature = buildMessageSignature(fdProperties,
requestBody, clientRequestId, epochTime);
GenericValue paymentGatewayResponse =
EntityQuery.use(delegator).from("PaymentGatewayResponse")
.where("orderPaymentPreferenceId",
orderPaymentPreference.getString("orderPaymentPreferenceId"), "paymentMethodId",
orderPaymentPreference.getString("paymentMethodId"), "transCodeEnumId",
"PGT_AUTHORIZE", "paymentServiceTypeEnumId",
"PRDS_PAY_AUTH")
.queryFirst();
- String releaseTransactionId = null;
- if
(UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))) {
- releaseTransactionId =
paymentGatewayResponse.getString("referenceNum");
- } else {
- releaseTransactionId =
paymentGatewayResponse.getString("altReference");
+ if (paymentGatewayResponse == null) {
+ return ServiceUtil.returnError("No authorization payment
gateway response found for orderPaymentPreferenceId: "
+ +
orderPaymentPreference.getString("orderPaymentPreferenceId"));
}
-
- CloseableHttpClient httpClient = HttpClients.createDefault();
- StringEntity stringEntity = new StringEntity(requestBody);
- HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
releaseTransactionId);
- httpPost.setEntity(stringEntity);
- httpPost.setHeader("Client-Request-Id", clientRequestId);
- httpPost.setHeader("Api-Key", fdProperties.getProperty("apiKey"));
- httpPost.setHeader("Timestamp", epochTime);
- httpPost.setHeader("Message-Signature", messageSignature);
- httpPost.setHeader("Content-Type", "application/json");
-
- CloseableHttpResponse response = httpClient.execute(httpPost);
-
- HttpEntity entity = response.getEntity();
- String responseString = EntityUtils.toString(entity);
-
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
- });
-
- String transactionStatus = (String)
convertedMap.get("transactionStatus");
- String transactionId = (String)
convertedMap.get("ipgTransactionId");
- String fdOrderId = (String) convertedMap.get("orderId");
- Map<String, Object> processor =
objectMapper.convertValue(convertedMap.get("processor"), new
TypeReference<Map<String, Object>>() {
- });
- String gatewayMessage = (String) processor.get("responseMessage");
- int statusCode = response.getStatusLine().getStatusCode();
- result.put("releaseCode", String.valueOf(statusCode));
- result.put("releaseMessage", gatewayMessage);
- if (UtilValidate.isNotEmpty(transactionId)) {
- result.put("releaseRefNum", transactionId);
- result.put("releaseAltRefNum", fdOrderId);
- if ("approved".equalsIgnoreCase(transactionStatus)) {
- isSuccess = Boolean.TRUE;
+ String releaseTransactionId =
UtilValidate.isNotEmpty(paymentGatewayResponse.getString("referenceNum"))
+ ? paymentGatewayResponse.getString("referenceNum")
+ : paymentGatewayResponse.getString("altReference");
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
+ StringEntity stringEntity = new StringEntity(requestBody);
+ HttpPost httpPost = new
HttpPost(fdProperties.getProperty("transactionUrl") + "/payments/" +
releaseTransactionId);
+ httpPost.setEntity(stringEntity);
+ httpPost.setHeader("Client-Request-Id", clientRequestId);
+ httpPost.setHeader("Api-Key",
fdProperties.getProperty("apiKey"));
+ httpPost.setHeader("Timestamp", epochTime);
+ httpPost.setHeader("Message-Signature", messageSignature);
+ httpPost.setHeader("Content-Type", "application/json");
+
+ try (CloseableHttpResponse response =
httpClient.execute(httpPost)) {
+ HttpEntity entity = response.getEntity();
+ String responseString = EntityUtils.toString(entity);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, Object> convertedMap =
objectMapper.readValue(responseString, new TypeReference<Map<String, Object>>()
{
+ });
+
+ String transactionStatus = (String)
convertedMap.get("transactionStatus");
+ String transactionId = (String)
convertedMap.get("ipgTransactionId");
+ String fdOrderId = (String) convertedMap.get("orderId");
+ int statusCode = response.getStatusLine().getStatusCode();
+ String gatewayMessage = "";
+ Object processorObj = convertedMap.get("processor");
+ if (processorObj != null) {
+ Map<String, Object> processor =
objectMapper.convertValue(processorObj, new TypeReference<Map<String,
Object>>() {
+ });
+ gatewayMessage = (String)
processor.get("responseMessage");
+ }
+ result.put("releaseCode", String.valueOf(statusCode));
+ result.put("releaseMessage", gatewayMessage);
+ if (UtilValidate.isNotEmpty(transactionId)) {
+ result.put("releaseRefNum", transactionId);
+ result.put("releaseAltRefNum", fdOrderId);
+ if ("APPROVED".equalsIgnoreCase(transactionStatus)) {
+ isSuccess = Boolean.TRUE;
+ }
+ }
+ if (!isSuccess) {
+ String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId
+ + " Transaction Status: " + transactionStatus
+ " Message: " + statusCode + "-" + gatewayMessage;
+ result.put("releaseMessage", errorMessage);
+ }
}
}
- if (!isSuccess) {
- String errorMessage = "Transaction Type:" + (String)
convertedMap.get("transactionType") + " Transaction Id: " + transactionId + " "
- + "Transaction Status: " + transactionStatus;
- errorMessage = errorMessage + " Message: " + statusCode + "-"
+ gatewayMessage;
- result.put(ModelService.ERROR_MESSAGE, errorMessage);
- }
} catch (TemplateException | IOException | GenericEntityException e) {
Debug.logError(e, "Could not complete First Data transaction: " +
e.toString(), MODULE);
}
@@ -415,23 +433,11 @@ public class FirstDataPaymentServices {
}
private static Properties buildFDProperties(String paymentGatewayConfigId,
Delegator delegator) {
- String transactionUrl = getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "transactionUrl");
- String appName = getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "appName");
- String apiKey = getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "apiKey");
- String apiSecret = getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "apiSecret");
- //String enableDataVault = getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "enableDataVault");
-
Properties props = new Properties();
- props.put("transactionUrl", transactionUrl);
- props.put("appName", appName);
- props.put("apiKey", apiKey);
- props.put("apiSecret", apiSecret);
- //props.put("enableDataVault", enableDataVault);
-
- if (fdProperties == null) {
- fdProperties = props;
- }
-
+ props.put("transactionUrl", getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "transactionUrl"));
+ props.put("appName", getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "appName"));
+ props.put("apiKey", getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "apiKey"));
+ props.put("apiSecret", getPaymentGatewayConfigValue(delegator,
paymentGatewayConfigId, "apiSecret"));
return props;
}
@@ -454,20 +460,13 @@ public class FirstDataPaymentServices {
return returnValue;
}
- private static String buildMessageSignature(String paymentGatewayConfigId,
String requestBody, String clientRequestId, String epochTime,
- Delegator delegator) {
- String messageSignature = null;
- if (fdProperties == null) {
- buildFDProperties(paymentGatewayConfigId, delegator);
- }
-
+ private static String buildMessageSignature(Properties fdProperties,
String requestBody, String clientRequestId, String epochTime) {
String apiKey = fdProperties.getProperty("apiKey");
final HmacUtils hmacHelper = new
HmacUtils(HmacAlgorithms.HMAC_SHA_256, fdProperties.getProperty("apiSecret"));
final Hex hexHelper = new Hex();
final String msg = apiKey + clientRequestId + epochTime + requestBody;
final byte[] raw = hmacHelper.hmac(msg);
final byte[] hex = hexHelper.encode(raw);
- messageSignature = Base64.encodeBase64String(hex);
- return messageSignature;
+ return Base64.encodeBase64String(hex);
}
}
diff --git a/firstdatapaymentgateway/template/FirstDataSaleTemplate.json.ftl
b/firstdatapaymentgateway/template/FirstDataSaleTemplate.json.ftl
deleted file mode 100644
index 66f1f2182..000000000
--- a/firstdatapaymentgateway/template/FirstDataSaleTemplate.json.ftl
+++ /dev/null
@@ -1,37 +0,0 @@
-<#--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-{
- "transactionAmount": {
- "total": "${amount}",
- "currency": "${currency}"
- },
- "requestType": "PaymentCardSaleTransaction",
- "paymentMethod": {
- "paymentCard": {
- "number": "${cardNumber}",
- <#if cardSecurityCode?has_content>
- "securityCode": "${cardSecurityCode}",
- </#if>
- "expiryDate": {
- "month": "${expireMonth}",
- "year": "${expireYear}"
- }
- }
- }
-}
diff --git a/firstdatapaymentgateway/webapp/firstdata/WEB-INF/controller.xml
b/firstdatapaymentgateway/webapp/firstdata/WEB-INF/controller.xml
index 2fe151c4c..7592cdd6b 100644
--- a/firstdatapaymentgateway/webapp/firstdata/WEB-INF/controller.xml
+++ b/firstdatapaymentgateway/webapp/firstdata/WEB-INF/controller.xml
@@ -41,12 +41,24 @@ under the License.
<!-- Request Mappings -->
<request-map uri="main"><security https="true" auth="true"/><response
name="success" type="view" value="main"/></request-map>
+ <request-map uri="createPaymentGatewayFirstData">
+ <security https="true" auth="true"/>
+ <event type="service" invoke="createPaymentGatewayFirstData"/>
+ <response name="success" type="request-redirect" value="main"/>
+ <response name="error" type="view" value="main"/>
+ </request-map>
<request-map uri="updatePaymentGatewayFirstData">
<security https="true" auth="true"/>
<event type="service" invoke="updatePaymentGatewayFirstData"/>
<response name="success" type="request-redirect" value="main"/>
<response name="error" type="view" value="main"/>
</request-map>
+ <request-map uri="deletePaymentGatewayFirstData">
+ <security https="true" auth="true"/>
+ <event type="service" invoke="deletePaymentGatewayFirstData"/>
+ <response name="success" type="request-redirect" value="main"/>
+ <response name="error" type="view" value="main"/>
+ </request-map>
<!-- View Mappings -->
<view-map name="main" type="screen"
page="component://firstdatapaymentgateway/widget/FirstDataScreens.xml#main"/>
diff --git a/firstdatapaymentgateway/widget/FirstDataForms.xml
b/firstdatapaymentgateway/widget/FirstDataForms.xml
index 9219800d4..f0ee60717 100644
--- a/firstdatapaymentgateway/widget/FirstDataForms.xml
+++ b/firstdatapaymentgateway/widget/FirstDataForms.xml
@@ -22,6 +22,7 @@ under the License.
xmlns="http://ofbiz.apache.org/Widget-Form"
xsi:schemaLocation="http://ofbiz.apache.org/Widget-Form
http://ofbiz.apache.org/dtds/widget-form.xsd">
<form name="EditPaymentGatewayFirstData" type="single"
target="updatePaymentGatewayFirstData"
default-entity-name="PaymentGatewayFirstData">
+ <alt-target use-when="paymentGatewayFirstData==null"
target="createPaymentGatewayFirstData"/>
<field name="paymentGatewayConfigId"><hidden
value="${paymentGatewayFirstData.paymentGatewayConfigId}"/></field>
<field name="appName"
title="${uiLabelMap.FormFieldTitle_appName}"><text
default-value="${paymentGatewayFirstData.appName}" size="30"/></field>
<field name="apiKey" title="${uiLabelMap.FormFieldTitle_apiKey}"><text
default-value="${paymentGatewayFirstData.apiKey}" size="30"/></field>
@@ -34,5 +35,6 @@ under the License.
</drop-down>
</field>
<field name="submitButton" use-when="paymentGatewayFirstData!=null"
title="${uiLabelMap.CommonUpdate}"><submit button-type="button"/></field>
+ <field name="submitButton" use-when="paymentGatewayFirstData==null"
title="${uiLabelMap.CommonAdd}"><submit button-type="button"/></field>
</form>
</forms>
\ No newline at end of file
diff --git a/firstdatapaymentgateway/widget/FirstDataMenus.xml
b/firstdatapaymentgateway/widget/FirstDataMenus.xml
index 93614db64..d7aba670d 100644
--- a/firstdatapaymentgateway/widget/FirstDataMenus.xml
+++ b/firstdatapaymentgateway/widget/FirstDataMenus.xml
@@ -20,6 +20,5 @@ under the License.
<menus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://ofbiz.apache.org/Widget-Menu"
xsi:schemaLocation="http://ofbiz.apache.org/Widget-Menu
http://ofbiz.apache.org/dtds/widget-menu.xsd">
<menu name="MainAppBar" title="${uiLabelMap.FirstDataApplication}"
extends="CommonAppBarMenu"
extends-resource="component://common/widget/CommonMenus.xml">
- <menu-item name="main" title="${uiLabelMap.CommonMain}"><link
target="main"/></menu-item>
</menu>
</menus>
\ No newline at end of file