diff --git a/GlobalPayments.Api.sln.GhostDoc.xml b/GlobalPayments.Api.sln.GhostDoc.xml
new file mode 100644
index 00000000..badf7a93
--- /dev/null
+++ b/GlobalPayments.Api.sln.GhostDoc.xml
@@ -0,0 +1,60 @@
+
+
+ *.min.js
+ jquery*.js
+
+
+
+
+
+
+
+
+
+
+
+ .\Help
+ true
+ BlueBridge
+ MemberName
+
+ FlatGray
+
+ true
+ false
+ false
+ false
+ false
+
+
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+
+
+
+
+
+ true
+ false
+ false
+
+ true
+
+
+
+
+
+
+
+ BlueBridge.CardPresent
+
+
+
+
+
+
diff --git a/dir.txt b/dir.txt
new file mode 100644
index 00000000..604a7b7f
--- /dev/null
+++ b/dir.txt
@@ -0,0 +1,20 @@
+ Volume in drive D is D Drive
+ Volume Serial Number is C6B3-DE8E
+
+ Directory of D:\Source\Repositories\HSS\BlueBear\BlueBridge.CardPresent\dotnet-sdk
+
+11/10/2022 01:53 PM
.
+11/10/2022 01:53 PM ..
+11/10/2022 11:01 AM 2,289 .gitignore
+11/10/2022 11:01 AM 438 .travis.yml
+11/10/2022 11:01 AM 12,930 CHANGELOG.md
+11/10/2022 01:54 PM 0 dir.txt
+11/10/2022 11:01 AM 109,984 Doxyfile
+11/10/2022 11:01 AM examples
+11/10/2022 11:01 AM 2,166 GlobalPayments.Api.sln
+11/10/2022 11:01 AM 15,439 LICENSE.md
+11/10/2022 11:01 AM 5,601 README.md
+11/10/2022 11:01 AM src
+11/10/2022 11:01 AM tests
+ 8 File(s) 148,847 bytes
+ 5 Dir(s) 122,987,163,648 bytes free
diff --git a/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs b/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs
index 7a2f764b..380eeb77 100644
--- a/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs
+++ b/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs
@@ -860,7 +860,7 @@ public AuthorizationBuilder WithMaskedDataResponse(bool value) {
}
public AuthorizationBuilder WithBlockedCardType(BlockedCardType cardTypesBlocking) {
- var hasNulls = cardTypesBlocking.GetType().GetTypeInfo().DeclaredFields.All(p => p.GetValue(cardTypesBlocking) == null);
+ var hasNulls = cardTypesBlocking.GetType().GetProperties().All(p => p.GetValue(cardTypesBlocking) == null);
if (hasNulls) {
throw new BuilderException("No properties set on the object");
}
diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj
new file mode 100644
index 00000000..44475492
--- /dev/null
+++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj
@@ -0,0 +1,798 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {547DFAEC-5056-4322-A771-4C631C9BBABC}
+ Library
+ Properties
+ GlobalPayments.Api
+ GlobalPayments.Api
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ ..\..\..\Certificates\BlueBridge.snk
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ 7.3
+ prompt
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ 7.3
+ prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.0.14
+
+
+ 13.0.1
+
+
+
+
+ BlueBridge.snk
+
+
+
+
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Properties/AssemblyInfo.cs b/src/GlobalPayments.Api/Properties/AssemblyInfo.cs
index b916759f..b1191dd5 100644
--- a/src/GlobalPayments.Api/Properties/AssemblyInfo.cs
+++ b/src/GlobalPayments.Api/Properties/AssemblyInfo.cs
@@ -10,7 +10,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GlobalPayments.Api")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/GlobalPayments.Api/Services/HostedService.cs b/src/GlobalPayments.Api/Services/HostedService.cs
index 1115c5ac..f48da452 100644
--- a/src/GlobalPayments.Api/Services/HostedService.cs
+++ b/src/GlobalPayments.Api/Services/HostedService.cs
@@ -103,7 +103,7 @@ private void MapTransactionStatusResponse(ref JsonDoc response)
.Set("PAYMENT_PURPOSE", response.GetValue("paymentpurpose"))
.Set("RESULT", response.GetValue("result"))
.Set($"{_config.ShaHashType}HASH", response.GetValue($"{_config.ShaHashType.ToString().ToLower()}hash"));
-
+
}
}
}
diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs
index b0f56639..82442818 100644
--- a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs
+++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs
@@ -81,6 +81,12 @@ public TerminalAuthBuilder WithEcrId(int ecrId) {
return this;
}
+ public TerminalAuthBuilder WithEcrId(string ecrId)
+ {
+ EcrId = int.Parse(ecrId);
+ return this;
+ }
+
public TerminalAuthBuilder WithAllowDuplicates(bool allowDuplicates) {
AllowDuplicates = allowDuplicates;
return this;
diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs
index cfd40789..6f36332b 100644
--- a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs
+++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs
@@ -41,6 +41,13 @@ public TerminalManageBuilder WithEcrId(int ecrId) {
EcrId = ecrId;
return this;
}
+
+ public TerminalManageBuilder WithEcrId(string ecrId)
+ {
+ EcrId = int.Parse(ecrId);
+ return this;
+ }
+
public TerminalManageBuilder WithOrigECRRefNumber(string origECRRefNumber)
{
OrigECRRefNumber = origECRRefNumber;
diff --git a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs
index 088f81ee..b2930057 100644
--- a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs
+++ b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs
@@ -113,9 +113,14 @@ public virtual IDeviceResponse StartCard(PaymentMethodType paymentMethodType) {
public virtual IDeviceResponse SendSaf() {
throw new UnsupportedTransactionException("This function is not supported by the currently configured device.");
}
- public virtual IDeviceResponse StartCardTransaction(UpaParam param, ProcessingIndicator indicator, UpaTransactionData transData) {
- throw new UnsupportedTransactionException("This function is not supported by the currently configured device.");
+
+ public virtual IDeviceResponse StartCardTransaction(UpaParam param, ProcessingIndicator indicator,
+ UpaTransactionData transData)
+ {
+ throw new UnsupportedTransactionException(
+ "This function is not supported by the currently configured device.");
}
+
public virtual IDeviceResponse DeleteResource(string fileName) {
throw new UnsupportedTransactionException("This function is not supported by the currently configured device.");
}
diff --git a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs
index 085b68d1..c813d8ba 100644
--- a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs
+++ b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs
@@ -2,6 +2,7 @@
using GlobalPayments.Api.Terminals.HPA;
using GlobalPayments.Api.Terminals.UPA;
using GlobalPayments.Api.Utils;
+using GlobalPayments.Api.Entities.UPA;
using System;
using System.Collections.Generic;
using System.Text;
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/MessageEventArgs.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/MessageEventArgs.cs
new file mode 100644
index 00000000..781e37dc
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/MessageEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class MessageEventArgs : EventArgs
+ {
+ public MessageEventArgs(string message, Exception exception = null)
+ {
+ Message = message;
+ Exception = exception;
+ }
+
+ public string Message { get; }
+
+ public Exception Exception { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/StreamBuffer.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/StreamBuffer.cs
new file mode 100644
index 00000000..88d0c054
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/StreamBuffer.cs
@@ -0,0 +1,386 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class StreamBuffer
+ {
+ #region Private data
+
+ private const int DefaultCapacity = 1024;
+
+ private readonly object _syncObj = new object();
+
+ private byte[] _buffer = new byte[DefaultCapacity];
+
+ private int _head;
+
+ private int _tail = -1;
+
+ private bool _isEmpty = true;
+
+ private TaskCompletionSource _dequeueManualTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private TaskCompletionSource _availableTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ #endregion Private data
+
+ #region Constructors
+
+ public StreamBuffer()
+ {
+ }
+
+ public StreamBuffer(byte[] bytes)
+ {
+ Enqueue(bytes);
+ }
+
+ public StreamBuffer(int capacity)
+ {
+ AutoTrimMinCapacity = capacity;
+ SetCapacity(capacity);
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public int Count
+ {
+ get
+ {
+ lock (_syncObj)
+ {
+ if (_isEmpty)
+ {
+ return 0;
+ }
+ if (_tail >= _head)
+ {
+ return _tail - _head + 1;
+ }
+ return Capacity - _head + _tail + 1;
+ }
+ }
+ }
+
+ public byte[] Buffer
+ {
+ get
+ {
+ lock (_syncObj)
+ {
+ var bytes = new byte[Count];
+ if (!_isEmpty)
+ {
+ if (_tail >= _head)
+ {
+ Array.Copy(_buffer, _head, bytes, 0, _tail - _head + 1);
+ }
+ else
+ {
+ Array.Copy(_buffer, _head, bytes, 0, Capacity - _head);
+ Array.Copy(_buffer, 0, bytes, Capacity - _head, _tail + 1);
+ }
+ }
+ return bytes;
+ }
+ }
+ }
+
+ public int Capacity => _buffer.Length;
+
+ public bool AutoTrim { get; set; } = true;
+
+ public int AutoTrimMinCapacity { get; set; } = DefaultCapacity;
+
+ #endregion Properties
+
+ #region Public methods
+
+ public void Clear()
+ {
+ lock (_syncObj)
+ {
+ _head = 0;
+ _tail = -1;
+ _isEmpty = true;
+ Reset(ref _availableTcs);
+ }
+ }
+
+ public void SetCapacity(int capacity)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException(nameof(capacity), "The capacity must not be negative.");
+
+ lock (_syncObj)
+ {
+ var count = Count;
+ if (capacity < count)
+ throw new ArgumentOutOfRangeException(nameof(capacity), "The capacity is too small to hold the current buffer content.");
+
+ if (capacity == _buffer.Length)
+ return;
+
+ var newBuffer = new byte[capacity];
+ Array.Copy(Buffer, newBuffer, count);
+ _buffer = newBuffer;
+ _head = 0;
+ _tail = count - 1;
+ }
+ }
+
+ public void TrimExcess()
+ {
+ lock (_syncObj)
+ {
+ if (Count < Capacity * 0.9)
+ {
+ SetCapacity(Count);
+ }
+ }
+ }
+
+ public void Enqueue(byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ Enqueue(bytes, 0, bytes.Length);
+ }
+
+ public void Enqueue(ArraySegment segment)
+ {
+ Enqueue(segment.Array, segment.Offset, segment.Count);
+ }
+
+ public void Enqueue(byte[] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ if (offset + count > bytes.Length)
+ throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (count == 0)
+ return; // Nothing to do
+
+ lock (_syncObj)
+ {
+ if (Count + count > Capacity)
+ {
+ SetCapacity(Math.Max(Capacity * 2, Count + count));
+ }
+
+ int tailCount;
+ int wrapCount;
+ if (_tail >= _head || _isEmpty)
+ {
+ tailCount = Math.Min(Capacity - 1 - _tail, count);
+ wrapCount = count - tailCount;
+ }
+ else
+ {
+ tailCount = Math.Min(_head - 1 - _tail, count);
+ wrapCount = 0;
+ }
+
+ if (tailCount > 0)
+ {
+ Array.Copy(bytes, offset, _buffer, _tail + 1, tailCount);
+ }
+ if (wrapCount > 0)
+ {
+ Array.Copy(bytes, offset + tailCount, _buffer, 0, wrapCount);
+ }
+ _tail = (_tail + count) % Capacity;
+ _isEmpty = false;
+ Set(_dequeueManualTcs);
+ Set(_availableTcs);
+ }
+ }
+
+ public byte[] Dequeue(int maxCount)
+ {
+ return DequeueInternal(maxCount, peek: false);
+ }
+
+ public int Dequeue(byte[] buffer, int offset, int maxCount)
+ {
+ return DequeueInternal(buffer, offset, maxCount, peek: false);
+ }
+
+ public byte[] Peek(int maxCount)
+ {
+ return DequeueInternal(maxCount, peek: true);
+ }
+
+ public async Task DequeueAsync(int count, CancellationToken cancellationToken = default)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), "The count must not be negative.");
+
+ while (true)
+ {
+ TaskCompletionSource myDequeueManualTcs;
+ lock (_syncObj)
+ {
+ if (count <= Count)
+ {
+ return Dequeue(count);
+ }
+ myDequeueManualTcs = Reset(ref _dequeueManualTcs);
+ }
+ await AwaitAsync(myDequeueManualTcs, cancellationToken);
+ }
+ }
+
+ public async Task DequeueAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), "The count must not be negative.");
+ if (buffer.Length < offset + count)
+ throw new ArgumentException("The buffer is too small for the requested data.", nameof(buffer));
+
+ while (true)
+ {
+ TaskCompletionSource myDequeueManualTcs;
+ lock (_syncObj)
+ {
+ if (count <= Count)
+ {
+ Dequeue(buffer, offset, count);
+ }
+ myDequeueManualTcs = Reset(ref _dequeueManualTcs);
+ }
+ await AwaitAsync(myDequeueManualTcs, cancellationToken);
+ }
+ }
+
+ public async Task WaitAsync(CancellationToken cancellationToken = default)
+ {
+ TaskCompletionSource myAvailableTcs;
+ lock (_syncObj)
+ {
+ if (Count > 0)
+ {
+ return;
+ }
+ myAvailableTcs = Reset(ref _availableTcs);
+ }
+ await AwaitAsync(myAvailableTcs, cancellationToken);
+ }
+
+ #endregion Public methods
+
+ #region Private methods
+
+ private byte[] DequeueInternal(int count, bool peek)
+ {
+ if (count > Count)
+ count = Count;
+ var bytes = new byte[count];
+ DequeueInternal(bytes, 0, count, peek);
+ return bytes;
+ }
+
+ private int DequeueInternal(byte[] bytes, int offset, int count, bool peek)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), "The count must not be negative.");
+ if (count == 0)
+ return count; // Easy
+ if (bytes.Length < offset + count)
+ throw new ArgumentException("The buffer is too small for the requested data.", nameof(bytes));
+
+ lock (_syncObj)
+ {
+ if (count > Count)
+ count = Count;
+
+ if (_tail >= _head)
+ {
+ Array.Copy(_buffer, _head, bytes, offset, count);
+ }
+ else
+ {
+ if (count <= Capacity - _head)
+ {
+ Array.Copy(_buffer, _head, bytes, offset, count);
+ }
+ else
+ {
+ var headCount = Capacity - _head;
+ Array.Copy(_buffer, _head, bytes, offset, headCount);
+ var wrapCount = count - headCount;
+ Array.Copy(_buffer, 0, bytes, offset + headCount, wrapCount);
+ }
+ }
+
+ if (peek)
+ return count;
+
+ if (count == Count)
+ {
+ _isEmpty = true;
+ _head = 0;
+ _tail = -1;
+ Reset(ref _availableTcs);
+ }
+ else
+ {
+ _head = (_head + count) % Capacity;
+ }
+
+ if (!AutoTrim || Capacity <= AutoTrimMinCapacity || Count > Capacity / 2)
+ return count;
+
+ var newCapacity = Count;
+ if (newCapacity < AutoTrimMinCapacity)
+ {
+ newCapacity = AutoTrimMinCapacity;
+ }
+ if (newCapacity < Capacity)
+ {
+ SetCapacity(newCapacity);
+ }
+ return count;
+ }
+ }
+
+ // Must be called within the lock
+ private static TaskCompletionSource Reset(ref TaskCompletionSource tcs)
+ {
+ if (tcs.Task.IsCompleted)
+ {
+ tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ }
+ return tcs;
+ }
+
+ // Must be called within the lock
+ private static void Set(TaskCompletionSource tcs)
+ {
+ tcs.TrySetResult(true);
+ }
+
+ // Must NOT be called within the lock
+ private static async Task AwaitAsync(TaskCompletionSource tcs, CancellationToken cancellationToken)
+ {
+ if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
+ {
+ await tcs.Task; // Already completed
+ return;
+ }
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ #endregion Private methods
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/TcpClientAsync.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/TcpClientAsync.cs
new file mode 100644
index 00000000..ecf95b4d
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/TcpClientAsync.cs
@@ -0,0 +1,240 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class TcpClientAsync : IDisposable
+ {
+ #region Private data
+
+ private TcpClient _tcpClient;
+ private NetworkStream _stream;
+ private TaskCompletionSource _closedTcs = new TaskCompletionSource();
+
+ #endregion Private data
+
+ #region Constructors
+
+ public TcpClientAsync()
+ {
+ _closedTcs.SetResult(true);
+ }
+
+ #endregion Constructors
+
+ #region Events
+
+ public event EventHandler Message;
+
+ #endregion Events
+
+ #region Properties
+
+ public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromSeconds(5);
+
+ public TimeSpan MaxConnectTimeout { get; set; } = TimeSpan.FromMinutes(1);
+
+ public bool AutoReconnect { get; set; }
+
+ public string HostName { get; set; }
+
+ public IPAddress IpAddress { get; set; }
+
+ public int Port { get; set; }
+
+ public bool IsConnected => _tcpClient.Client.Connected;
+
+ public StreamBuffer StreamBuffer { get; private set; } = new StreamBuffer();
+
+ public Task ClosedTask => _closedTcs.Task;
+
+ public bool IsClosing => ClosedTask.IsCompleted;
+
+ public Func ConnectedCallback { get; set; }
+
+ public Action ClosedCallback { get; set; }
+
+ public Func ReceivedCallback { get; set; }
+
+ #endregion Properties
+
+ #region Public methods
+
+ public async Task RunAsync()
+ {
+ var isReconnected = false;
+ var reconnectTry = -1;
+ do
+ {
+ reconnectTry++;
+ StreamBuffer = new StreamBuffer();
+
+ // Try to connect to remote host
+ var connectTimeout = TimeSpan.FromTicks(ConnectTimeout.Ticks +
+ (MaxConnectTimeout.Ticks - ConnectTimeout.Ticks) / 20 *
+ Math.Min(reconnectTry, 20));
+ _tcpClient = new TcpClient(AddressFamily.InterNetworkV6)
+ {
+ SendTimeout = 2000,
+ ReceiveTimeout = 2000,
+ LingerState = new LingerOption(true, 0)
+ };
+ _tcpClient.Client.DualMode = true;
+ Message?.Invoke(this, new MessageEventArgs("Connecting to server"));
+ var connectTask = !string.IsNullOrWhiteSpace(HostName) ? _tcpClient.ConnectAsync(HostName, Port) : _tcpClient.ConnectAsync(IpAddress, Port);
+
+ var timeoutTask = Task.Delay(connectTimeout);
+ if (await Task.WhenAny(connectTask, timeoutTask) == timeoutTask)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection timeout"));
+ continue;
+ }
+
+ try
+ {
+ await connectTask;
+ }
+ catch (Exception ex)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Error connecting to remote host", ex));
+ await timeoutTask;
+ continue;
+ }
+
+ reconnectTry = -1;
+ _stream = _tcpClient.GetStream();
+ _stream.ReadTimeout = 1000 * 3;
+
+ // Read until the connection is closed.
+ var networkReadTask = Task.Run(async () =>
+ {
+ var buffer = new byte[10240];
+ while (true)
+ {
+ int readLength;
+ try
+ {
+ var readTask = _stream.ReadAsync(buffer, 0, buffer.Length);
+
+ readLength = await readTask;
+ }
+ catch (IOException ex) when ((ex.InnerException as SocketException)?.ErrorCode == (int)SocketError.OperationAborted)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection closed locally", ex));
+ readLength = -1;
+ }
+ catch (IOException ex) when ((ex.InnerException as SocketException)?.ErrorCode == (int)SocketError.ConnectionAborted)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection aborted", ex));
+ readLength = -1;
+ }
+ catch (IOException ex) when ((ex.InnerException as SocketException)?.ErrorCode == (int)SocketError.ConnectionReset)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection reset remotely", ex));
+ readLength = -2;
+ }
+ catch (ObjectDisposedException)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection closed by client"));
+ readLength = -4;
+ }
+ catch (Exception ex)
+ {
+ Message?.Invoke(this, new MessageEventArgs(ex.Message, ex));
+ readLength = -2;
+ }
+
+ if (readLength <= 0)
+ {
+ if (readLength == 0)
+ {
+ Message?.Invoke(this, new MessageEventArgs("Connection closed remotely"));
+ }
+
+ _closedTcs.TrySetResult(true);
+ OnClosed(readLength != -1);
+ return;
+ }
+
+ var segment = new ArraySegment(buffer, 0, readLength);
+ StreamBuffer.Enqueue(segment);
+ await OnReceivedAsync(readLength);
+ }
+ });
+
+ _closedTcs = new TaskCompletionSource();
+ await OnConnectedAsync(isReconnected);
+
+ // Wait for closed connection
+ await networkReadTask;
+ _tcpClient.Close();
+
+ isReconnected = true;
+ } while (AutoReconnect);
+ }
+
+ public void Disconnect()
+ {
+ _tcpClient.Client.Shutdown(SocketShutdown.Send);
+ var read = 0;
+ try
+ {
+ var buffer = new byte[10240];
+ while ((read = _tcpClient.Client.Receive(buffer)) > 0)
+ {
+
+ }
+ }
+ catch
+ {
+ Message?.Invoke(this, new MessageEventArgs($"Connection Disconnected {read}"));
+ }
+
+ _tcpClient?.Client?.Close();
+ _stream.Close(0);
+ _stream.Dispose();
+ }
+
+ public void Dispose()
+ {
+ AutoReconnect = false;
+ _tcpClient?.Client?.Dispose();
+ _tcpClient?.Dispose();
+ _stream = null;
+ _tcpClient = null;
+ }
+
+ public async Task Send(ArraySegment data, CancellationToken cancellationToken = default)
+ {
+ if (!_tcpClient.Client.Connected)
+ throw new InvalidOperationException("Not connected.");
+
+ await _stream.WriteAsync(data.Array, data.Offset, data.Count, cancellationToken);
+ }
+
+ #endregion Public methods
+
+ #region Protected virtual methods
+
+ protected virtual Task OnConnectedAsync(bool isReconnected)
+ {
+ return ConnectedCallback != null ? ConnectedCallback(this, isReconnected) : Task.CompletedTask;
+ }
+
+ protected virtual void OnClosed(bool remote)
+ {
+ ClosedCallback?.Invoke(this, remote);
+ }
+
+ protected virtual Task OnReceivedAsync(int count)
+ {
+ return ReceivedCallback != null ? ReceivedCallback(this, count) : Task.CompletedTask;
+ }
+
+ #endregion Protected virtual methods
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs
index fdff6089..d238d7dc 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs
@@ -1,70 +1,84 @@
-using GlobalPayments.Api.Entities;
-using GlobalPayments.Api.Terminals.Abstractions;
+using GlobalPayments.Api.Terminals.Abstractions;
using GlobalPayments.Api.Terminals.Messaging;
using GlobalPayments.Api.Utils;
using System;
-using System.Net.Sockets;
-
-namespace GlobalPayments.Api.Terminals.UPA {
- internal class UpaTcpInterface : IDeviceCommInterface {
- TcpClient _client;
- NetworkStream _stream;
- ITerminalConfiguration _settings;
- int _connectionCount = 0;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using log4net;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ internal class UpaTcpInterface : IDeviceCommInterface
+ {
+
+ private readonly ITerminalConfiguration _settings;
+ private readonly ILog _logger = LogManager.GetLogger(typeof(UpaTcpInterface));
+ private readonly CancellationTokenSource _tokenSource;
+
public event MessageSentEventHandler OnMessageSent;
public event MessageReceivedEventHandler OnMessageReceived;
- public UpaTcpInterface(ITerminalConfiguration settings) {
+ public UpaTcpInterface(ITerminalConfiguration settings)
+ {
_settings = settings;
+ _tokenSource = new CancellationTokenSource();
}
- public void Connect() {
- int connectionTimestamp = Int32.Parse(DateTime.Now.ToString("mmssfff"));
+ public void Connect()
+ {
- if (_client == null) {
- _client = new TcpClient();
- _client.ConnectAsync(_settings.IpAddress, int.Parse(_settings.Port)).Wait(_settings.Timeout);
-
- if (Int32.Parse(DateTime.Now.ToString("mmssfff")) > connectionTimestamp + _settings.Timeout) {
- throw new MessageException("Connection not established within the specified timeout.");
- }
+ }
- _stream = _client.GetStream();
- _stream.ReadTimeout = _settings.Timeout;
- }
- _connectionCount++;
+ public void Disconnect()
+ {
+ _tokenSource.Cancel();
}
- public void Disconnect() {
- _connectionCount--;
- if (_connectionCount == 0) {
- _stream?.Dispose();
- _stream = null;
+ public byte[] Send(IDeviceMessage deviceMessage)
+ {
+ var token = _tokenSource.Token;
- _client?.Dispose();
- _client = null;
- }
- }
+ var requestId = deviceMessage.GetRequestBuilder().GetValue("data")?.GetValue("requestId");
+
+ var tcs = new TaskCompletionSource();
- public byte[] Send(IDeviceMessage message) {
- byte[] buffer = message.GetSendBuffer();
+ var serverIsBusy = false;
- var readyReceived = false;
byte[] responseMessage = null;
- Connect();
- try {
- var task = _stream.WriteAsync(buffer, 0, buffer.Length);
+ var buffer = deviceMessage.GetSendBuffer();
- if (!task.Wait(_settings.Timeout)) {
- throw new MessageException("Terminal did not respond in the given timeout.");
- }
+ var client = new TcpClientAsync
+ {
+ IpAddress = IPAddress.Parse(_settings.IpAddress),
+ Port = int.Parse(_settings.Port),
+ AutoReconnect = false,
+ ConnectedCallback = async (c, isReconnected) =>
+ {
+ await c.Send(new ArraySegment(buffer, 0, buffer.Length), token);
- do {
- var rvalue = _stream.GetTerminalResponseAsync();
- if (rvalue != null) {
- var msgValue = GetResponseMessageType(rvalue);
+ OnMessageSent?.Invoke(deviceMessage.ToString());
+ while (true)
+ {
+ await c.StreamBuffer.WaitAsync(token);
+ if (c.IsClosing)
+ {
+ break;
+ }
+ }
+ },
+ ReceivedCallback = async (c, count) =>
+ {
+ var bytes = await c.StreamBuffer.DequeueAsync(count, token);
+
+ var rvalue = bytes.GetTerminalResponseAsync();
+ if (rvalue != null)
+ {
+ var msgValue = GetResponseMessageType(rvalue);
+ OnMessageSent?.Invoke($"Server Response: {msgValue}");
switch (msgValue)
{
case UpaMessageType.Ack:
@@ -72,78 +86,121 @@ public byte[] Send(IDeviceMessage message) {
case UpaMessageType.Nak:
break;
case UpaMessageType.Ready:
- readyReceived = true;
+ if (serverIsBusy)
+ {
+ await c.Send(new ArraySegment(buffer, 0, buffer.Length), token);
+ OnMessageSent?.Invoke("Resending Request...");
+ serverIsBusy = false;
+ }
break;
case UpaMessageType.Busy:
+ serverIsBusy = true;
break;
case UpaMessageType.TimeOut:
break;
case UpaMessageType.Msg:
responseMessage = TrimResponse(rvalue);
- if (IsNonReadyResponse(responseMessage)) {
- readyReceived = true; // since reboot doesn't return READY
- }
- SendAckMessageToDevice();
+ OnMessageSent?.Invoke($"Sending {UpaMessageType.Ack}...");
+ await SendAckMessageToDevice(c);
+ break;
+ case UpaMessageType.Error:
+ var errorJson = $@"{{""id"": ""{requestId}"",""status"": ""Error"",""action"": {{""result_code"": ""Unexpected error from terminal.""}}}}";
+ responseMessage = Encoding.UTF8.GetBytes(errorJson);
+ OnMessageSent?.Invoke($"Sending {UpaMessageType.Ack}...");
+ await SendAckMessageToDevice(c);
break;
default:
throw new Exception("Message field value is unknown in API response.");
}
}
- else
+
+ if (responseMessage != null)
{
- // Reset the connection before the next attempt
- Disconnect();
- Connect();
+ OnMessageReceived?.Invoke(Encoding.UTF8.GetString(responseMessage));
+ c.Disconnect();
}
- } while (!readyReceived);
+ },
+ ClosedCallback = (c, r) => { tcs.SetResult(true); }
+ };
- return responseMessage;
- }
- catch (Exception exc) {
- throw new MessageException(exc.Message, exc);
- }
- finally {
- OnMessageSent?.Invoke(message.ToString());
- Disconnect();
+ client.Message += ClientOnMessage();
+
+ var t = Task.WhenAny(client.RunAsync(), tcs.Task);
+
+ t.Wait(token);
+
+ client.Message -= ClientOnMessage();
+
+ client.Dispose();
+
+ return responseMessage;
+
+ EventHandler ClientOnMessage()
+ {
+ return (s, a) =>
+ {
+ if (a.Exception == null)
+ {
+ _logger.Debug($"Tcp Client: {a.Message}");
+ }
+ else
+ {
+ _logger.Error($"Tcp Client: {a.Message}", a.Exception);
+ }
+ };
}
}
- private byte[] TrimResponse(byte[] value) {
- return System.Text.Encoding.UTF8.GetBytes(
- System.Text.Encoding.UTF8.GetString(value)
- .TrimStart((char)ControlCodes.STX, (char)ControlCodes.LF)
- .TrimEnd((char)ControlCodes.LF, (char)ControlCodes.ETX)
- );
+ private static byte[] TrimResponse(byte[] value)
+ {
+ var json = Encoding.UTF8.GetString(value)
+ .TrimStart((char)ControlCodes.STX, (char)ControlCodes.LF)
+ .TrimEnd((char)ControlCodes.LF, (char)ControlCodes.ETX);
+
+ return Encoding.UTF8.GetBytes(json);
}
- private string GetResponseMessageType(byte[] response) {
- var jsonObject = System.Text.Encoding.UTF8.GetString(TrimResponse(response));
- var jsonDoc = JsonDoc.Parse(jsonObject);
- return jsonDoc.GetValue("message");
+ private string GetResponseMessageType(byte[] response)
+ {
+ var jsonObject = Encoding.UTF8.GetString(TrimResponse(response));
+ try
+ {
+ var jsonDoc = JsonDoc.Parse(jsonObject);
+ return jsonDoc.GetValue("message");
+ }
+ catch (Exception ex)
+ {
+ _logger.Error($"{ex.Message} : Response - {jsonObject}");
+ return UpaMessageType.Error;
+ }
}
- private void SendAckMessageToDevice() {
+ private static Task SendAckMessageToDevice(TcpClientAsync c)
+ {
var doc = new JsonDoc();
doc.Set("message", UpaMessageType.Ack);
var message = TerminalUtilities.BuildUpaRequest(doc.ToString());
var ackBuffer = message.GetSendBuffer();
- _stream.Write(ackBuffer, 0, ackBuffer.Length);
+ return c.Send(new ArraySegment(ackBuffer, 0, ackBuffer.Length));
}
- private bool IsNonReadyResponse(byte[] responseMessage) {
- var responseMessageString = System.Text.Encoding.UTF8.GetString(responseMessage);
+ private static bool IsNonReadyResponse(byte[] responseMessage)
+ {
+ var responseMessageString = Encoding.UTF8.GetString(responseMessage);
var response = JsonDoc.Parse(responseMessageString);
var data = response.Get("data");
- if (data == null) {
+ if (data == null)
+ {
return false;
}
+
var cmdResult = data.Get("cmdResult");
return (
- data.GetValue("response") == UpaTransType.Reboot ||
- (cmdResult != null && cmdResult.GetValue("result") == "Failed")
- );
+ data.GetValue("response") == UpaTransType.Reboot ||
+ (cmdResult != null && cmdResult.GetValue("result") == "Failed")
+ );
}
}
}
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BasicResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BasicResponse.cs
new file mode 100644
index 00000000..9cce88cc
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BasicResponse.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class BasicResponse
+ {
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("data")]
+ public Data Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs
index 1a3e62d7..825176ce 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs
@@ -5,7 +5,8 @@
namespace GlobalPayments.Api.Terminals.UPA
{
- public class BatchReportResponse : ITerminalReport {
+ public class BatchReportResponse : ITerminalReport
+ {
const string INVALID_RESPONSE_FORMAT = "The response received is not in the proper format.";
public BatchReportResponse(JsonDoc root) {
@@ -19,6 +20,9 @@ public BatchReportResponse(JsonDoc root) {
throw new MessageException(INVALID_RESPONSE_FORMAT);
}
+ EcrId = firstDataNode.GetValue("EcrId");
+ RequestId = firstDataNode.GetValue("requestId");
+
Status = cmdResult.GetValue("result");
if (string.IsNullOrEmpty(Status)) {
var errorCode = cmdResult.GetValue("errorCode");
@@ -49,7 +53,7 @@ public BatchReportResponse(JsonDoc root) {
OpenTnxId = batchRecord.GetValue("openTnxId"),
TotalAmount = batchRecord.GetValue("totalAmount"),
TotalCnt = batchRecord.GetValue("totalCnt"),
- CreditCnt = batchRecord.GetValue("credictCnt"),
+ CreditCnt = batchRecord.GetValue("creditCnt"),
CreditAmt = batchRecord.GetValue("creditAmt"),
DebitCnt = batchRecord.GetValue("debitCnt"),
DebitAmt = batchRecord.GetValue("debitAmt"),
@@ -96,7 +100,7 @@ public BatchReportResponse(JsonDoc root) {
public string Message { get; set; }
public string Response { get; set; }
- public int EcrId { get; set; }
+ public string EcrId { get; set; }
public int RequestId { get; set; }
public string Result { get; set; }
public string ErrorCode { get; set; }
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/CmdResult.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/CmdResult.cs
new file mode 100644
index 00000000..622ab925
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/CmdResult.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class CmdResult
+ {
+ [JsonProperty("result")]
+ public string Result { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/Data.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/Data.cs
new file mode 100644
index 00000000..d95ce495
--- /dev/null
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/Data.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+
+namespace GlobalPayments.Api.Terminals.UPA
+{
+ public class Data
+ {
+ [JsonProperty("cmdResult")]
+ public CmdResult CmdResult { get; set; }
+
+ [JsonProperty("response")]
+ public string Response { get; set; }
+
+ [JsonProperty("EcrId")]
+ public string EcrId { get; set; }
+
+ [JsonProperty("requestId")]
+ public string RequestId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs
index 7264279c..4eb467c6 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs
@@ -4,7 +4,7 @@
namespace GlobalPayments.Api.Terminals.UPA
{
- internal class TransactionResponse: ITerminalResponse {
+ public class TransactionResponse: ITerminalResponse {
#region Properties
public decimal? AvailableBalance { get; set; }
public string TransactionId { get; set; }
@@ -55,6 +55,10 @@ internal class TransactionResponse: ITerminalResponse {
public string ReferenceNumber { get; set; }
public string CardHolderName { get; set; }
public string RequestId { get; set; }
+ public string EcrId { get; set; }
+ public string GatewayResponseCode { get; set; }
+ public string GatewayResponseText { get; set; }
+ public bool PartialApproval { get; set; }
#endregion
@@ -66,11 +70,15 @@ public TransactionResponse(JsonDoc root) {
}
RequestId = response.GetValue("requestId");
+ EcrId = response.GetValue("EcrId");
+ TransactionType = response.GetValue("response");
+
HydrateCmdResult(response);
var responseData = response.Get("data");
if (responseData == null) {
return;
}
+
HydrateHostData(responseData);
HydratePaymentData(responseData);
HydrateTransactionData(responseData);
@@ -79,11 +87,9 @@ public TransactionResponse(JsonDoc root) {
HydrateHeaderData(responseData);
}
else {
- RequestId = root.GetValue("id");
- TransactionId = RequestId;
- DeviceResponseText = root.GetValue("status");
- ResponseText = root.Get("action").GetValue("result_code");
- DeviceResponseCode = ResponseText;
+ TransactionId = RequestId = root.GetValue("id");
+ Status = ResponseCode = DeviceResponseCode = root.GetValue("status");
+ DeviceResponseText = ResponseText = root.Get("action").GetValue("result_code");
}
}
@@ -109,8 +115,8 @@ protected void HydrateHostData(JsonDoc data) {
TransactionId = host.GetValue("responseId");
TerminalRefNumber = host.GetValue("tranNo");
// TransactionDate = host.GetValue("respDateTime");
- // GatewayResponseCode = host.GetValue("gatewayResponseCode");
- // GatewayResponsemessage = host.GetValue("gatewayResponsemessage");
+ GatewayResponseCode = host.GetValue("gatewayResponseCode");
+ GatewayResponseText = host.GetValue("gatewayResponseMessage");
ResponseCode = NormalizeResponseCode(host.GetValue("responseCode"), host.GetValue("partialApproval"));
ResponseText = host.GetValue("responseText");
ApprovalCode = host.GetValue("approvalCode");
@@ -136,9 +142,13 @@ protected void HydrateHostData(JsonDoc data) {
// RecurringDataCode = host.GetValue("recurringDataCode");
// CavvResultCode = host.GetValue("cavvResultCode");
// TokenPANLast = host.GetValue("tokenPANLast");
- // PartialApproval = host.GetValue("partialApproval");
+
+ var partialAmount = host.GetValue("partialApproval");
+
+ PartialApproval = !string.IsNullOrEmpty(partialAmount) && !"0".Equals(partialAmount);
+
// TraceNumber = host.GetValue("traceNumber");
- // BalanceDue = host.GetValue("balanceDue");
+ AmountDue = BalanceAmount = host.GetValue("balanceDue");
// BaseDue = host.GetValue("baseDue");
// TaxDue = host.GetValue("taxDue");
// TipDue = host.GetValue("tipDue");
diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs
index 5048a329..3c688bee 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs
@@ -18,6 +18,9 @@ public UpaEODResponse(JsonDoc root) {
throw new MessageException(INVALID_RESPONSE_FORMAT);
}
+ EcrId = firstDataNode.GetValue("EcrId");
+ RequestId = firstDataNode.GetValue("requestId");
+
Status = cmdResult.GetValue("result");
// Log error info if it's there
@@ -42,7 +45,8 @@ public UpaEODResponse(JsonDoc root) {
}
}
else {
- RequestId = root.GetValue("id");
+ RequestId = root.GetValue("id");
+
DeviceResponseText = root.GetValue("status");
DeviceResponseCode = root.Get("action").GetValue("result_code"); ;
}
@@ -52,7 +56,8 @@ private bool isGpApiResponse(JsonDoc root) {
return !root.Has("data");
}
- public string RequestId { get; set; }
+ public int RequestId { get; set; }
+
public string Multiplemessage { get; set; }
public IDeviceResponse AttachmentResponse { get; set; }
@@ -74,6 +79,8 @@ private bool isGpApiResponse(JsonDoc root) {
public IBatchReportResponse BatchReportResponse { get; set; }
public string RespDateTime { get; set; }
public int BatchId { get; set; }
+ public string EcrId { get; set; }
+
public int GatewayResponseCode { get; set; }
public string GatewayResponseMessage { get; set; }
diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs
index aa46f594..b6e2d5d5 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs
@@ -10,6 +10,7 @@
namespace GlobalPayments.Api.Terminals.UPA {
public class UpaController : DeviceController {
+
internal override IDeviceInterface ConfigureInterface() {
if (_interface == null) {
_interface = new UpaInterface(this);
@@ -47,6 +48,7 @@ internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) {
var response = Send(BuildReportTransaction(builder));
string jsonObject = Encoding.UTF8.GetString(response);
+
var jsonParse = JsonDoc.Parse(jsonObject);
switch (builder.ReportType) {
@@ -255,7 +257,8 @@ internal TransactionResponse DoTransaction(IDeviceMessage request) {
return null;
}
- string jsonObject = Encoding.UTF8.GetString(response);
+ var jsonObject = Encoding.UTF8.GetString(response);
+
var jsonParse = JsonDoc.Parse(jsonObject);
return new TransactionResponse(jsonParse);
diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs
index de2e5c8d..d3541c7a 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs
@@ -6,6 +6,7 @@
using GlobalPayments.Api.Utils;
using Newtonsoft.Json;
using System.Text;
+using Newtonsoft.Json;
using System.Text.RegularExpressions;
namespace GlobalPayments.Api.Terminals.UPA
@@ -38,7 +39,7 @@ public override IEODResponse EndOfDay()
{
var requestId = _controller.GetRequestId();
var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.EodProcessing));
- string jsonObject = Encoding.UTF8.GetString(response);
+ var jsonObject = Encoding.UTF8.GetString(response);
var jsonParse = JsonDoc.Parse(jsonObject);
return new UpaEODResponse(jsonParse);
}
@@ -47,16 +48,26 @@ public override IDeviceResponse Reboot()
{
var requestId = _controller.GetRequestId();
var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.Reboot));
- string jsonObject = Encoding.UTF8.GetString(response);
- var jsonParse = JsonDoc.Parse(jsonObject);
- return new TransactionResponse(jsonParse);
+ var json = Encoding.UTF8.GetString(response);
+
+ var j = JsonConvert.DeserializeObject(json);
+
+ return new DeviceResponse
+ {
+ Command = j.Data.Response,
+ DeviceId = "",
+ DeviceResponseCode = "",
+ DeviceResponseText = "",
+ Status = j.Data.CmdResult.Result,
+ ReferenceNumber = j.Data.RequestId
+ };
}
public override IDeviceResponse LineItem(string leftText, string rightText = null, string runningLeftText = null, string runningRightText = null)
{
var requestId = _controller.GetRequestId();
var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.LineItemDisplay, leftText, rightText));
- string jsonObject = Encoding.UTF8.GetString(response);
+ var jsonObject = Encoding.UTF8.GetString(response);
var jsonParse = JsonDoc.Parse(jsonObject);
return new TransactionResponse(jsonParse);
}
@@ -64,15 +75,16 @@ public override IDeviceResponse LineItem(string leftText, string rightText = nul
public override void Cancel()
{
var requestId = _controller.GetRequestId();
- var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.CancelTransaction));
+ _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.CancelTransaction));
}
public override ISAFResponse SendStoreAndForward()
{
var requestId = _controller.GetRequestId();
var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.SendSAF));
- string jsonObject = Encoding.UTF8.GetString(response);
- JsonDoc doc = JsonDoc.Parse(jsonObject);
+ var jsonObject = Encoding.UTF8.GetString(response);
+
+ var doc = JsonDoc.Parse(jsonObject);
return new UpaSAFResponse(doc);
}
public override ISAFResponse DeleteSaf(string safReferenceNumber, string tranNo=null)
@@ -224,6 +236,7 @@ public override TerminalReportBuilder GetOpenTabDetails()
{
return new TerminalReportBuilder(TerminalReportType.GetOpenTabDetails);
}
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaMessageType.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaMessageType.cs
index dfadea18..10757878 100644
--- a/src/GlobalPayments.Api/Terminals/UPA/UpaMessageType.cs
+++ b/src/GlobalPayments.Api/Terminals/UPA/UpaMessageType.cs
@@ -8,5 +8,6 @@ internal class UpaMessageType
public const string Busy = "BUSY";
public const string TimeOut = "TO";
public const string Msg = "MSG";
+ public const string Error = "ERROR";
}
}
diff --git a/src/GlobalPayments.Api/Utils/Extensions.cs b/src/GlobalPayments.Api/Utils/Extensions.cs
index 20910ea8..1b64facd 100644
--- a/src/GlobalPayments.Api/Utils/Extensions.cs
+++ b/src/GlobalPayments.Api/Utils/Extensions.cs
@@ -240,5 +240,103 @@ public static string TrimEnd(this string str, string trimString) {
public static string ExtractDigits(this string str) {
return string.IsNullOrEmpty(str) ? str : new string(str.Where(char.IsDigit).ToArray());
}
+
+
+
+ public static byte[] GetTerminalResponse(this byte[] buffer)
+ {
+
+ var bytesReceived = buffer.Length;
+
+ if (bytesReceived <= 0)
+ return null;
+
+ var readBuffer = new byte[bytesReceived];
+ Array.Copy(buffer, readBuffer, bytesReceived);
+
+ var code = (ControlCodes)readBuffer[0];
+ switch (code)
+ {
+ case ControlCodes.NAK:
+ return null;
+ case ControlCodes.EOT:
+ throw new MessageException("Terminal returned EOT for the current message.");
+ case ControlCodes.ACK:
+ return buffer.GetTerminalResponse();
+ case ControlCodes.STX:
+ {
+ var queue = new Queue(readBuffer);
+
+ // break off only one message
+ var rec_buffer = new List();
+ do
+ {
+ rec_buffer.Add(queue.Dequeue());
+ if (rec_buffer[rec_buffer.Count - 1] == (byte)ControlCodes.ETX)
+ break;
+ }
+ while (queue.Count > 0);
+
+ // Should be the LRC
+ if (queue.Count > 0)
+ {
+ rec_buffer.Add(queue.Dequeue());
+ }
+ return rec_buffer.ToArray();
+ }
+ default:
+ throw new MessageException($"Unknown message received: {code}");
+ }
+ }
+
+ public static byte[] GetTerminalResponseAsync(this byte[] buffer)
+ {
+
+ var bytesReceived = buffer.Length;
+
+ if (bytesReceived <= 0)
+ return null;
+
+ var readBuffer = new byte[bytesReceived];
+ Array.Copy(buffer, readBuffer, bytesReceived);
+
+ var code = (ControlCodes)readBuffer[0];
+
+ switch (code)
+ {
+ case ControlCodes.NAK:
+ return null;
+ case ControlCodes.EOT:
+ throw new MessageException("Terminal returned EOT for the current message.");
+ case ControlCodes.ACK:
+ return readBuffer.GetTerminalResponse();
+ case ControlCodes.STX:
+ {
+ var queue = new Queue(readBuffer);
+
+ // break off only one message
+ var rec_buffer = new List();
+ do
+ {
+ rec_buffer.Add(queue.Dequeue());
+ if (rec_buffer[rec_buffer.Count - 1] == (byte)ControlCodes.ETX)
+ break;
+ }
+ while (queue.Count > 0);
+
+ // Should be the LRC
+ if (queue.Count > 0)
+ {
+ rec_buffer.Add(queue.Dequeue());
+ }
+ return rec_buffer.ToArray();
+ }
+ default:
+ throw new MessageException($"Unknown message received: {code}");
+ }
+
+ }
+
+
}
}