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}"); + } + + } + + } }