Skip to content
Open
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## Latest Version - V14.4.3 (08/07/2025)
## Latest Version - V14.4.4 (08/19/2025)
### Bug Fixes
- [VAPS] - Improved timeout handling and added additional logging for timeout issue (10350).

## V14.4.3 (08/07/2025)
### Bug Fixes
- [NTS] - Fixed issue 10366 NullPointerException in P66 VISA 2.0 by refactoring conditions for NetFuelAmount and NetNonFuelAmount.

Expand Down
10 changes: 5 additions & 5 deletions examples/iOS-Hybrid-App-Java-Server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
<version>3.5.13</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
Expand All @@ -47,17 +47,17 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.8</version>
<version>6.2.17</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.2.8</version>
<version>6.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>11.0.9</version>
<version>11.0.12</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
Expand All @@ -73,7 +73,7 @@
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>11.0.8</version>
<version>11.0.12</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.heartlandpaymentsystems</groupId>
<artifactId>globalpayments-sdk</artifactId>
<version>14.4.3</version>
<version>14.4.4</version>
<packaging>jar</packaging>
<name>Heartland &amp; Global Payments SDK</name>
<description>API for processing payments through Global Payments</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ public GatewayTimeoutException(Exception innerException) {
public GatewayTimeoutException(String message,String gatewayRspCode, String gatewayRspText, String gatewayTxnId) {
super(message, gatewayRspCode, gatewayRspText, gatewayTxnId);
}
public GatewayTimeoutException(String message) {
super(message);
}
}
121 changes: 80 additions & 41 deletions src/main/java/com/global/api/gateways/NetworkGateway.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
import com.global.api.entities.enums.HostError;
import com.global.api.entities.enums.Target;
import com.global.api.entities.exceptions.GatewayComsException;
import com.global.api.entities.exceptions.GatewayException;
import com.global.api.entities.exceptions.GatewayTimeoutException;
import com.global.api.gateways.events.*;
import com.global.api.serviceConfigs.NetworkGatewayConfig;
import com.global.api.terminals.abstractions.IDeviceMessage;
import com.global.api.utils.NtsUtils;
import com.global.api.utils.StringUtils;
import com.global.api.utils.GnapUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

Expand Down Expand Up @@ -46,6 +49,10 @@ public class NetworkGateway {
@Getter @Setter
private Target target;
private ExecutorService executorService = null;
@Getter @Setter
private int connectionTimeout;
@Getter @Setter
public NetworkGatewayConfig config;

public String getPrimaryEndpoint() {
return primaryEndpoint;
Expand Down Expand Up @@ -85,7 +92,7 @@ public void setEnableLogging(boolean enableLogging) {
if(target.equals(Target.GNAP))
{
GnapUtils.enableLogging(enableLogging);
}else if(target.equals(Target.NTS)){
} else if(target.equals(Target.NTS)){
NtsUtils.enableLogging();
}
}
Expand Down Expand Up @@ -126,6 +133,7 @@ private void connect(String endpoint, Integer port) throws GatewayComsException
try {
// connection started
connectionEvent.setConnectionStarted(connectionStarted);
logResponse(connectionEvent.getEventMessage());

// check for simulated connection error
if(!isForcedError(HostError.Connection)) {
Expand All @@ -137,14 +145,17 @@ private void connect(String endpoint, Integer port) throws GatewayComsException
return 0;
};
Future<Integer> future = executorService.submit(task);
future.get(5000, TimeUnit.MILLISECONDS);
future.get(connectionTimeout, TimeUnit.MILLISECONDS);
client.startHandshake();
raiseGatewayEvent(new SslHandshakeEvent(connectorName, null));
} catch (TimeoutException | InterruptedException e) {
logResponse(new SslHandshakeEvent(connectorName, null).getEventMessage());
}
catch (TimeoutException | InterruptedException e) {
throw new GatewayTimeoutException();
}
catch(Exception exc) {
raiseGatewayEvent(new SslHandshakeEvent(connectorName, exc));
logResponse(new SslHandshakeEvent(connectorName, exc).getEventMessage());
if(client != null && client.isConnected()) {
disconnect();
}
Expand All @@ -154,6 +165,7 @@ private void connect(String endpoint, Integer port) throws GatewayComsException
if(client != null && client.isConnected()) {
// connection completed
raiseGatewayEvent(new ConnectionCompleteEvent(connectorName, connectionStarted, DateTime.now(DateTimeZone.UTC)));
logResponse(new ConnectionCompleteEvent(connectorName, connectionStarted, DateTime.now(DateTimeZone.UTC)).getEventMessage());

out = new DataOutputStream(client.getOutputStream());
in = client.getInputStream();
Expand All @@ -163,6 +175,7 @@ private void connect(String endpoint, Integer port) throws GatewayComsException
else {
// connection fail over
raiseGatewayEvent(new FailOverEvent(connectorName, connectionStarted, DateTime.now(DateTimeZone.UTC)));
logResponse(new FailOverEvent(connectorName, connectionStarted, DateTime.now(DateTimeZone.UTC)).getEventMessage());

if(connectionFaults++ != 3) {
if(endpoint.equals(primaryEndpoint) && secondaryEndpoint != null) {
Expand Down Expand Up @@ -203,7 +216,8 @@ private void disconnect() {
}


public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, GatewayComsException {
@SneakyThrows
public byte[] send(IDeviceMessage message) throws GatewayException {
/*
1) if the initial attempt to connect fails (on both hosts) a GatewayComsException is thrown
2) if the send/receive fails, no exception is thrown (timeout flag is tripped) and fail over occurs
Expand All @@ -220,6 +234,7 @@ public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, Gatew
for (int i = 0; i < 2; i++) {
raiseGatewayEvent(new RequestSentEvent(connectorName));
DateTime requestSent = DateTime.now(DateTimeZone.UTC);
logResponse(new RequestSentEvent(connectorName).getEventMessage());
try {
if (!isForcedError(HostError.SendFailure)) {
out.write(buffer);
Expand All @@ -229,6 +244,7 @@ public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, Gatew

if (rvalue != null && !isForcedError(HostError.Timeout)) {
raiseGatewayEvent(new ResponseReceivedEvent(connectorName, requestSent));
logResponse(new ResponseReceivedEvent(connectorName, requestSent).getEventMessage());
return rvalue;
}
timeout = true;
Expand All @@ -239,13 +255,15 @@ public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, Gatew
// did not get a response, switch endpoints and try again
if (!currentHost.equals(Host.Secondary) && !StringUtils.isNullOrEmpty(secondaryEndpoint) && i < 1) {
raiseGatewayEvent(new TimeoutEvent(connectorName, GatewayEventType.TimeoutFailOver));
logResponse(new TimeoutEvent(connectorName, GatewayEventType.TimeoutFailOver).getEventMessage());

disconnect();
connect(getSecondaryEndpoint(), getSecondaryPort());
}
}

raiseGatewayEvent(new TimeoutEvent(connectorName, GatewayEventType.Timeout));
logResponse(new TimeoutEvent(connectorName, GatewayEventType.Timeout).getEventMessage());
if (timeout) {
throw new GatewayTimeoutException();
} else throw new GatewayComsException();
Expand All @@ -259,6 +277,7 @@ public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, Gatew
shutdownExecutorService();
// remove the force timeout
raiseGatewayEvent(new DisconnectEvent(connectorName));
logResponse(new DisconnectEvent(connectorName).getEventMessage());

// remove simulated errors
if (simulatedHostErrors != null) {
Expand All @@ -267,7 +286,7 @@ public byte[] send(IDeviceMessage message) throws GatewayTimeoutException, Gatew
}
}

private byte[] getGatewayResponse() throws IOException, GatewayTimeoutException {
private byte[] getGatewayResponse() throws IOException, GatewayException {
byte[] buffer = new byte[2048];

int bytesReceived = awaitResponse(in, buffer);
Expand All @@ -280,50 +299,66 @@ private byte[] getGatewayResponse() throws IOException, GatewayTimeoutException

return null;
}

private int awaitResponse(InputStream in, byte[] buffer) throws GatewayTimeoutException {
private int awaitResponse(InputStream in, byte[] buffer) throws GatewayException {
Callable<Integer> task = () -> {
long t = System.currentTimeMillis();

int position = 0;
Integer messageLength = null;
do {
if(messageLength == null) {
byte[] lengthBuffer = new byte[2];
int length = in.read(lengthBuffer, 0, 2);
if(length == 2) {
if(target!=null && target.equals(Target.GNAP)) {
messageLength = new BigInteger(lengthBuffer).intValue();
}else{
messageLength = new BigInteger(lengthBuffer).intValue() - 2;
long startTime = System.currentTimeMillis();
int position = 0;
Integer messageLength = null;
do {
try {
if (messageLength == null) {
byte[] lengthBuffer = new byte[2];
int length = in.read(lengthBuffer, 0, 2);
if (length == 2) {
messageLength = target != null && target.equals(Target.GNAP)
? new BigInteger(lengthBuffer).intValue()
: new BigInteger(lengthBuffer).intValue() - 2;
}
else {
throw new IOException("Failed to read message length.");
}
}

if (messageLength != null) {
int currLength = in.read(buffer, position, messageLength - position);
if (currLength == -1) {
throw new IOException("End of stream reached unexpectedly.");
}
position += currLength;

if (position == messageLength) {
return messageLength;
}
}
}
}

if(messageLength != null) {
int currLength = in.read(buffer, position, messageLength);
if (currLength == messageLength) {
return messageLength;
Thread.sleep(50);
}
else {
position += currLength;
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new GatewayException("Thread interrupted while waiting for response.");
}
catch (IOException e) {
throw new GatewayException("Error while reading response: " + e.getMessage());
}
}

try {
Thread.sleep(50);
}
catch(InterruptedException e) { break; }
}
while((System.currentTimeMillis() - t) <= 20000);
} while ((System.currentTimeMillis() - startTime) <= timeout);

throw new GatewayTimeoutException();
throw new GatewayTimeoutException("Response timed out after " + timeout + " milliseconds.");
};

Future<Integer> future = executorService.submit(task);
try {
return future.get( timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
throw new GatewayTimeoutException();
return future.get(timeout, TimeUnit.MILLISECONDS);
}
catch (TimeoutException e) {
throw new GatewayTimeoutException("Response timed out after " + timeout + " milliseconds.");
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new GatewayException("Thread interrupted while waiting for response.");
}
catch (ExecutionException e) {
throw new GatewayException("Error while executing response task: " + e.getCause().getMessage());
}
}

Expand All @@ -333,7 +368,8 @@ public void shutdownExecutorService(){
if(!executorService.awaitTermination(1,TimeUnit.SECONDS)){
executorService.shutdownNow();
}
} catch (InterruptedException e) {
}
catch (InterruptedException e) {
executorService.shutdownNow();
}
}
Expand All @@ -343,9 +379,12 @@ private void raiseGatewayEvent(final IGatewayEvent event) {
new Thread(new Runnable() {
public void run() {
gatewayEventHandler.eventRaised(event);
NtsUtils.log(event.getEventMessage());
}
}).start();
}
}

protected void logResponse(String response) throws IOException {
config.getRequestLogger().ResponseReceived(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public abstract class Configuration {
protected int timeout = 30000;
protected boolean validated;
protected HashMap<String, String> dynamicHeaders;
protected int connectionTimeout = 5000;


public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.global.api.network.enums.*;
import com.global.api.entities.exceptions.ConfigurationException;
import com.global.api.gateways.VapsConnector;
import com.global.api.utils.NtsUtils;
import com.global.api.utils.StringUtils;
import lombok.Setter;

Expand Down Expand Up @@ -192,6 +193,7 @@ public void configureContainer(ConfiguredServices services) {
gateway.setSecondaryPort(secondaryPort);
gateway.setTimeout(timeout);
gateway.setTarget(target);
gateway.setConnectionTimeout(connectionTimeout);
gateway.setEnableLogging(enableLogging);
gateway.setSimulatedHostErrors(simulatedHostErrors);

Expand Down Expand Up @@ -222,6 +224,11 @@ public void configureContainer(ConfiguredServices services) {
// event handler
gateway.setGatewayEventHandler(gatewayEventHandler);

if (requestLogger == null) {
requestLogger = new NtsUtils();
}
gateway.setConfig(this);

services.setGatewayConnector(gateway);
}
}
Expand Down
Loading