From 2a94f796117518d3b4a2d4f89153f72f1db28d07 Mon Sep 17 00:00:00 2001 From: globalpaymentsinc <52252853+globalpaymentsinc@users.noreply.github.com> Date: Thu, 10 Nov 2022 12:33:21 -0700 Subject: [PATCH 01/19] Add files via upload --- .../GlobalPayments.Api.4.6.csproj | 642 ++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj 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..83797323 --- /dev/null +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -0,0 +1,642 @@ + + + + + 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 + + + BlueBridge.pfx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13.0.1 + + + + + + + \ No newline at end of file From 604278998382314b61894455fcdb693231fbffa5 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Thu, 10 Nov 2022 13:54:53 -0700 Subject: [PATCH 02/19] Updates --- dir.txt | 20 +++++++++++++++++++ .../GlobalPayments.Api.4.6.csproj | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 dir.txt 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/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index 83797323..bf273e5c 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -639,4 +639,4 @@ - \ No newline at end of file + From 1e44d0e9a7d14f7efa3d1d751c55e62b4a2a8b5e Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Thu, 10 Nov 2022 14:40:05 -0700 Subject: [PATCH 03/19] Updated path to pfx file --- src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index bf273e5c..4fecb0a7 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -34,7 +34,7 @@ true - BlueBridge.pfx + ..\..\..\Certificates\BlueBridge.pfx @@ -636,7 +636,9 @@ - + + BlueBridge.pfx + - + \ No newline at end of file From 5fef4498c32e86d1674bbf80fc434f127a0a6c6a Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Thu, 10 Nov 2022 14:49:28 -0700 Subject: [PATCH 04/19] Changed certificate --- src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index 4fecb0a7..dc3e52d7 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -34,7 +34,7 @@ true - ..\..\..\Certificates\BlueBridge.pfx + ..\..\..\Certificates\BlueBridge.snk @@ -636,8 +636,8 @@ - - BlueBridge.pfx + + BlueBridge.snk From 34019e811de28af240fcc1ae504cfafb5994876f Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Thu, 12 Jan 2023 14:52:01 -0700 Subject: [PATCH 05/19] Get AmountDue property --- .../Terminals/UPA/Responses/TransactionResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index d478a272..ce5dcd73 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs @@ -67,7 +67,7 @@ protected void HydrateHostData(JsonDoc data) { // TokenPANLast = host.GetValue("tokenPANLast"); // PartialApproval = host.GetValue("partialApproval"); // 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"); From 9d26be15444d5e0deb7ed88082522a8e168139b8 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 17 Jan 2023 12:30:57 -0700 Subject: [PATCH 06/19] Added RequestId to the response model --- .../Terminals/Abstractions/IDeviceResponse.cs | 1 + .../Terminals/PAX/Responses/DeviceResponse.cs | 3 +++ .../Terminals/UPA/Responses/TransactionResponse.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs index dee676d3..a5d0fda4 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs @@ -49,6 +49,7 @@ public interface ITerminalResponse : IDeviceResponse { string CardHolderVerificationMethod { get; set; } string TerminalVerificationResults { get; set; } decimal? MerchantFee { get; set; } + string RequestId { get; } } public interface ITerminalReport : IDeviceResponse { } diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs index 8d3da3ff..2f012f09 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs @@ -246,6 +246,9 @@ public class PaxTerminalResponse : PaxBaseResponse, ITerminalResponse { public string TerminalVerificationResults { get; set; } public decimal? MerchantFee { get; set; } + + public string RequestId { get; } + public int TranNo { get; set; } internal PaxTerminalResponse(byte[] buffer, params string[] messageIds) : base(buffer, messageIds) { } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index ce5dcd73..8bfaa888 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 { + internal class TransactionResponse : ITerminalResponse { public TransactionResponse(JsonDoc root) { var response = root.Get("data"); if (response == null) { From 5a5555444e0298230eea22a9da44a9422fcc7716 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 30 May 2023 09:42:06 -0700 Subject: [PATCH 07/19] Updated project file --- .../GlobalPayments.Api.4.6.csproj | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index dc3e52d7..f689293f 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -49,17 +49,19 @@ - + + + @@ -78,13 +80,16 @@ + + + @@ -102,15 +107,19 @@ + + + + @@ -128,6 +137,9 @@ + + + @@ -135,10 +147,13 @@ + + + @@ -147,12 +162,14 @@ + + @@ -167,21 +184,28 @@ + + + + + + + @@ -194,6 +218,7 @@ + @@ -208,8 +233,11 @@ + + + @@ -220,6 +248,7 @@ + @@ -228,10 +257,13 @@ + + + @@ -243,8 +275,26 @@ + + + + + + + + + + + + + + + + + + @@ -307,6 +357,7 @@ + @@ -324,11 +375,13 @@ + + @@ -436,8 +489,10 @@ + + @@ -458,6 +513,7 @@ + @@ -481,6 +537,7 @@ + @@ -497,6 +554,7 @@ + From 4e0e85571125f9ffeecec9187a51b700d792dd26 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 30 May 2023 12:01:39 -0700 Subject: [PATCH 08/19] Added log4net and mapped missing transaction properties --- .../GlobalPayments.Api.4.6.csproj | 3 +++ .../UPA/Responses/BatchReportResponse.cs | 3 +++ .../Terminals/UPA/UpaController.cs | 11 ++++++++++ .../Terminals/UPA/UpaInterface.cs | 20 +++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index f689293f..a25f33bc 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -689,6 +689,9 @@ + + 2.0.14 + 13.0.1 diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs index 1a3e62d7..4d1cc7a4 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs @@ -19,6 +19,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"); diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs index cf8336c1..c7eb7d07 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs @@ -6,11 +6,14 @@ using System; using System.Text; using System.Text.RegularExpressions; +using log4net; namespace GlobalPayments.Api.Terminals.UPA { public class UpaController : DeviceController { + private readonly ILog _logger = LogManager.GetLogger(typeof(UpaController)); + internal override IDeviceInterface ConfigureInterface() { if (_interface == null) @@ -53,6 +56,10 @@ internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) var response = Send(BuildReportTransaction(builder)); string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + var jsonParse = JsonDoc.Parse(jsonObject); switch (builder.ReportType) @@ -266,6 +273,10 @@ internal TransactionResponse DoTransaction(IDeviceMessage request) { } string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + 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 d9446c40..5776b22e 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs @@ -3,9 +3,13 @@ using GlobalPayments.Api.Terminals.Builders; using GlobalPayments.Api.Utils; using System.Text; +using log4net; namespace GlobalPayments.Api.Terminals.UPA { public class UpaInterface : DeviceInterface, IDeviceInterface { + + private readonly ILog _logger = LogManager.GetLogger(typeof(UpaInterface)); + internal UpaInterface(UpaController controller) : base(controller) { } public override TerminalAuthBuilder TipAdjust(decimal? amount) { @@ -31,6 +35,10 @@ public override IEODResponse EndOfDay() { var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.EodProcessing)); string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + var jsonParse = JsonDoc.Parse(jsonObject); return new UpaEODResponse(jsonParse); } @@ -39,6 +47,10 @@ public override IDeviceResponse Reboot() { var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.Reboot)); string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + var jsonParse = JsonDoc.Parse(jsonObject); return new TransactionResponse(jsonParse); } @@ -47,6 +59,10 @@ public override IDeviceResponse LineItem(string leftText, string rightText = nul var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.LineItemDisplay, leftText, rightText)); string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + var jsonParse = JsonDoc.Parse(jsonObject); return new TransactionResponse(jsonParse); } @@ -61,6 +77,10 @@ public override ISAFResponse SendStoreAndForward() var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.SendSAF)); string jsonObject = Encoding.UTF8.GetString(response); + + if(_logger.IsDebugEnabled) + _logger.Debug($"Raw Response: {jsonObject}"); + JsonDoc doc = JsonDoc.Parse(jsonObject); return new UpaSAFResponse(doc); } From 4234c3ed84c933ade014e2bc98139f83b8aab36c Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Wed, 31 May 2023 12:21:14 -0700 Subject: [PATCH 09/19] Made EcrId a string consistently --- .../Terminals/Builders/TerminalAuthBuilder.cs | 2 +- src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs | 2 +- .../Terminals/Builders/TerminalManageBuilder.cs | 2 +- .../Terminals/UPA/Responses/BatchReportResponse.cs | 4 ++-- .../Terminals/UPA/Responses/TransactionResponse.cs | 4 +++- .../Terminals/UPA/Responses/UpaEODResponse.cs | 5 +++++ src/GlobalPayments.Api/Terminals/UPA/UpaController.cs | 4 ++-- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs index 30b80ac9..e5dac7ca 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs @@ -66,7 +66,7 @@ public TerminalAuthBuilder WithAddress(Address address) { return this; } - public TerminalAuthBuilder WithEcrId(int ecrId) { + public TerminalAuthBuilder WithEcrId(string ecrId) { EcrId = ecrId; return this; } diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs index 499cdce8..01825fb8 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs @@ -6,7 +6,7 @@ namespace GlobalPayments.Api.Terminals.Builders { public abstract class TerminalBuilder : TransactionBuilder where T : TerminalBuilder { internal PaymentMethodType PaymentMethodType { get; set; } internal int ReferenceNumber { get; set; } - internal int EcrId { get; set; } + internal string EcrId { get; set; } public T WithPaymentMethodType(PaymentMethodType value) { PaymentMethodType = value; diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs index df937bf0..34d1683b 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs @@ -27,7 +27,7 @@ public TerminalManageBuilder WithTerminalRefNumber(string terminalRefNumber) { return this; } - public TerminalManageBuilder WithEcrId(int ecrId) { + public TerminalManageBuilder WithEcrId(string ecrId) { EcrId = ecrId; return this; } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs index 4d1cc7a4..a8fc1b51 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs @@ -19,7 +19,7 @@ public BatchReportResponse(JsonDoc root) { throw new MessageException(INVALID_RESPONSE_FORMAT); } - EcrId = firstDataNode.GetValue("EcrId"); + EcrId = firstDataNode.GetValue("EcrId"); RequestId = firstDataNode.GetValue("requestId"); Status = cmdResult.GetValue("result"); @@ -99,7 +99,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/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index 8bfaa888..432d3e87 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 { public TransactionResponse(JsonDoc root) { var response = root.Get("data"); if (response == null) { @@ -12,6 +12,7 @@ public TransactionResponse(JsonDoc root) { } RequestId = response.GetValue("requestId"); + EcrId = response.GetValue("EcrId"); HydrateCmdResult(response); var responseData = response.Get("data"); if (responseData == null) { @@ -213,5 +214,6 @@ private string ConvertHEX(string hexString) { public string ReferenceNumber { get; set; } public string CardHolderName { get; set; } public string RequestId { get; set; } + public string EcrId { get; set; } } } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs index f7938569..33186310 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs @@ -19,6 +19,9 @@ public UpaEODResponse(JsonDoc root) { Status = cmdResult.GetValue("result"); + EcrId = firstDataNode.GetValue("EcrId"); + RequestId = firstDataNode.GetValue("requestId"); + // Log error info if it's there var errorCode = cmdResult.GetValue("errorCode"); var errorMsg = cmdResult.GetValue("errorMessage"); @@ -62,6 +65,8 @@ public UpaEODResponse(JsonDoc root) { public IBatchReportResponse BatchReportResponse { get; set; } public string RespDateTime { get; set; } public int BatchId { get; set; } + public string EcrId { get; set; } + public int RequestId { 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 c7eb7d07..b261c029 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs @@ -111,7 +111,7 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) var baseRequest = doc.SubElement("data"); baseRequest.Set("command", MapTransactionType(transType, transModifier, builder.RequestMultiUseToken, builder.Gratuity)); - baseRequest.Set("EcrId", builder.EcrId.ToString()); + baseRequest.Set("EcrId", builder.EcrId); baseRequest.Set("requestId", requestId.ToString()); if (transType != TransactionType.Balance) { @@ -226,7 +226,7 @@ internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { var baseRequest = doc.SubElement("data"); // Possibly update the requestToken parameter in the future if necessary baseRequest.Set("command", MapTransactionType(transType, transModifier, false, builder.Gratuity)); - baseRequest.Set("EcrId", builder.EcrId.ToString()); + baseRequest.Set("EcrId", builder.EcrId); baseRequest.Set("requestId", requestId.ToString()); var txnData = baseRequest.SubElement("data"); From 44f1a3c7c6fe53a7571454810feb848b0ad08de9 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Mon, 5 Jun 2023 15:31:46 -0700 Subject: [PATCH 10/19] Added missing properties --- GlobalPayments.Api.sln.GhostDoc.xml | 60 +++++++++++++++++++ .../UPA/Responses/BatchReportResponse.cs | 10 ++-- .../UPA/Responses/TransactionResponse.cs | 33 ++++++++-- .../Terminals/UPA/Responses/UpaEODResponse.cs | 5 +- 4 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 GlobalPayments.Api.sln.GhostDoc.xml 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/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs index a8fc1b51..6508179c 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs @@ -24,9 +24,8 @@ public BatchReportResponse(JsonDoc root) { Status = cmdResult.GetValue("result"); if (string.IsNullOrEmpty(Status)) { - var errorCode = cmdResult.GetValue("errorCode"); - var errorMsg = cmdResult.GetValue("errorMessage"); - DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + DeviceResponseCode = cmdResult.GetValue("errorCode"); + DeviceResponseText = cmdResult.GetValue("errorMessage"); } else { // If the Status is not "Success", there is either nothing to process, or something else went wrong. @@ -83,9 +82,8 @@ public BatchReportResponse(JsonDoc root) { } } else { // the only other option is "Failed" - var errorCode = cmdResult.GetValue("errorCode"); - var errorMsg = cmdResult.GetValue("errorMessage"); - DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + DeviceResponseCode = cmdResult.GetValue("errorCode"); + DeviceResponseText = cmdResult.GetValue("errorMessage"); } } } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index 432d3e87..4be96e7c 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs @@ -13,6 +13,8 @@ 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) { @@ -41,16 +43,33 @@ 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"); ReferenceNumber = host.GetValue("referenceNumber"); - AvsResponseCode = host.GetValue("avsResultCode"); - CvvResponseCode = host.GetValue("cvvResultCode"); - AvsResponseText = host.GetValue("avsResultText"); - CvvResponseText = host.GetValue("cvvResultText"); + + AvsResponseCode = host.GetValue("AvsResultCode"); + + if (string.IsNullOrEmpty(AvsResponseCode)) + AvsResponseCode = "0"; + + CvvResponseCode = host.GetValue("CvvResultCode"); + + if (string.IsNullOrEmpty(CvvResponseCode)) + CvvResponseCode = "0"; + + AvsResponseText = host.GetValue("AvsResultText"); + + if (string.IsNullOrEmpty(AvsResponseText)) + AvsResponseText = "AVS Not Requested."; + + CvvResponseText = host.GetValue("CvvResultText"); + + if (string.IsNullOrEmpty(CvvResponseText)) + CvvResponseText = "CVV Not Requested."; + // AdditionalTipAmount = host.GetValue("additionalTipAmount"); // BaseAmount = host.GetValue("baseAmount"); TipAmount = host.GetValue("tipAmount"); @@ -215,5 +234,7 @@ private string ConvertHEX(string hexString) { 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; } } } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs index 33186310..43468276 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs @@ -23,9 +23,8 @@ public UpaEODResponse(JsonDoc root) { RequestId = firstDataNode.GetValue("requestId"); // Log error info if it's there - var errorCode = cmdResult.GetValue("errorCode"); - var errorMsg = cmdResult.GetValue("errorMessage"); - DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + DeviceResponseCode = cmdResult.GetValue("errorCode"); + DeviceResponseText = cmdResult.GetValue("errorMessage"); // Unlike in other response types, this data should always be here, even if the Status is "Failed" var secondDataNode = firstDataNode.Get("data"); From b4c4a8391b96ec0e8e06a475604c8155620c3f74 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Tue, 6 Jun 2023 11:57:48 -0700 Subject: [PATCH 11/19] Better error handling --- src/GlobalPayments.Api/Entities/Exceptions.cs | 16 +++++++++ .../UPA/Interfaces/UpaTcpInterface.cs | 35 ++++++++++++++----- .../UPA/Responses/BatchReportResponse.cs | 3 +- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/GlobalPayments.Api/Entities/Exceptions.cs b/src/GlobalPayments.Api/Entities/Exceptions.cs index c2662cf4..b14dba53 100644 --- a/src/GlobalPayments.Api/Entities/Exceptions.cs +++ b/src/GlobalPayments.Api/Entities/Exceptions.cs @@ -12,6 +12,22 @@ public class ApiException : Exception { public ApiException(string message = null, Exception innerException = null) : base(message, innerException) { } } + public class DeviceException : Exception + { + /// The exception message + /// The device state + public DeviceException(string message, string state) : base(message) + { + State = state; + } + + /// + /// Gets the state. + /// + /// The state. + public string State { get; } + } + /// /// A builder error occurred. Check the method calls against the builder. /// diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index 36bbe7ab..15902b1c 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -4,6 +4,8 @@ using GlobalPayments.Api.Utils; using System; using System.Net.Sockets; +using log4net; +using Newtonsoft.Json; namespace GlobalPayments.Api.Terminals.UPA { internal class UpaTcpInterface : IDeviceCommInterface { @@ -11,6 +13,9 @@ internal class UpaTcpInterface : IDeviceCommInterface { NetworkStream _stream; ITerminalConfiguration _settings; int _connectionCount = 0; + + private readonly ILog _logger = LogManager.GetLogger(typeof(UpaTcpInterface)); + public event MessageSentEventHandler OnMessageSent; public UpaTcpInterface(ITerminalConfiguration settings) { @@ -47,22 +52,26 @@ public void Disconnect() { public byte[] Send(IDeviceMessage message) { byte[] buffer = message.GetSendBuffer(); - + var msgValue = string.Empty; var readyReceived = false; byte[] responseMessage = null; Connect(); - try { + try + { var task = _stream.WriteAsync(buffer, 0, buffer.Length); - if (!task.Wait(_settings.Timeout)) { + if (!task.Wait(_settings.Timeout)) + { throw new MessageException("Terminal did not respond in the given timeout."); } - do { + do + { var rvalue = _stream.GetTerminalResponseAsync(); - if (rvalue != null) { - var msgValue = GetResponseMessageType(rvalue); + if (rvalue != null) + { + msgValue = GetResponseMessageType(rvalue); switch (msgValue) { @@ -74,14 +83,18 @@ public byte[] Send(IDeviceMessage message) { readyReceived = true; break; case UpaMessageType.Busy: + Disconnect(); + Connect(); break; case UpaMessageType.TimeOut: break; case UpaMessageType.Msg: responseMessage = TrimResponse(rvalue); - if (IsNonReadyResponse(responseMessage)) { + if (IsNonReadyResponse(responseMessage)) + { readyReceived = true; // since reboot doesn't return READY } + SendAckMessageToDevice(); break; default: @@ -96,10 +109,16 @@ public byte[] Send(IDeviceMessage message) { } } while (!readyReceived); + if (responseMessage == null) + { + throw new DeviceException("The device did not return the expected response.", msgValue); + } + return responseMessage; } catch (Exception exc) { - throw new MessageException(exc.Message, exc); + _logger.Error(exc); + throw; } finally { OnMessageSent?.Invoke(message.ToString()); diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs index 6508179c..0d15be9d 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) { From 0536dc84cf483b8847d623549c0d6c05ae589705 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Tue, 6 Jun 2023 14:58:08 -0700 Subject: [PATCH 12/19] Added PartialApproval flag --- .../Terminals/UPA/Responses/TransactionResponse.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index 4be96e7c..aefa55bb 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs @@ -85,7 +85,11 @@ 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"); AmountDue = BalanceAmount = host.GetValue("balanceDue"); // BaseDue = host.GetValue("baseDue"); @@ -236,5 +240,6 @@ private string ConvertHEX(string hexString) { public string EcrId { get; set; } public string GatewayResponseCode { get; set; } public string GatewayResponseText { get; set; } + public bool PartialApproval { get; set; } } } From e739d2f4ef563547a46549dbde51e951f2a930bb Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Wed, 7 Jun 2023 18:55:02 -0700 Subject: [PATCH 13/19] Fixed spelling error on creditCnt --- .../Terminals/UPA/Responses/BatchReportResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs index 0d15be9d..3abb7855 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs @@ -52,7 +52,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"), From 9cd2efd7052f789a5123dc5a21961f55e187ad95 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Tue, 29 Aug 2023 06:39:52 -0700 Subject: [PATCH 14/19] Added exception handling for bad json response handling --- .../Terminals/UPA/Interfaces/UpaTcpInterface.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index 15902b1c..44b77eba 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -134,10 +134,19 @@ private byte[] TrimResponse(byte[] value) { ); } - private string GetResponseMessageType(byte[] response) { + private string GetResponseMessageType(byte[] response) + { var jsonObject = System.Text.Encoding.UTF8.GetString(TrimResponse(response)); - var jsonDoc = JsonDoc.Parse(jsonObject); - return jsonDoc.GetValue("message"); + try + { + var jsonDoc = JsonDoc.Parse(jsonObject); + return jsonDoc.GetValue("message"); + } + catch (Exception ex) + { + _logger.Error($"{ex.Message} : Response - {jsonObject}"); + throw; + } } private void SendAckMessageToDevice() { From 0ee55e76a4d82738f71eecbf3a7ed97cdf74fea1 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Thu, 14 Dec 2023 09:20:52 -0700 Subject: [PATCH 15/19] Added new UpaTcpInterface --- .../GlobalPayments.Api.4.6.csproj | 3 + .../UPA/Interfaces/MessageEventArgs.cs | 17 + .../Terminals/UPA/Interfaces/StreamBuffer.cs | 386 ++++++++++++++++++ .../UPA/Interfaces/TcpClientAsync.cs | 240 +++++++++++ .../UPA/Interfaces/UpaTcpInterface.cs | 192 +++++---- .../Terminals/UPA/UpaController.cs | 2 +- src/GlobalPayments.Api/Utils/Extensions.cs | 98 +++++ 7 files changed, 855 insertions(+), 83 deletions(-) create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Interfaces/MessageEventArgs.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Interfaces/StreamBuffer.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Interfaces/TcpClientAsync.cs diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index 73409536..120695d9 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -655,6 +655,9 @@ + + + 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 44b77eba..22ea5005 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -1,77 +1,82 @@ -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; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using log4net; -using Newtonsoft.Json; -namespace GlobalPayments.Api.Terminals.UPA { - internal class UpaTcpInterface : IDeviceCommInterface { - TcpClient _client; - NetworkStream _stream; - ITerminalConfiguration _settings; - int _connectionCount = 0; +namespace GlobalPayments.Api.Terminals.UPA +{ + internal class UpaTcpInterface : IDeviceCommInterface + { + + private readonly ITerminalConfiguration _settings; private readonly ILog _logger = LogManager.GetLogger(typeof(UpaTcpInterface)); public event MessageSentEventHandler OnMessageSent; - public UpaTcpInterface(ITerminalConfiguration settings) { + public UpaTcpInterface(ITerminalConfiguration settings) + { _settings = settings; } - 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."); - } + public void Disconnect() + { - _stream = _client.GetStream(); - _stream.ReadTimeout = _settings.Timeout; - } - _connectionCount++; } - public void Disconnect() { - _connectionCount--; - if (_connectionCount == 0) { - _stream?.Dispose(); - _stream = null; + public byte[] Send(IDeviceMessage deviceMessage) + { + var tokenSource = new CancellationTokenSource(); + var token = tokenSource.Token; - _client?.Dispose(); - _client = null; - } - } + var tcs = new TaskCompletionSource(); + + var serverIsBusy = false; - public byte[] Send(IDeviceMessage message) { - byte[] buffer = message.GetSendBuffer(); - var msgValue = string.Empty; - 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)) + var readyReceived = false; + + var client = new TcpClientAsync + { + IpAddress = IPAddress.Parse(_settings.IpAddress), + Port = int.Parse(_settings.Port), + AutoReconnect = false, + ConnectedCallback = async (c, isReconnected) => { - throw new MessageException("Terminal did not respond in the given timeout."); - } + await c.Send(new ArraySegment(buffer, 0, buffer.Length), token); + + OnMessageSent?.Invoke(deviceMessage.ToString()); - do + while (true) + { + await c.StreamBuffer.WaitAsync(token); + if (c.IsClosing) + { + break; + } + } + }, + ReceivedCallback = async (c, count) => { - var rvalue = _stream.GetTerminalResponseAsync(); + var bytes = await c.StreamBuffer.DequeueAsync(count, token); + + var rvalue = bytes.GetTerminalResponseAsync(); if (rvalue != null) { - msgValue = GetResponseMessageType(rvalue); + var msgValue = GetResponseMessageType(rvalue); switch (msgValue) { @@ -80,11 +85,17 @@ public byte[] Send(IDeviceMessage message) { case UpaMessageType.Nak: break; case UpaMessageType.Ready: + if (serverIsBusy) + { + await c.Send(new ArraySegment(buffer, 0, buffer.Length), token); + OnMessageSent?.Invoke(deviceMessage.ToString()); + serverIsBusy = false; + } + readyReceived = true; break; case UpaMessageType.Busy: - Disconnect(); - Connect(); + serverIsBusy = true; break; case UpaMessageType.TimeOut: break; @@ -95,48 +106,61 @@ public byte[] Send(IDeviceMessage message) { readyReceived = true; // since reboot doesn't return READY } - SendAckMessageToDevice(); + 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(); + c.Disconnect(); } - } while (!readyReceived); + }, + ClosedCallback = (c, r) => { tcs.SetResult(true); } + }; - if (responseMessage == null) - { - throw new DeviceException("The device did not return the expected response.", msgValue); - } + client.Message += ClientOnMessage(); - return responseMessage; - } - catch (Exception exc) { - _logger.Error(exc); - throw; - } - finally { - OnMessageSent?.Invoke(message.ToString()); - Disconnect(); + 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($"Client: {a.Message}"); + } + else + { + _logger.Error($"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 jsonObject = Encoding.UTF8.GetString(TrimResponse(response)); try { var jsonDoc = JsonDoc.Parse(jsonObject); @@ -149,28 +173,32 @@ private string GetResponseMessageType(byte[] response) } } - private void SendAckMessageToDevice() { + private static async 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); + await 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/UpaController.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs index 589b8fc4..e166a84b 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs @@ -264,7 +264,7 @@ internal TransactionResponse DoTransaction(IDeviceMessage request) { return null; } - string jsonObject = Encoding.UTF8.GetString(response); + var jsonObject = Encoding.UTF8.GetString(response); if(_logger.IsDebugEnabled) _logger.Debug($"Raw Response: {jsonObject}"); diff --git a/src/GlobalPayments.Api/Utils/Extensions.cs b/src/GlobalPayments.Api/Utils/Extensions.cs index 2abddfed..5979761b 100644 --- a/src/GlobalPayments.Api/Utils/Extensions.cs +++ b/src/GlobalPayments.Api/Utils/Extensions.cs @@ -239,5 +239,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}"); + } + + } + + } } From 00f2aa317afeb280215773587cd35b42ce40d149 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Fri, 12 Jan 2024 07:44:28 -0700 Subject: [PATCH 16/19] Updated to global api master and integrated with Payments 2.17.00.D and PA 2.17.17.00.A --- .../Builders/AuthorizationBuilder.cs | 44 +- .../Builders/FileProcessingBuilder.cs | 34 ++ .../Builders/ManagementBuilder.cs | 3 +- .../Builders/PayFacBuilder.cs | 34 ++ .../GpApi/GpApiAuthorizationRequestBuilder.cs | 39 +- .../GpApiFileProcessingRequestBuilder.cs | 44 ++ .../GpApi/GpApiPayFacRequestBuilder.cs | 47 ++ .../RequestBuilderValidations.cs | 56 +++ .../Entities/BankPaymentResponse.cs | 1 + .../Entities/BlockedCardType.cs | 14 + .../Entities/CommercialData.cs | 3 +- .../Entities/DisputeDocument.cs | 6 +- src/GlobalPayments.Api/Entities/Document.cs | 19 + .../Entities/DocumentUploadData.cs | 35 +- src/GlobalPayments.Api/Entities/Enums.cs | 24 +- .../Entities/Enums/AcquisitionType.cs | 22 + .../Entities/Enums/BlockCardType.cs | 19 + .../Entities/Enums/CardTypeFilter.cs | 16 + .../Entities/Enums/DocumentCategory.cs | 19 + .../Enums/FileProcessingActionType.cs | 6 + .../Entities/Enums/FileType.cs | 18 + .../Entities/Enums/FraudFilterResult.cs | 4 +- .../Entities/Enums/FundsStatus.cs | 11 + .../Entities/Enums/MerchantCategory.cs | 16 + .../Entities/Enums/Region.cs | 10 + .../Entities/Enums/SafIndicator.cs | 9 + .../Entities/Enums/SafMode.cs | 10 + .../Entities/Enums/ServiceEndpoints.cs | 5 + .../Entities/Enums/TransactionType.cs | 3 +- src/GlobalPayments.Api/Entities/Exceptions.cs | 16 - .../Entities/FileProcessor.cs | 15 + .../Entities/FileUploaded.cs | 15 + ...countDetails.cs => FundsAccountDetails.cs} | 8 +- .../Entities/GpApi/AccessTokenInfo.cs | 2 + .../Entities/GpApi/GpApiRequest.cs | 1 + .../Entities/GpApi/GpApiTokenResponse.cs | 3 + .../Entities/HostedPaymentData.cs | 2 + src/GlobalPayments.Api/Entities/Lodging.cs | 25 + .../Entities/Reporting/TransactionSummary.cs | 9 +- .../Entities/Transaction.cs | 6 +- .../Entities/UPA/ProcessingIndicator.cs | 10 + .../Entities/UPA/UpaParam.cs | 13 + .../Entities/UPA/UpaTransactionData.cs | 11 + src/GlobalPayments.Api/Entities/User.cs | 36 +- .../Entities/UserAccount.cs | 18 + .../BillPay/Responses/BillPayResponseBase.cs | 19 + .../TokenInformationRequestResponse.cs | 1 + src/GlobalPayments.Api/Gateways/Gateway.cs | 1 + .../Gateways/GpApiConnector.cs | 20 +- .../Gateways/GpEcomConnector.cs | 67 ++- .../Interfaces/IFileProcessingService.cs | 12 + .../Gateways/PorticoConnector.cs | 168 ++++++- .../Gateways/ProPayConnector.cs | 30 +- .../Gateways/TransitConnector.cs | 4 +- .../GlobalPayments.Api.4.6.csproj | 81 +++- .../Mapping/GpApiMapping.cs | 103 +++- .../AlternativePaymentMethod.cs | 1 + .../PaymentMethods/TransactionReference.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../ServiceConfigs/Gateways/GpApiConfig.cs | 4 + .../ServiceConfigs/Gateways/PorticoConfig.cs | 2 +- .../Services/CreditService.cs | 11 + .../Services/DeviceCloudService.cs | 35 ++ .../Services/DeviceService.cs | 5 + .../Services/FileProcessingService.cs | 21 + .../Services/GpApiService.cs | 4 +- .../Services/HostedService.cs | 74 ++- .../Services/PayFacService.cs | 2 +- src/GlobalPayments.Api/ServicesContainer.cs | 11 + .../Abstractions/IBatchClearResponse.cs | 7 + .../Abstractions/IDeviceCommInterface.cs | 2 + .../Abstractions/IDeviceInterface.cs | 39 +- .../Terminals/Abstractions/IDeviceResponse.cs | 1 - .../Abstractions/ISafDeleteFileResponse.cs | 7 + .../Abstractions/ISafParamsResponse.cs | 17 + .../Abstractions/ISafSummaryReport.cs | 8 + .../Abstractions/ISafUploadResponse.cs | 13 + .../Terminals/Builders/TerminalAuthBuilder.cs | 40 +- .../Terminals/Builders/TerminalBuilder.cs | 8 +- .../Builders/TerminalManageBuilder.cs | 75 ++- .../Builders/TerminalReportBuilder.cs | 24 +- .../Terminals/ConnectionConfig.cs | 29 +- .../Terminals/DeviceController.cs | 11 +- .../Terminals/DeviceInterface.cs | 122 +++-- .../Terminals/DeviceResponse.cs | 16 +- .../Terminals/Diamond/DiamondController.cs | 292 ++++++++++++ .../Diamond/Entities/DiamondCloudRequest.cs | 20 + .../Entities/Enums/AuthorizationMethod.cs | 9 + .../Entities/Enums/AuthorizationType.cs | 7 + .../Diamond/Entities/Enums/CardSource.cs | 9 + .../Enums/DiamondCloudSearchCriteria.cs | 6 + .../Enums/DiamondCloudTransactionType.cs | 13 + .../Entities/Enums/TransactionResult.cs | 8 + .../Interfaces/DiamondHttpInterface.cs | 118 +++++ .../Diamond/Interfaces/DiamondInterface.cs | 41 ++ .../Diamond/Responses/DiamondCloudResponse.cs | 265 +++++++++++ .../Terminals/DiamondCloudConfig.cs | 32 ++ .../Genius/Builders/MitcManageBuilder.cs | 66 +++ .../Terminals/Genius/Enums/MitcRequestType.cs | 74 +++ .../Genius/Enums/TransactionIdType.cs | 13 + .../Terminals/Genius/GeniusController.cs | 449 ++++++++++++++---- .../Terminals/Genius/GeniusInterface.cs | 192 +++++++- .../Genius/Interfaces/GeniusHttpInterface.cs | 2 + .../Genius/Interfaces/MitcGateway.cs | 132 +++++ .../Genius/Request/GeniusMitcRequest.cs | 43 ++ .../Genius/Responses/MitcResponse.cs | 357 ++++++++++++++ .../Genius/ServiceConfigs/MitcConfig.cs | 75 +++ .../Terminals/HPA/HpaController.cs | 8 +- .../Terminals/HPA/HpaEnums.cs | 1 + .../Terminals/HPA/HpaInterface.cs | 6 + .../HPA/Interfaces/HpaTcpInterface.cs | 18 +- .../Terminals/HPA/Responses/EODResponse.cs | 13 + .../Terminals/HPA/Responses/SAFResponse.cs | 96 ++-- .../HPA/Responses/SipBaseResponse.cs | 6 + .../HPA/Responses/SipDeviceResponse.cs | 3 +- .../Messaging/MessageReceivedEventHandler.cs | 4 + .../PAX/Interfaces/PaxHttpInterface.cs | 11 + .../PAX/Interfaces/PaxTcpInterface.cs | 28 +- .../Terminals/PAX/PaxController.cs | 20 +- .../Terminals/PAX/PaxEnums.cs | 2 + .../Terminals/PAX/PaxInterface.cs | 85 +++- .../PAX/Responses/BatchClearResponse.cs | 34 ++ .../Terminals/PAX/Responses/DeviceResponse.cs | 5 +- .../PAX/Responses/SafDeleteFileResponse.cs | 25 + .../PAX/Responses/SafParamsResponse.cs | 46 ++ .../PAX/Responses/SafSummaryReport.cs | 23 + .../PAX/Responses/SafUploadResponse.cs | 35 ++ .../Terminals/PAX/SubGroups/TraceSubGroups.cs | 3 + .../Terminals/SummaryResponse.cs | 10 +- .../Terminals/TerminalUtilities.cs | 22 +- .../UPA/Interfaces/UpaMicInterface.cs | 4 +- .../UPA/Interfaces/UpaTcpInterface.cs | 29 +- .../Terminals/UPA/Responses/BasicResponse.cs | 13 + .../UPA/Responses/BatchReportResponse.cs | 10 +- .../Terminals/UPA/Responses/CmdResult.cs | 10 + .../Terminals/UPA/Responses/Data.cs | 19 + .../UPA/Responses/SafReportResponse.cs | 4 +- .../UPA/Responses/TransactionResponse.cs | 137 ++++-- .../Terminals/UPA/Responses/UpaEODResponse.cs | 5 +- .../UPA/Responses/UpaGiftCardResponse.cs | 110 +++++ .../Terminals/UPA/Responses/UpaSAFResponse.cs | 1 + .../UPA/Responses/UpaSignatureResponse.cs | 50 ++ .../Terminals/UPA/UpaController.cs | 100 ++-- .../Terminals/UPA/UpaInterface.cs | 50 +- .../Terminals/UPA/UpaTransType.cs | 11 +- src/GlobalPayments.Api/Utils/EnumUtils.cs | 21 + src/GlobalPayments.Api/Utils/Extensions.cs | 7 +- .../Utils/GenerationUtils.cs | 17 +- src/GlobalPayments.Api/Utils/JsonUtils.cs | 10 + 149 files changed, 4667 insertions(+), 530 deletions(-) create mode 100644 src/GlobalPayments.Api/Builders/FileProcessingBuilder.cs create mode 100644 src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiFileProcessingRequestBuilder.cs create mode 100644 src/GlobalPayments.Api/Builders/RequestBuilder/RequestBuilderValidations.cs create mode 100644 src/GlobalPayments.Api/Entities/BlockedCardType.cs create mode 100644 src/GlobalPayments.Api/Entities/Document.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/AcquisitionType.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/BlockCardType.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/CardTypeFilter.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/DocumentCategory.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/FileProcessingActionType.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/FileType.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/FundsStatus.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/MerchantCategory.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/Region.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/SafIndicator.cs create mode 100644 src/GlobalPayments.Api/Entities/Enums/SafMode.cs create mode 100644 src/GlobalPayments.Api/Entities/FileProcessor.cs create mode 100644 src/GlobalPayments.Api/Entities/FileUploaded.cs rename src/GlobalPayments.Api/Entities/{TransferFundsAccountDetails.cs => FundsAccountDetails.cs} (56%) create mode 100644 src/GlobalPayments.Api/Entities/Lodging.cs create mode 100644 src/GlobalPayments.Api/Entities/UPA/ProcessingIndicator.cs create mode 100644 src/GlobalPayments.Api/Entities/UPA/UpaParam.cs create mode 100644 src/GlobalPayments.Api/Entities/UPA/UpaTransactionData.cs create mode 100644 src/GlobalPayments.Api/Entities/UserAccount.cs create mode 100644 src/GlobalPayments.Api/Gateways/Interfaces/IFileProcessingService.cs create mode 100644 src/GlobalPayments.Api/Services/DeviceCloudService.cs create mode 100644 src/GlobalPayments.Api/Services/FileProcessingService.cs create mode 100644 src/GlobalPayments.Api/Terminals/Abstractions/IBatchClearResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/Abstractions/ISafDeleteFileResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/Abstractions/ISafParamsResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/Abstractions/ISafSummaryReport.cs create mode 100644 src/GlobalPayments.Api/Terminals/Abstractions/ISafUploadResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/DiamondController.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/DiamondCloudRequest.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationMethod.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationType.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/CardSource.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudSearchCriteria.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudTransactionType.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/TransactionResult.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondHttpInterface.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondInterface.cs create mode 100644 src/GlobalPayments.Api/Terminals/Diamond/Responses/DiamondCloudResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/DiamondCloudConfig.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Builders/MitcManageBuilder.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Enums/MitcRequestType.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Enums/TransactionIdType.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Interfaces/MitcGateway.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Request/GeniusMitcRequest.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/Responses/MitcResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/Genius/ServiceConfigs/MitcConfig.cs create mode 100644 src/GlobalPayments.Api/Terminals/Messaging/MessageReceivedEventHandler.cs create mode 100644 src/GlobalPayments.Api/Terminals/PAX/Responses/BatchClearResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/PAX/Responses/SafDeleteFileResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/PAX/Responses/SafParamsResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/PAX/Responses/SafSummaryReport.cs create mode 100644 src/GlobalPayments.Api/Terminals/PAX/Responses/SafUploadResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Responses/BasicResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Responses/CmdResult.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Responses/Data.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Responses/UpaGiftCardResponse.cs create mode 100644 src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSignatureResponse.cs diff --git a/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs b/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs index cc629e28..d5c3a1f2 100644 --- a/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs +++ b/src/GlobalPayments.Api/Builders/AuthorizationBuilder.cs @@ -7,6 +7,8 @@ using GlobalPayments.Api.Network.Elements; using GlobalPayments.Api.Network.Entities; using GlobalPayments.Api.PaymentMethods; +using System.Reflection; +using System.Linq; namespace GlobalPayments.Api.Builders { /// @@ -97,6 +99,8 @@ public class AuthorizationBuilder : TransactionBuilder { internal StoredCredentialInitiator? TransactionInitiator { get; set; } internal BNPLShippingMethod BNPLShippingMethod {get;set;} internal bool MaskedDataResponse { get; set; } + internal BlockedCardType CardTypesBlocking { get; set; } + internal MerchantCategory? MerchantCategory { get; set; } internal bool HasEmvFallbackData { get { return (EmvFallbackCondition != null || EmvLastChipRead != null || !string.IsNullOrEmpty(PaymentApplicationVersion)); @@ -847,12 +851,26 @@ public AuthorizationBuilder WithSurchargeAmount(decimal? value) { return this; } - public AuthorizationBuilder WithMaskedDataResponse(bool value) - { + public AuthorizationBuilder WithMaskedDataResponse(bool value) { MaskedDataResponse = value; return this; } + public AuthorizationBuilder WithBlockedCardType(BlockedCardType cardTypesBlocking) { + var hasNulls = cardTypesBlocking.GetType().GetProperties().All(p => p.GetValue(cardTypesBlocking) == null); + if (hasNulls) { + throw new BuilderException("No properties set on the object"); + } + CardTypesBlocking = cardTypesBlocking; + + return this; + } + + public AuthorizationBuilder WithMerchantCategory(MerchantCategory value) { + MerchantCategory = value; + return this; + } + internal AuthorizationBuilder(TransactionType type, IPaymentMethod payment = null) : base(type) { WithPaymentMethod(payment); } @@ -987,7 +1005,27 @@ protected override void SetupValidations() { Validations.For(TransactionType.Sale) .With(TransactionModifier.EncryptedMobile) - .Check(() => PaymentMethod).IsNotNull(); + .Check(() => PaymentMethod).IsNotNull(); + + Validations.For(TransactionType.Sale) + .With(TransactionModifier.AlternativePaymentMethod) + .Check(() => PaymentMethod).IsNotNull() + .Check(() => Amount).IsNotNull() + .Check(() => Currency).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.StatusUpdateUrl)).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.ReturnUrl)).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.AccountHolderName)).IsNotNull(); + + Validations.For(TransactionType.Auth) + .With(TransactionModifier.AlternativePaymentMethod) + .Check(() => PaymentMethod).IsNotNull() + .Check(() => Amount).IsNotNull() + .Check(() => Currency).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.StatusUpdateUrl)).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.ReturnUrl)).IsNotNull() + .Check(() => PaymentMethod).PropertyOf(nameof(AlternativePaymentMethod.AccountHolderName)).IsNotNull(); + + } /// diff --git a/src/GlobalPayments.Api/Builders/FileProcessingBuilder.cs b/src/GlobalPayments.Api/Builders/FileProcessingBuilder.cs new file mode 100644 index 00000000..d66da05a --- /dev/null +++ b/src/GlobalPayments.Api/Builders/FileProcessingBuilder.cs @@ -0,0 +1,34 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Builders { + public class FileProcessingBuilder : BaseBuilder { + + public string ResourceId { get; set; } + + public FileProcessingActionType FileProcessingActionType { get; set; } + + public FileProcessingBuilder(FileProcessingActionType actionType) { + FileProcessingActionType = actionType; + } + + public override FileProcessor Execute(string configName = "default") { + base.Execute(configName); + var client = ServicesContainer.Instance.GetFileProcessingClient(configName); + return client.ProcessFileUpload(this); + } + + public FileProcessingBuilder WithResourceId(string resourceId) { + ResourceId = resourceId; + return this; + } + + protected override void SetupValidations() { + Validations.For(FileProcessingActionType.GET_DETAILS) + .Check(() => ResourceId).IsNotNull(); + } + } +} diff --git a/src/GlobalPayments.Api/Builders/ManagementBuilder.cs b/src/GlobalPayments.Api/Builders/ManagementBuilder.cs index be58b571..7b5c2381 100644 --- a/src/GlobalPayments.Api/Builders/ManagementBuilder.cs +++ b/src/GlobalPayments.Api/Builders/ManagementBuilder.cs @@ -23,6 +23,7 @@ internal string AuthorizationCode { return null; } } + internal decimal? batchDeviceId { get; set; } internal string BatchReference { get; set; } internal IEnumerable Bills { get; set; } internal string CardType { @@ -492,7 +493,7 @@ public override Transaction Execute(string configName = "default") { PaymentMethod is TransactionReference && PaymentMethod.PaymentMethodType == PaymentMethodType.BankPayment) { var obClient = ServicesContainer.Instance.GetOpenBanking(configName); - if (obClient != client) { + if (obClient != null && obClient != client) { return obClient.ManageOpenBanking(this); } } diff --git a/src/GlobalPayments.Api/Builders/PayFacBuilder.cs b/src/GlobalPayments.Api/Builders/PayFacBuilder.cs index fd3c7273..67bf24b9 100644 --- a/src/GlobalPayments.Api/Builders/PayFacBuilder.cs +++ b/src/GlobalPayments.Api/Builders/PayFacBuilder.cs @@ -33,6 +33,14 @@ public class PayFacBuilder : BaseBuilder where TResult : class /// Required for partners ordering Portico devices. Valid values: [ UTC, PT, MST, MT, CT, ET, HST, AT, AST, AKST, ACT, EET, EAT, MET, NET, PLT, IST, BST, VST, CTT, JST, ACT, AET, SST, NST, MIT, CNT, AGT, CAT ] /// public string TimeZone { get; set; } + + public PaymentMethodName? PaymentMethodName { get; set; } + + public PaymentMethodType? PaymentMethodType { get; set; } + + public string Currency { get; set; } + + public string ClientTransactionId{ get; set; } /// /// Business Data - Required for business validated accounts. May also be required for personal validated accounts @@ -161,6 +169,12 @@ protected override void SetupValidations() { .Check(() => AccountNumber).IsNotNull() .Check(() => DocumentUploadData).IsNotNull(); + Validations.For(TransactionType.UploadDocument) + .With(TransactionModifier.Merchant) + .Check(() => DocumentUploadData).IsNotNull() + .Check(() => DocumentUploadData).PropertyOf(nameof(DocumentUploadData.DocType)).IsNotNull(); + + Validations.For(TransactionType.ObtainSSOKey) .With(TransactionModifier.None) .Check(() => AccountNumber).IsNotNull() @@ -362,6 +376,26 @@ public PayFacBuilder WithTimeZone(string timezone) { return this; } + public PayFacBuilder WithPaymentMethodType(PaymentMethodType? paymentMethodType) { + PaymentMethodType = paymentMethodType; + return this; + } + + public PayFacBuilder WithPaymentMethodName(PaymentMethodName? paymentMethodName) { + PaymentMethodName = paymentMethodName; + return this; + } + + public PayFacBuilder WithCurrency(string currency) { + Currency = currency; + return this; + } + + public PayFacBuilder WithClientTransactionId(string value) { + ClientTransactionId = value; + return this; + } + public PayFacBuilder WithBusinessData(BusinessData businessData) { BusinessData = businessData; return this; diff --git a/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.cs b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.cs index 3004588b..90d8318e 100644 --- a/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.cs +++ b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.cs @@ -213,6 +213,10 @@ public Request BuildRequest(AuthorizationBuilder builder, GpApiConnector gateway } verificationData.Set("payment_method", paymentMethod); + if (builder.StoredCredential != null) { + SetRequestStoredCredentials(builder.StoredCredential, verificationData); + } + Request.MaskedValues = MaskedValues; return new Request { @@ -302,7 +306,13 @@ public Request BuildRequest(AuthorizationBuilder builder, GpApiConnector gateway var secureEcom = (builder.PaymentMethod as CreditCardData).ThreeDSecure; if (secureEcom != null) { var authentication = new JsonDoc().Set("id", secureEcom.ServerTransactionId); - var three_ds = new JsonDoc().Set("exempt_status", secureEcom.ExemptStatus.ToString()); + var three_ds = new JsonDoc() + .Set("exempt_status", secureEcom.ExemptStatus?.ToString()) + .Set("message_version", secureEcom.MessageVersion) + .Set("eci", secureEcom.Eci) + .Set("server_trans_reference", secureEcom.ServerTransactionId) + .Set("ds_trans_reference", secureEcom.DirectoryServerTransactionId) + .Set("value", secureEcom.AuthenticationValue); authentication.Set("three_ds", three_ds); paymentMethod.Set("authentication", authentication); @@ -520,9 +530,11 @@ public Request BuildRequest(AuthorizationBuilder builder, GpApiConnector gateway .Set("convenience_amount", builder.ConvenienceAmount.ToNumericCurrencyString()) .Set("country", gateway.GpApiConfig.Country) //.Set("language", EnumConverter.GetMapping(Target.GP_API, Language)) - .Set("ip_address", builder.CustomerIpAddress) - //.Set("site_reference", "") // - .Set("currency_conversion", !string.IsNullOrEmpty(builder.DccRateData?.DccId) ? new JsonDoc().Set("id", builder.DccRateData.DccId) : null) + .Set("ip_address", builder.CustomerIpAddress); + //.Set("site_reference", "") // + data.Set("merchant_category", builder.MerchantCategory.ToString() ?? null); + + data.Set("currency_conversion", !string.IsNullOrEmpty(builder.DccRateData?.DccId) ? new JsonDoc().Set("id", builder.DccRateData.DccId) : null) .Set("payment_method", paymentMethod) .Set("risk_assessment", builder.FraudFilterMode != null ? MapFraudManagement(builder) : null) .Set("link", !string.IsNullOrEmpty(builder.PaymentLinkId) ? new JsonDoc() @@ -556,12 +568,7 @@ builder.PaymentMethod is BNPL || // stored credential if (builder.StoredCredential != null) { - data.Set("initiator", EnumConverter.GetMapping(Target.GP_API, builder.StoredCredential.Initiator)); - var storedCredential = new JsonDoc() - .Set("model", EnumConverter.GetMapping(Target.GP_API, builder.StoredCredential.Type)) - .Set("reason", EnumConverter.GetMapping(Target.GP_API, builder.StoredCredential.Reason)) - .Set("sequence", EnumConverter.GetMapping(Target.GP_API, builder.StoredCredential.Sequence)); - data.Set("stored_credential", storedCredential); + SetRequestStoredCredentials(builder.StoredCredential, data); } Request.MaskedValues = MaskedValues; @@ -573,6 +580,16 @@ builder.PaymentMethod is BNPL || }; } + private void SetRequestStoredCredentials(StoredCredential storedCredential, JsonDoc request) + { + request.Set("initiator", EnumConverter.GetMapping(Target.GP_API, storedCredential.Initiator)); + var storedCredentialJson = new JsonDoc() + .Set("model", EnumConverter.GetMapping(Target.GP_API, storedCredential.Type)) + .Set("reason", EnumConverter.GetMapping(Target.GP_API, storedCredential.Reason)) + .Set("sequence", EnumConverter.GetMapping(Target.GP_API, storedCredential.Sequence)); + request.Set("stored_credential", storedCredentialJson); + } + private static void DisposeMaskedValues() { MaskedValues = null; @@ -932,6 +949,6 @@ private static JsonDoc SetOrderInformation(AuthorizationBuilder builder, ref Jso } return requestBody; - } + } } } diff --git a/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiFileProcessingRequestBuilder.cs b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiFileProcessingRequestBuilder.cs new file mode 100644 index 00000000..9e94f478 --- /dev/null +++ b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiFileProcessingRequestBuilder.cs @@ -0,0 +1,44 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Entities.GpApi; +using GlobalPayments.Api.Gateways; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace GlobalPayments.Api.Builders.RequestBuilder.GpApi { + internal class GpApiFileProcessingRequestBuilder : IRequestBuilder { + public Request BuildRequest(FileProcessingBuilder builder, GpApiConnector gateway) { + switch (builder.FileProcessingActionType) { + case FileProcessingActionType.CREATE_UPLOAD_URL: + var data = new JsonDoc() + .Set("merchant_id", gateway.GpApiConfig.MerchantId) + .Set("account_id", gateway.GpApiConfig.AccessTokenInfo.FileProcessingAccountID); + + var notifications = new JsonDoc() + .Set("status_url", gateway.GpApiConfig.StatusUrl); + + if (notifications.HasKeys()) { + data.Set("notifications", notifications); + } + + return new Request { + Verb = HttpMethod.Post, + Endpoint = $"{GpApiRequest.FILE_PROCESSING}", + RequestBody = data.ToString(), + }; + + case FileProcessingActionType.GET_DETAILS: + return new Request { + Verb = HttpMethod.Get, + Endpoint = $"{GpApiRequest.FILE_PROCESSING}/{builder.ResourceId}" + }; + + default: + return null; + } + } + } +} diff --git a/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiPayFacRequestBuilder.cs b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiPayFacRequestBuilder.cs index dabd2055..81e477e3 100644 --- a/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiPayFacRequestBuilder.cs +++ b/src/GlobalPayments.Api/Builders/RequestBuilder/GpApi/GpApiPayFacRequestBuilder.cs @@ -21,6 +21,7 @@ public Request BuildRequest(PayFacBuilder builder, GpApiConnector gateway) { _builder = builder; var merchantUrl = !string.IsNullOrEmpty(gateway.GpApiConfig.MerchantId) ? $"/merchants/{gateway.GpApiConfig.MerchantId}" : string.Empty; + Validate(builder.TransactionType, gateway); switch (builder.TransactionType) { case TransactionType.Create: @@ -93,6 +94,34 @@ public Request BuildRequest(PayFacBuilder builder, GpApiConnector gateway) }; } break; + case TransactionType.AddFunds: + var dataFunds = new JsonDoc(); + dataFunds.Set("account_id", builder.AccountNumber) + .Set("type", builder.PaymentMethodType.ToString() ?? null) + .Set("amount", builder.Amount) + .Set("currency", builder.Currency ?? null) + .Set("payment_method", builder.PaymentMethodName.ToString() ?? null) + .Set("reference", builder.ClientTransactionId ?? GenerationUtils.GenerateOrderId()); + return new Request + { + Verb = HttpMethod.Post, + Endpoint = $"{merchantUrl}{GpApiRequest.MERCHANT_MANAGEMENT_ENDPOINT}/{_builder.UserReference.UserId}/settlement/funds", + RequestBody = dataFunds.ToString(), + }; + + break; + case TransactionType.UploadDocument: + var requestData = new JsonDoc(); + requestData.Set("function", builder.DocumentUploadData.DocCategory.ToString()) + .Set("b64_content", builder.DocumentUploadData.Document) + .Set("format", builder.DocumentUploadData.DocType.ToString()); + return new Request + { + Verb = HttpMethod.Post, + Endpoint = $"{merchantUrl}{GpApiRequest.MERCHANT_MANAGEMENT_ENDPOINT}/{_builder.UserReference.UserId}/documents", + RequestBody = requestData.ToString() + }; + break; default: break; } @@ -100,6 +129,24 @@ public Request BuildRequest(PayFacBuilder builder, GpApiConnector gateway) return null; } + private void Validate(TransactionType transactionType, GpApiConnector gateway) + { + string errorMsg = string.Empty; + switch (transactionType) { + case TransactionType.AddFunds: + if (string.IsNullOrEmpty(gateway.GpApiConfig.MerchantId) && string.IsNullOrEmpty(_builder.UserReference?.UserId)) { + errorMsg = "property UserId or config MerchantId cannot be null for this transactionType"; + } + break; + default: + break; + } + + if (!string.IsNullOrEmpty(errorMsg)) { + throw new GatewayException(errorMsg); + } + } + private static Dictionary MapAddress(Address address, string countryCodeType = null, string functionKey = null) { var countryCode = string.Empty; diff --git a/src/GlobalPayments.Api/Builders/RequestBuilder/RequestBuilderValidations.cs b/src/GlobalPayments.Api/Builders/RequestBuilder/RequestBuilderValidations.cs new file mode 100644 index 00000000..518c108c --- /dev/null +++ b/src/GlobalPayments.Api/Builders/RequestBuilder/RequestBuilderValidations.cs @@ -0,0 +1,56 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals; +using System; +using System.Reflection; + +namespace GlobalPayments.Api.Builders.RequestBuilder { + internal class RequestBuilderValidations { + + private Validations _validations; + public RequestBuilderValidations(Validations validations) { + _validations = validations; + } + /// + /// Validate method for Terminals + /// + /// + /// + /// + public void Validate(T builder, TerminalReportType actionType) { + foreach (var key in _validations.rules.Keys) { + if (!key.Equals(actionType)) { + return; + } + foreach (var validation in _validations.rules[key]) { + if (validation.clause == null) continue; + + // modifier + if (validation.constraint != null) { + Enum modifier = GetPropertyValue(builder, validation.constraint); + if (!Equals(validation.constraint, modifier)) + continue; + } + + // check precondition + if (validation.precondition != null) { + if (!validation.precondition.callback(actionType)) + continue; + } + + if (!validation.clause.callback(builder)) + throw new BuilderException(validation.clause.message); + } + } + } + + private Enum GetPropertyValue(object obj, object comp) { + if (obj == null) return null; + + foreach (var propInfo in obj.GetType().GetRuntimeProperties()) { + if (propInfo.Name == comp.GetType().Name) + return (Enum)propInfo.GetValue(obj); + } + return null; + } + } +} diff --git a/src/GlobalPayments.Api/Entities/BankPaymentResponse.cs b/src/GlobalPayments.Api/Entities/BankPaymentResponse.cs index 83bd6a77..d291cfd0 100644 --- a/src/GlobalPayments.Api/Entities/BankPaymentResponse.cs +++ b/src/GlobalPayments.Api/Entities/BankPaymentResponse.cs @@ -19,5 +19,6 @@ public class BankPaymentResponse { public string RemittanceReferenceType { get; set; } public decimal? Amount { get; set; } public string Currency { get; set; } + public string MaskedIbanLast4 { get; set; } } } diff --git a/src/GlobalPayments.Api/Entities/BlockedCardType.cs b/src/GlobalPayments.Api/Entities/BlockedCardType.cs new file mode 100644 index 00000000..2f9ff698 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/BlockedCardType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities +{ + public class BlockedCardType + { + public bool? Consumerdebit { get; set; } + public bool? Consumercredit { get; set; } + public bool? Commercialcredit { get; set; } + public bool? Commercialdebit { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/CommercialData.cs b/src/GlobalPayments.Api/Entities/CommercialData.cs index d535259d..510f811f 100644 --- a/src/GlobalPayments.Api/Entities/CommercialData.cs +++ b/src/GlobalPayments.Api/Entities/CommercialData.cs @@ -40,7 +40,8 @@ public class CommercialData { public TaxType TaxType { get; private set; } public string VAT_InvoiceNumber { get; set; } - + public decimal? VATTaxAmtFreight { get; set; } + public decimal? VATTaxRateFreight { get; set; } public CommercialData(TaxType taxType, CommercialIndicator level = CommercialIndicator.Level_II) { TaxType = taxType; CommercialIndicator = level; diff --git a/src/GlobalPayments.Api/Entities/DisputeDocument.cs b/src/GlobalPayments.Api/Entities/DisputeDocument.cs index ae407514..51aa0b66 100644 --- a/src/GlobalPayments.Api/Entities/DisputeDocument.cs +++ b/src/GlobalPayments.Api/Entities/DisputeDocument.cs @@ -1,10 +1,8 @@ using Newtonsoft.Json; namespace GlobalPayments.Api.Entities { - public class DisputeDocument { - [JsonProperty("id")] - public string Id { get; set; } - + public class DisputeDocument : Document + { [JsonProperty("type")] public string Type { get; set; } diff --git a/src/GlobalPayments.Api/Entities/Document.cs b/src/GlobalPayments.Api/Entities/Document.cs new file mode 100644 index 00000000..eb876ce1 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Document.cs @@ -0,0 +1,19 @@ +using GlobalPayments.Api.Entities.Enums; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities +{ + public class Document + { + [JsonProperty("id")] + public string Id { get; set; } + public string Name { get; set; } + public string Status { get; set; } + public string TimeCreated { get; set; } + public FileType Format { get; set; } + public DocumentCategory Category { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/DocumentUploadData.cs b/src/GlobalPayments.Api/Entities/DocumentUploadData.cs index c7f0f4b0..242dafb4 100644 --- a/src/GlobalPayments.Api/Entities/DocumentUploadData.cs +++ b/src/GlobalPayments.Api/Entities/DocumentUploadData.cs @@ -1,4 +1,5 @@ -using System; +using GlobalPayments.Api.Entities.Enums; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; @@ -17,14 +18,14 @@ public class DocumentUploadData { /// /// The file format of the Document to be uploaded. This property MUST be set if using the Document property directly, but will be set automatically if using the DocumentPath property /// - private string _docType; - public string DocType + private FileType? _docType; + public FileType? DocType { get { return _docType; } set { - if (_validDocTypes.Contains(value)) { - _docType = value; + if (_validDocTypes.Contains(value.Value.ToString())) { + _docType = value.Value; } else { throw new Exception("The provided file type is not supported."); @@ -41,15 +42,15 @@ public string DocType /// The type of document you've been asked to provide by ProPay's Risk team. Valid values are: /// Verification, FraudHolds, Underwriting, RetrievalRequest /// - public string DocCategory { get; set; } + public DocumentCategory? DocCategory { get; set; } public string DocumentPath { set { var docPath = value; if (docPath != null) { - var docType = docPath.Substring(docPath.LastIndexOf('.') + 1); + var docType = docPath.Substring(docPath.LastIndexOf('.') + 1).ToUpper(); if (_validDocTypes.Contains(docType)) { - DocType = docType; + DocType = (FileType)Enum.Parse(typeof(FileType), docType); Document = Convert.ToBase64String(System.IO.File.ReadAllBytes(docPath)); } else { @@ -65,15 +66,15 @@ public string DocumentPath { private ReadOnlyCollection _validDocTypes { get; } = new ReadOnlyCollection( new string[] { - "tif", - "tiff", - "bmp", - "jpg", - "jpeg", - "gif", - "png", - "doc", - "docx" + "TIF", + "TIFF", + "BMP", + "JPG", + "JPEG", + "GIF", + "PNG", + "DOC", + "DOCX" } ); } diff --git a/src/GlobalPayments.Api/Entities/Enums.cs b/src/GlobalPayments.Api/Entities/Enums.cs index 1198beb8..bbf049f6 100644 --- a/src/GlobalPayments.Api/Entities/Enums.cs +++ b/src/GlobalPayments.Api/Entities/Enums.cs @@ -72,6 +72,12 @@ public enum DeviceType /// Indicates a UPA device /// UPA_DEVICE, + PAX_ARIES8, + PAX_A80, + PAX_A35, + PAX_A920, + PAX_A77, + NEXGO_N5, /// /// Indicates a genius terminal /// @@ -80,7 +86,11 @@ public enum DeviceType /// /// Indicates a Nucleus terminal /// - NUCLEUS_SATURN_1000 + NUCLEUS_SATURN_1000, + /// + /// Indicates a genius verifone P400 + /// + GENIUS_VERIFONE_P400, } @@ -993,7 +1003,8 @@ public enum AlternativePaymentType { WECHAT_PAY, ZIMPLER, UK_DIRECT_DEBIT, - PAYBYBANKAPP + PAYBYBANKAPP, + ALIPAY } public enum CardType { @@ -1041,6 +1052,14 @@ internal static class SAFReportType { public const string APPROVED_VOID = "APPROVED SAF VOID SUMMARY"; public const string PENDING_VOID = "PENDING SAF VOID SUMMARY"; public const string DECLINED_VOID = "DECLINED SAF VOID SUMMARY"; + public const string PROVISIONAL = "PROVISIONAL SAF SUMMARY"; + public const string DISCARDED = "DISCARDED SAF SUMMARY"; + public const string REVERSAL = "REVERSAL SUMMARY"; + public const string EMV_DECLINED = "EMV OFFLINE DECLINE SUMMARY"; + public const string ATTACHMENT = "ATTACHMENT SUMMARY"; + public const string PROVISIONAL_VOID = "PROVISIONAL SAF VOID SUMMARY"; + public const string DISCARDED_VOID = "DISCARDED SAF VOID SUMMARY"; + } internal static class EODCommandType { @@ -1055,6 +1074,7 @@ internal static class EODCommandType { public const string EMV_PARAMETER_DOWNLOAD = "EMVPDL"; public const string EMV_CRYPTOGRAM_TYPE = "EMVTC"; public const string GET_BATCH_REPORT = "GetBatchReport"; + public const string GET_SAF_REPORT = "GetSAFReport"; } internal static class CardSummaryType { diff --git a/src/GlobalPayments.Api/Entities/Enums/AcquisitionType.cs b/src/GlobalPayments.Api/Entities/Enums/AcquisitionType.cs new file mode 100644 index 00000000..ef7736aa --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/AcquisitionType.cs @@ -0,0 +1,22 @@ +using GlobalPayments.Api.Utils; + +namespace GlobalPayments.Api.Entities.Enums { + public enum AcquisitionType { + [Map(Target.UPA, "None")] + None, + [Map(Target.UPA, "Contact")] + Contact, + [Map(Target.UPA, "Contactless")] + Contactless, + [Map(Target.UPA, "Swipe")] + Swipe, + [Map(Target.UPA, "Manual")] + Manual, + [Map(Target.UPA, "Scan")] + Scan, + [Map(Target.UPA, "Insert")] + Insert, + [Map(Target.UPA, "Tap")] + Tap + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/BlockCardType.cs b/src/GlobalPayments.Api/Entities/Enums/BlockCardType.cs new file mode 100644 index 00000000..0cbb01c0 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/BlockCardType.cs @@ -0,0 +1,19 @@ +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities.Enums +{ + public enum BlockCardType + { + [Description("consumercredit")] + CONSUMER_CREDIT, + [Description("consumerdebit")] + CONSUMER_DEBIT, + [Description("commercialdebit")] + COMMERCIAL_DEBIT, + [Description("commercialcredit")] + COMMERCIAL_CREDIT + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/CardTypeFilter.cs b/src/GlobalPayments.Api/Entities/Enums/CardTypeFilter.cs new file mode 100644 index 00000000..61eb1bd4 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/CardTypeFilter.cs @@ -0,0 +1,16 @@ +using GlobalPayments.Api.Utils; + +namespace GlobalPayments.Api.Entities.Enums { + public enum CardTypeFilter { + [Map(Target.UPA, "GIFT")] + GIFT, + [Map(Target.UPA, "VISA")] + VISA, + [Map(Target.UPA, "MC")] + MC, + [Map(Target.UPA, "AMEX")] + AMEX, + [Map(Target.UPA, "DISCOVER")] + DISCOVER + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/DocumentCategory.cs b/src/GlobalPayments.Api/Entities/Enums/DocumentCategory.cs new file mode 100644 index 00000000..e005aba0 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/DocumentCategory.cs @@ -0,0 +1,19 @@ +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities.Enums +{ + public enum DocumentCategory + { + [Map(Target.GP_API, "IDENTITY_VERIFICATION")] + IDENTITY_VERIFICATION, + [Map(Target.GP_API, "RISK_REVIEW")] + RISK_REVIEW, + [Map(Target.GP_API, "UNDERWRITING")] + UNDERWRITING, + + VERIFICATION + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/FileProcessingActionType.cs b/src/GlobalPayments.Api/Entities/Enums/FileProcessingActionType.cs new file mode 100644 index 00000000..84227821 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/FileProcessingActionType.cs @@ -0,0 +1,6 @@ +namespace GlobalPayments.Api.Entities.Enums { + public enum FileProcessingActionType { + CREATE_UPLOAD_URL = 1, + GET_DETAILS = 2 + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/FileType.cs b/src/GlobalPayments.Api/Entities/Enums/FileType.cs new file mode 100644 index 00000000..16757855 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/FileType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities.Enums +{ + public enum FileType { + TIF, + TIFF, + BMP, + JPG, + JPEG, + GIF, + PNG, + DOC, + DOCX + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/FraudFilterResult.cs b/src/GlobalPayments.Api/Entities/Enums/FraudFilterResult.cs index 5f1d744f..5f47ccd4 100644 --- a/src/GlobalPayments.Api/Entities/Enums/FraudFilterResult.cs +++ b/src/GlobalPayments.Api/Entities/Enums/FraudFilterResult.cs @@ -21,10 +21,10 @@ public enum FraudFilterResult { [Map(Target.GP_API, "ERROR")] ERROR, - [Map(Target.GP_API, "RELEASE_SUCCESSFULL")] + [Map(Target.GP_API, "RELEASE_SUCCESSFUL")] RELEASE_SUCCESSFUL, - [Map(Target.GP_API, "HOLD_SUCCESSFULL")] + [Map(Target.GP_API, "HOLD_SUCCESSFUL")] HOLD_SUCCESSFUL } } diff --git a/src/GlobalPayments.Api/Entities/Enums/FundsStatus.cs b/src/GlobalPayments.Api/Entities/Enums/FundsStatus.cs new file mode 100644 index 00000000..6beff77b --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/FundsStatus.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities.Enums +{ + public enum FundsStatus { + CAPTURED, + DECLINE + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/MerchantCategory.cs b/src/GlobalPayments.Api/Entities/Enums/MerchantCategory.cs new file mode 100644 index 00000000..f72da6e0 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/MerchantCategory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities.Enums +{ + public enum MerchantCategory { + HOTEL, + AIRLINE, + RETAIL, + TOP_UP, + PLAYER, + CD_KEY, + OTHER + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/Region.cs b/src/GlobalPayments.Api/Entities/Enums/Region.cs new file mode 100644 index 00000000..7f1900ab --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/Region.cs @@ -0,0 +1,10 @@ +namespace GlobalPayments.Api.Entities.Enums { + public enum Region { + US, + CA, + AU, + NZ, + UK, + EU + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/SafIndicator.cs b/src/GlobalPayments.Api/Entities/Enums/SafIndicator.cs new file mode 100644 index 00000000..13bbccbc --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/SafIndicator.cs @@ -0,0 +1,9 @@ +namespace GlobalPayments.Api.Entities.Enums +{ + public enum SafIndicator + { + NEWLY_STORED_TRANSACTIONS, + FAILED_TRANSACTIONS, + ALL_TRANSACTIONS + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/SafMode.cs b/src/GlobalPayments.Api/Entities/Enums/SafMode.cs new file mode 100644 index 00000000..3f9b1455 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Enums/SafMode.cs @@ -0,0 +1,10 @@ +namespace GlobalPayments.Api.Entities.Enums +{ + public enum SafMode + { + STAY_ONLINE, + STAY_OFFLINE, + OFFLINE_TILL_BATCH, + OFFLINE_ONDEMAND_OR_AUTO + } +} diff --git a/src/GlobalPayments.Api/Entities/Enums/ServiceEndpoints.cs b/src/GlobalPayments.Api/Entities/Enums/ServiceEndpoints.cs index fdd023f0..78824484 100644 --- a/src/GlobalPayments.Api/Entities/Enums/ServiceEndpoints.cs +++ b/src/GlobalPayments.Api/Entities/Enums/ServiceEndpoints.cs @@ -30,5 +30,10 @@ public static class ServiceEndpoints { public const string Transaction_API_TEST = "https://api.pit.paygateway.com/transactions"; public const string OPEN_BANKING_TEST = "https://api.sandbox.globalpay-ecommerce.com/openbanking"; public const string OPEN_BANKING_PRODUCTION = "https://api.globalpay-ecommerce.com/openbanking"; + public const string DIAMOND_CLOUD_TEST = "https://qr-cert.simpletabcloud.com/tomcat/command"; + public const string DIAMOND_CLOUD_PROD = "https://qr.simpletabcloud.com/tomcat/command"; + public const string DIAMOND_CLOUD_PROD_EU= "https://qreu.simpletabcloud.com/tomcat/command"; + public const string GENIUS_MITC_PRODUCTION = "https://api.paygateway.com/transactions"; + public const string GENIUS_MITC_TEST = "https://api.pit.paygateway.com/transactions"; } } diff --git a/src/GlobalPayments.Api/Entities/Enums/TransactionType.cs b/src/GlobalPayments.Api/Entities/Enums/TransactionType.cs index 73cc2575..01bb2a9d 100644 --- a/src/GlobalPayments.Api/Entities/Enums/TransactionType.cs +++ b/src/GlobalPayments.Api/Entities/Enums/TransactionType.cs @@ -334,6 +334,7 @@ public enum TransactionType : byte { OrderDevice = 66, - TransferFunds = 67 + TransferFunds = 67, + } } diff --git a/src/GlobalPayments.Api/Entities/Exceptions.cs b/src/GlobalPayments.Api/Entities/Exceptions.cs index b14dba53..c2662cf4 100644 --- a/src/GlobalPayments.Api/Entities/Exceptions.cs +++ b/src/GlobalPayments.Api/Entities/Exceptions.cs @@ -12,22 +12,6 @@ public class ApiException : Exception { public ApiException(string message = null, Exception innerException = null) : base(message, innerException) { } } - public class DeviceException : Exception - { - /// The exception message - /// The device state - public DeviceException(string message, string state) : base(message) - { - State = state; - } - - /// - /// Gets the state. - /// - /// The state. - public string State { get; } - } - /// /// A builder error occurred. Check the method calls against the builder. /// diff --git a/src/GlobalPayments.Api/Entities/FileProcessor.cs b/src/GlobalPayments.Api/Entities/FileProcessor.cs new file mode 100644 index 00000000..b20f05c9 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/FileProcessor.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace GlobalPayments.Api.Entities { + public class FileProcessor { + public string ResourceId { get; set; } + public string UploadUrl { get; set; } + public string ExpirationDate { get; set; } + public string Status { get; set; } + public string CreatedDate { get; set; } + public string TotalRecordCount { get; set; } + public string ResponseCode { get; set; } + public string ResponseMessage { get; set; } + public List FilesUploaded { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/FileUploaded.cs b/src/GlobalPayments.Api/Entities/FileUploaded.cs new file mode 100644 index 00000000..43219ee0 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/FileUploaded.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities +{ + public class FileUploaded + { + public string FileId { get; set; } + public string FileName { get; set; } + public string TimeCreated { get; set; } + public string Url { get; set; } + public string ExpirationDate { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/TransferFundsAccountDetails.cs b/src/GlobalPayments.Api/Entities/FundsAccountDetails.cs similarity index 56% rename from src/GlobalPayments.Api/Entities/TransferFundsAccountDetails.cs rename to src/GlobalPayments.Api/Entities/FundsAccountDetails.cs index 36ff4d4a..cb06cf73 100644 --- a/src/GlobalPayments.Api/Entities/TransferFundsAccountDetails.cs +++ b/src/GlobalPayments.Api/Entities/FundsAccountDetails.cs @@ -4,13 +4,17 @@ namespace GlobalPayments.Api.Entities { - public class TransferFundsAccountDetails { + public class FundsAccountDetails { public string Id { get; set; } public string Status { get; set; } public string TimeCreated { get; set; } + public string TimeLastUpdated { get; set; } public decimal? Amount { get; set; } public string Reference { get; set; } public string Description { get; set; } - + public string Currency { get; set; } + public string PaymentMethodType { get; set; } + public string PaymentMethodName { get; set; } + public UserAccount Account { get; set; } } } diff --git a/src/GlobalPayments.Api/Entities/GpApi/AccessTokenInfo.cs b/src/GlobalPayments.Api/Entities/GpApi/AccessTokenInfo.cs index fad9a623..c1d5b321 100644 --- a/src/GlobalPayments.Api/Entities/GpApi/AccessTokenInfo.cs +++ b/src/GlobalPayments.Api/Entities/GpApi/AccessTokenInfo.cs @@ -13,11 +13,13 @@ public class AccessTokenInfo public string TransactionProcessingAccountName { get; set; } public string RiskAssessmentAccountName { get; set; } public string MerchantManagementAccountName { get; set; } + public string FileProcessingAccountName { get; set; } public string DataAccountID { get; set; } public string DisputeManagementAccountID { get; set; } public string TokenizationAccountID { get; set; } public string TransactionProcessingAccountID { get; set; } public string RiskAssessmentAccountID { get; set; } public string MerchantManagementAccountID { get; set; } + public string FileProcessingAccountID { get; set; } } } diff --git a/src/GlobalPayments.Api/Entities/GpApi/GpApiRequest.cs b/src/GlobalPayments.Api/Entities/GpApi/GpApiRequest.cs index de62da1e..131db7a3 100644 --- a/src/GlobalPayments.Api/Entities/GpApi/GpApiRequest.cs +++ b/src/GlobalPayments.Api/Entities/GpApi/GpApiRequest.cs @@ -22,5 +22,6 @@ internal class GpApiRequest { public const string ACCOUNTS_ENDPOINT = "/accounts"; public const string TRANSFER_ENDPOINT = "/transfers"; public const string DEVICES = "/devices"; + public const string FILE_PROCESSING = "/files"; } } diff --git a/src/GlobalPayments.Api/Entities/GpApi/GpApiTokenResponse.cs b/src/GlobalPayments.Api/Entities/GpApi/GpApiTokenResponse.cs index 04d30850..3896b5e5 100644 --- a/src/GlobalPayments.Api/Entities/GpApi/GpApiTokenResponse.cs +++ b/src/GlobalPayments.Api/Entities/GpApi/GpApiTokenResponse.cs @@ -11,6 +11,7 @@ internal class GpApiTokenResponse { const string TRANSACTION_PROCESSING_ACCOUNT_NAME_PREFIX = "TRA_"; const string RIKS_ASSESSMENT_ACCOUNT_NAME_PREFIX = "RAA_"; const string MERCHANT_MANAGEMENT_ACCOUNT_NAME_PREFIX = "MMA_"; + const string FILE_PROCESSING_ACCOUNT_NAME_PREFIX = "FPA_"; internal string Token { get; private set; } internal string Type { get; private set; } @@ -28,6 +29,7 @@ internal class GpApiTokenResponse { internal string TransactionProcessingAccountName { get { return GetAccountName(TRANSACTION_PROCESSING_ACCOUNT_NAME_PREFIX); } } internal string RiskAssessmentAccountName { get { return GetAccountName(RIKS_ASSESSMENT_ACCOUNT_NAME_PREFIX); } } internal string MerchantManagementAccountName { get { return GetAccountName(MERCHANT_MANAGEMENT_ACCOUNT_NAME_PREFIX); } } + internal string FileProcessingAccountName { get { return GetAccountName(FILE_PROCESSING_ACCOUNT_NAME_PREFIX); } } internal string DataAccountID { get { return GetAccountID(DATA_ACCOUNT_NAME_PREFIX); } } internal string DisputeManagementAccountID { get { return GetAccountID(DISPUTE_MANAGEMENT_ACCOUNT_NAME_PREFIX); } } @@ -35,6 +37,7 @@ internal class GpApiTokenResponse { internal string TransactionProcessingAccountID { get { return GetAccountID(TRANSACTION_PROCESSING_ACCOUNT_NAME_PREFIX); } } internal string RiskAssessmentAccountID { get { return GetAccountID(RIKS_ASSESSMENT_ACCOUNT_NAME_PREFIX); } } internal string MerchantManagementAccountID { get { return GetAccountID(MERCHANT_MANAGEMENT_ACCOUNT_NAME_PREFIX); } } + internal string FileProcessingAccountID { get { return GetAccountID(FILE_PROCESSING_ACCOUNT_NAME_PREFIX); } } private string GetAccountID(string accountPrefix) { return Accounts?.Where(a => a.Id.StartsWith(accountPrefix)).Select(a => a.Id).FirstOrDefault(); diff --git a/src/GlobalPayments.Api/Entities/HostedPaymentData.cs b/src/GlobalPayments.Api/Entities/HostedPaymentData.cs index 3e9e8ac7..68eb57c5 100644 --- a/src/GlobalPayments.Api/Entities/HostedPaymentData.cs +++ b/src/GlobalPayments.Api/Entities/HostedPaymentData.cs @@ -142,6 +142,8 @@ public HostedPaymentData() { public bool EnableExemptionOptimization { get; set; } + public BlockCardType[] BlockCardTypes { get; set; } + /// /// Determine whether or not the address and contact information must be provided in the HPP request and whether they will/won't be editable by the customer. /// diff --git a/src/GlobalPayments.Api/Entities/Lodging.cs b/src/GlobalPayments.Api/Entities/Lodging.cs new file mode 100644 index 00000000..d48c7bb1 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/Lodging.cs @@ -0,0 +1,25 @@ +using System; + +namespace GlobalPayments.Api.Entities +{ + public class Lodging + { + public int? FolioNumber { get; set; } + public int? StayDuration { get; set; } + public DateTime? CheckInDate { get; set; } + public DateTime? CheckOutDate { get; set; } + public decimal? DailyRate { get; set; } + public int? PreferredCustomer { get; set; } + public ExtraChargeTypes ExtraChargeTypes { get; set; } + public decimal? ExtraChargeTotal { get; set; } + } + public class ExtraChargeTypes + { + public bool HasRestaurantCharge { get; set; } + public bool HasGiftShopCharge { get; set; } + public bool HasMiniBarCharge { get; set; } + public bool HasTelephoneCharge { get; set; } + public bool HasLaundryCharge { get; set; } + public bool HasOtherCharge { get; set; } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Entities/Reporting/TransactionSummary.cs b/src/GlobalPayments.Api/Entities/Reporting/TransactionSummary.cs index ff9849db..75756d93 100644 --- a/src/GlobalPayments.Api/Entities/Reporting/TransactionSummary.cs +++ b/src/GlobalPayments.Api/Entities/Reporting/TransactionSummary.cs @@ -78,7 +78,8 @@ public class TransactionSummary { /// The client transaction ID sent in the authorization request. /// public string ClientTransactionId { get; set; } - + public CommercialData CommercialData { get; set; } + public CommercialLineItem CommercialLineItem { get; set; } public string CompanyName { get; set; } /// @@ -217,6 +218,8 @@ public class TransactionSummary { /// The reference number provided by the issuer. /// public string ReferenceNumber { get; set; } + public string SafReferenceNumber { get; set; } + public string TranNo { get; set; } public int? RepeatCount { get; set; } @@ -245,6 +248,9 @@ public class TransactionSummary { /// The originally requested shipping amount. /// public decimal? ShippingAmount { get; set; } + public string ShippingInvoiceNbr { get; set; } + public int? ShippingMonth { get; set; } + public int? ShippingDay { get; set; } public string SiteTrace { get; set; } @@ -316,5 +322,6 @@ public class TransactionSummary { public string Fingerprint { get; set; } public string FingerprintIndicator { get; set; } + public string Email { get; set;} } } diff --git a/src/GlobalPayments.Api/Entities/Transaction.cs b/src/GlobalPayments.Api/Entities/Transaction.cs index c5276f41..aa2cd780 100644 --- a/src/GlobalPayments.Api/Entities/Transaction.cs +++ b/src/GlobalPayments.Api/Entities/Transaction.cs @@ -133,8 +133,8 @@ public string CardType public PayByLinkResponse PayByLinkResponse { get; set; } - private List _transfersFundsAccounts; - public List TransfersFundsAccounts + private List _transfersFundsAccounts; + public List TransfersFundsAccounts { get { return _transfersFundsAccounts; @@ -145,7 +145,7 @@ public List TransfersFundsAccounts } if (value.Count > 0) { if(TransactionReference.TransfersFundsAccounts == null) { - TransactionReference.TransfersFundsAccounts = new List(); + TransactionReference.TransfersFundsAccounts = new List(); } TransactionReference.TransfersFundsAccounts = value; } diff --git a/src/GlobalPayments.Api/Entities/UPA/ProcessingIndicator.cs b/src/GlobalPayments.Api/Entities/UPA/ProcessingIndicator.cs new file mode 100644 index 00000000..aec49043 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/UPA/ProcessingIndicator.cs @@ -0,0 +1,10 @@ +using GlobalPayments.Api.Entities.Enums; + +namespace GlobalPayments.Api.Entities.UPA { + public class ProcessingIndicator { + public string QuickChip { get; set; } + public string CheckLuhn { get; set; } + public string SecurityCode { get; set; } + public CardTypeFilter CardTypeFilter { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/UPA/UpaParam.cs b/src/GlobalPayments.Api/Entities/UPA/UpaParam.cs new file mode 100644 index 00000000..34c29732 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/UPA/UpaParam.cs @@ -0,0 +1,13 @@ +using GlobalPayments.Api.Entities.Enums; + +namespace GlobalPayments.Api.Entities.UPA { + public class UpaParam { + public int Timeout { get; set; } + public AcquisitionType AcquisitionTypes { get; set; } + public string Header { get; set; } + public string DisplayTotalAmount { get; set; } + public bool PromptForManual { get; set; } + public int BrandIcon1 { get; set; } + public int BrandIcon2 { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/UPA/UpaTransactionData.cs b/src/GlobalPayments.Api/Entities/UPA/UpaTransactionData.cs new file mode 100644 index 00000000..0b7aaf81 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/UPA/UpaTransactionData.cs @@ -0,0 +1,11 @@ +using System; + +namespace GlobalPayments.Api.Entities.UPA { + public class UpaTransactionData { + public decimal TotalAmount { get; set; } + public decimal CashBackAmount { get; set; } + public DateTime TranDate { get; set; } + public DateTime TranTime { get; set; } + public TransactionType TransType { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Entities/User.cs b/src/GlobalPayments.Api/Entities/User.cs index d1589022..a5485e03 100644 --- a/src/GlobalPayments.Api/Entities/User.cs +++ b/src/GlobalPayments.Api/Entities/User.cs @@ -8,8 +8,7 @@ namespace GlobalPayments.Api.Entities { - public class User - { + public class User { /// /// This is a label to identify the user /// @@ -24,11 +23,11 @@ public class User /// The date and time the resource object was last changed. /// public DateTime? TimeLastUpdated { get; set; } - + public string Email { get; set; } - + public List
Addresses { get; set; } - + public PhoneNumber ContactPhone { get; set; } /// @@ -46,7 +45,11 @@ public class User public List PersonList { get; set; } - public List PaymentMethods { get; set; } + public List PaymentMethods { get; set; } + + public Document Document { get; set; } + + public FundsAccountDetails FundsAccountDetails { get; set; } /// /// Creates an `User` object from an existing user ID. @@ -75,5 +78,26 @@ public PayFacBuilder Edit() return builder; } + + public PayFacBuilder UploadDocument(DocumentUploadData data) + { + PayFacBuilder builder = new PayFacBuilder(TransactionType.UploadDocument) + .WithUserReference(this.UserReference) + .WithDocumentUploadData(data); + + if (UserReference.UserType != null) { + builder = builder.WithModifier(EnumConverter.FromDescription(UserReference.UserType.ToString())); + } + return builder; + } + + public PayFacBuilder AddFunds() + { + PayFacBuilder builder = new PayFacBuilder(TransactionType.AddFunds) + .WithUserReference(this.UserReference); + + return builder; + } + } } diff --git a/src/GlobalPayments.Api/Entities/UserAccount.cs b/src/GlobalPayments.Api/Entities/UserAccount.cs new file mode 100644 index 00000000..e1af16b1 --- /dev/null +++ b/src/GlobalPayments.Api/Entities/UserAccount.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Entities +{ + public class UserAccount + { + public string Id { get; set; } + public string Name { get; set; } + public string Type { get; set; } + + public UserAccount(string id, string name = null) { + Id = id; + Name = name; + } + } +} diff --git a/src/GlobalPayments.Api/Gateways/BillPay/Responses/BillPayResponseBase.cs b/src/GlobalPayments.Api/Gateways/BillPay/Responses/BillPayResponseBase.cs index 6ee61bcc..75573ba4 100644 --- a/src/GlobalPayments.Api/Gateways/BillPay/Responses/BillPayResponseBase.cs +++ b/src/GlobalPayments.Api/Gateways/BillPay/Responses/BillPayResponseBase.cs @@ -61,5 +61,24 @@ internal PaymentMethodType SetPaymentMethodType(string paymentMethod) { return paymentMethodType; } + + /// + /// Convert a string value cardType to a CardType enum value + /// + /// A string representing the card type + /// The enumeration value of the specified card type, if supported + internal string GetCardType(string cardType) + { + if (cardType.Contains("Visa")) + return CardType.VISA.ToString(); + else if (cardType.Contains("Mastercard")) + return CardType.MC.ToString(); + else if (cardType.Contains("Discover")) + return CardType.DISC.ToString(); + else if (cardType.Contains("AmericanExpress")) + return CardType.AMEX.ToString(); + else + return string.Empty; + } } } diff --git a/src/GlobalPayments.Api/Gateways/BillPay/Responses/TokenInformationRequestResponse.cs b/src/GlobalPayments.Api/Gateways/BillPay/Responses/TokenInformationRequestResponse.cs index 2e63eb5e..bc39e622 100644 --- a/src/GlobalPayments.Api/Gateways/BillPay/Responses/TokenInformationRequestResponse.cs +++ b/src/GlobalPayments.Api/Gateways/BillPay/Responses/TokenInformationRequestResponse.cs @@ -38,6 +38,7 @@ public override Transaction Map() { CardExpYear = tokenDetailsElement.GetValue("a:ExpirationYear"), CardLast4 = tokenDetailsElement.GetValue("a:Last4"), PaymentMethodType = SetPaymentMethodType(tokenDetailsElement.GetValue("a:PaymentMethod")), + CardType = GetCardType(tokenDetailsElement.GetValue("a:PaymentMethod")), Token = tokenDetailsElement.GetValue("a:Token"), TokenData = new TokenData { diff --git a/src/GlobalPayments.Api/Gateways/Gateway.cs b/src/GlobalPayments.Api/Gateways/Gateway.cs index 03ba9485..9e1cadba 100644 --- a/src/GlobalPayments.Api/Gateways/Gateway.cs +++ b/src/GlobalPayments.Api/Gateways/Gateway.cs @@ -59,6 +59,7 @@ private string GenerateRequestLog(HttpRequestMessage request, bool isXml = false protected GatewayResponse SendRequest(HttpMethod verb, string endpoint, string data = null, Dictionary queryStringParams = null, string contentType = null, bool isCharSet = true, bool isXml = false) { HttpClient httpClient = new HttpClient(HttpClientHandlerBuilder.Build(WebProxy)) { Timeout = TimeSpan.FromMilliseconds(Timeout) + }; var queryString = BuildQueryString(queryStringParams); diff --git a/src/GlobalPayments.Api/Gateways/GpApiConnector.cs b/src/GlobalPayments.Api/Gateways/GpApiConnector.cs index a1c756ed..22401ace 100644 --- a/src/GlobalPayments.Api/Gateways/GpApiConnector.cs +++ b/src/GlobalPayments.Api/Gateways/GpApiConnector.cs @@ -1,6 +1,7 @@ using GlobalPayments.Api.Builders; using GlobalPayments.Api.Builders.RequestBuilder.GpApi; using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Gateways.Interfaces; using GlobalPayments.Api.Logging; using GlobalPayments.Api.Mapping; using GlobalPayments.Api.PaymentMethods; @@ -12,7 +13,7 @@ using System.Reflection; namespace GlobalPayments.Api.Gateways { - internal partial class GpApiConnector : RestGateway, IPaymentGateway, IReportingService, ISecure3dProvider, IPayFacProvider, IFraudCheckService, IDeviceCloudService { + internal partial class GpApiConnector : RestGateway, IPaymentGateway, IReportingService, ISecure3dProvider, IPayFacProvider, IFraudCheckService, IDeviceCloudService, IFileProcessingService { private const string IDEMPOTENCY_HEADER = "x-gp-idempotency"; private string _AccessToken; @@ -89,6 +90,18 @@ public string ProcessPassThrough(JsonDoc rawRequest) { } #endregion + public FileProcessor ProcessFileUpload(FileProcessingBuilder builder) { + if (string.IsNullOrEmpty(AccessToken)) { + SignIn(); + } + var request = new GpApiFileProcessingRequestBuilder().BuildRequest(builder, this); + if (request != null) { + var response = DoTransaction(request.Verb, request.Endpoint, request.RequestBody, request.QueryStringParams); + return GpApiMapping.MapFileProcessingResponse(response); + } + return null; + } + public void SignIn() { AccessTokenInfo accessTokenInfo = GpApiConfig.AccessTokenInfo; @@ -130,6 +143,11 @@ public void SignIn() { string.IsNullOrEmpty(accessTokenInfo.MerchantManagementAccountID)) { accessTokenInfo.MerchantManagementAccountID = response.MerchantManagementAccountID; } + if (string.IsNullOrEmpty(accessTokenInfo.FileProcessingAccountName) && + string.IsNullOrEmpty(accessTokenInfo.FileProcessingAccountID)) + { + accessTokenInfo.FileProcessingAccountID = response.FileProcessingAccountID; + } GpApiConfig.AccessTokenInfo = accessTokenInfo; } diff --git a/src/GlobalPayments.Api/Gateways/GpEcomConnector.cs b/src/GlobalPayments.Api/Gateways/GpEcomConnector.cs index fce2b074..343c8587 100644 --- a/src/GlobalPayments.Api/Gateways/GpEcomConnector.cs +++ b/src/GlobalPayments.Api/Gateways/GpEcomConnector.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; using GlobalPayments.Api.Builders; using GlobalPayments.Api.Entities; @@ -114,6 +115,25 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { MaskedValues = ProtectSensitiveData.HideValue("request.card.cvn.number", card.Cvn); } + + // Card block + if (builder.CardTypesBlocking != null) { + var cardTypes = builder.CardTypesBlocking; + + var cardTypeBlock = et.SubElement(request, "blockcard"); + if (cardTypes.Commercialcredit.HasValue) { + et.SubElement(cardTypeBlock, "commercialcredit", cardTypes.Commercialcredit.ToString().ToLower()); + } + if (cardTypes.Commercialdebit.HasValue) { + et.SubElement(cardTypeBlock, "commercialdebit", cardTypes.Commercialdebit.ToString().ToLower()); + } + if (cardTypes.Consumercredit.HasValue) { + et.SubElement(cardTypeBlock, "consumercredit", cardTypes.Consumercredit.ToString().ToLower()); + } + if (cardTypes.Consumerdebit.HasValue) { + et.SubElement(cardTypeBlock, "consumerdebit", cardTypes.Consumerdebit.ToString().ToLower()); + } + } } string hash = string.Empty; @@ -534,18 +554,45 @@ public string SerializeRequest(AuthorizationBuilder builder) { if (builder.HostedPaymentData != null) { AlternativePaymentType[] PaymentTypes = builder.HostedPaymentData.PresetPaymentMethods; HostedPaymentMethods[] HostedPaymentMethods = builder.HostedPaymentData.HostedPaymentMethods; - if (PaymentTypes != null) { - PaymentValues = string.Join("|", PaymentTypes); + + if (HostedPaymentMethods != null) { + if (HostedPaymentMethods.ToList().Contains(Entities.HostedPaymentMethods.CARDS)) { + List cardItem = new List(); + List Items = new List(); + foreach (var hosted in HostedPaymentMethods) { + if (hosted == Entities.HostedPaymentMethods.CARDS) { + cardItem.Add(hosted); + } + else { + Items.Add(hosted); + } + } + if(cardItem.Count > 0) { + PaymentValues = string.Join("|", new string[] { string.Join("|", cardItem)}); + if(Items.Count > 0) { + PaymentValues = string.Join("|", new string[] { PaymentValues, string.Join("|", Items) }); + } + } + else { + PaymentValues = string.Join("|", Items); + } + } + else { + PaymentValues = string.Join("|", HostedPaymentMethods); + } } - if (HostedPaymentMethods != null) - { + if (PaymentTypes != null) { if (PaymentValues != null) { - PaymentValues = string.Join("|", new string[] { PaymentValues, string.Join("|", HostedPaymentMethods) }); + PaymentValues = string.Join("|", new string[] { PaymentValues, string.Join("|", PaymentTypes) }); } else { - PaymentValues = string.Join("|", HostedPaymentMethods); + PaymentValues = string.Join("|", PaymentTypes); } } + var blockCardTypes = builder.HostedPaymentData.BlockCardTypes?.ToList(); + if(blockCardTypes != null) { + request.Set("BLOCK_CARD_TYPE", string.Join("|", blockCardTypes.Select(x => EnumConverter.GetDescription(x)))) ; + } request.Set("CUST_NUM", builder.HostedPaymentData.CustomerNumber); if (HostedPaymentConfig.DisplaySavedCards.HasValue && builder.HostedPaymentData.CustomerKey != null) { request.Set("HPP_SELECT_STORED_CARD", builder.HostedPaymentData.CustomerKey); @@ -628,7 +675,9 @@ public string SerializeRequest(AuthorizationBuilder builder) { request.Set("HPP_BILLING_POSTALCODE", builder.BillingAddress.PostalCode); request.Set("HPP_BILLING_COUNTRY", CountryUtils.GetNumericCodeByCountry(builder.BillingAddress.Country)); } - request.Set("CUST_NUM", builder.CustomerId); + if (!request.Has("CUST_NUM")) { + request.Set("CUST_NUM", builder.CustomerId); + } request.Set("VAR_REF", builder.ClientTransactionId); request.Set("HPP_LANG", HostedPaymentConfig.Language); request.Set("MERCHANT_RESPONSE_URL", HostedPaymentConfig.ResponseUrl); @@ -702,7 +751,7 @@ public string SerializeRequest(AuthorizationBuilder builder) { if (builder.DynamicDescriptor != null) { request.Set("CHARGE_DESCRIPTION", builder.DynamicDescriptor); } - request.Set("SHA1HASH", GenerationUtils.GenerateHash(SharedSecret, toHash.ToArray())); + request.Set($"{ShaHashType.ToString()}HASH", GenerationUtils.GenerateHash(SharedSecret, ShaHashType, toHash.ToArray())); return request.ToString(); } public T ProcessReport(ReportBuilder builder) where T : class { @@ -1028,7 +1077,7 @@ private Transaction MapResponse(string rawResponse, TransactionBuilder("exchangeratesourcetimestamp"); if (!string.IsNullOrEmpty(exchangeTimestamp)) { - dccRateData.ExchangeRateSourceTimestamp = DateTime.ParseExact(exchangeTimestamp, "yyyyMMdd hh:mm", CultureInfo.InvariantCulture); + dccRateData.ExchangeRateSourceTimestamp = DateTime.ParseExact(exchangeTimestamp, "yyyyMMdd HH:mm", CultureInfo.InvariantCulture); } result.DccRateData = dccRateData; diff --git a/src/GlobalPayments.Api/Gateways/Interfaces/IFileProcessingService.cs b/src/GlobalPayments.Api/Gateways/Interfaces/IFileProcessingService.cs new file mode 100644 index 00000000..24bcafe9 --- /dev/null +++ b/src/GlobalPayments.Api/Gateways/Interfaces/IFileProcessingService.cs @@ -0,0 +1,12 @@ +using GlobalPayments.Api.Builders; +using GlobalPayments.Api.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Gateways.Interfaces +{ + public interface IFileProcessingService { + FileProcessor ProcessFileUpload(FileProcessingBuilder builder); + } +} diff --git a/src/GlobalPayments.Api/Gateways/PorticoConnector.cs b/src/GlobalPayments.Api/Gateways/PorticoConnector.cs index 4e562ae1..c33bfef2 100644 --- a/src/GlobalPayments.Api/Gateways/PorticoConnector.cs +++ b/src/GlobalPayments.Api/Gateways/PorticoConnector.cs @@ -22,7 +22,7 @@ internal class PorticoConnector : XmlGateway, IPaymentGateway, IReportingService public string UniqueDeviceId { get; set; } public string SDKNameVersion { get; set; } public bool SupportsOpenBanking => false; - public bool IsSafDataSupported { get; set; } + public bool? IsSafDataSupported = null; public PorticoConnector() { } @@ -32,8 +32,10 @@ public PorticoConnector() { public Transaction ProcessAuthorization(AuthorizationBuilder builder) { var et = new ElementTree(); + string transactionName = MapTransactionType(builder); + // build request - var transaction = et.Element(MapTransactionType(builder)); + var transaction = et.Element(transactionName); var block1 = et.SubElement(transaction, "Block1"); if (builder.TransactionType.HasFlag(TransactionType.Sale) || builder.TransactionType.HasFlag(TransactionType.Auth)) { if (builder.PaymentMethod.PaymentMethodType != PaymentMethodType.Gift && builder.PaymentMethod.PaymentMethodType != PaymentMethodType.ACH) { @@ -64,6 +66,8 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { && builder.PaymentMethod.PaymentMethodType != PaymentMethodType.Gift && builder.TransactionType != TransactionType.Tokenize && builder.TransactionType != TransactionType.Reversal + && transactionName != "CreditAdditionalAuth" + && transactionName != "CreditIncrementalAuth" ) { var isCheck = (builder.PaymentMethod.PaymentMethodType == PaymentMethodType.ACH); var holder = et.SubElement(block1, isCheck ? "ConsumerInfo" : "CardHolderData"); @@ -75,6 +79,10 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { et.SubElement(holder, isCheck ? "Zip" : "CardHolderZip", builder.BillingAddress.PostalCode); } + if (builder.CustomerData != null) { + et.SubElement(holder, isCheck ? "EmailAddress" : "CardHolderEmail", builder.CustomerData.Email); + } + if (isCheck) { var check = builder.PaymentMethod as eCheck; if (!string.IsNullOrEmpty(check.CheckHolderName)) { @@ -161,7 +169,7 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { et.SubElement(Secure3D, "ECI", secureEcom.Eci); et.SubElement(Secure3D, "DirectoryServerTxnId", secureEcom.Xid); } - if (IsAppleOrGooglePay(secureEcom.PaymentDataSource)) + if (IsAppleOrGooglePay(secureEcom.PaymentDataSource) && builder.TransactionType != TransactionType.Refund) { var WalletData = et.SubElement(block1, "WalletData"); et.SubElement(WalletData, "PaymentSource", secureEcom.PaymentDataSource); @@ -171,13 +179,13 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { } //WalletData Element if ((CreditCardData.MobileType == MobilePaymentMethodType.APPLEPAY || CreditCardData.MobileType == MobilePaymentMethodType.GOOGLEPAY) - && !string.IsNullOrEmpty(CreditCardData.PaymentSource) && IsAppleOrGooglePay(CreditCardData.PaymentSource)) + && !string.IsNullOrEmpty(CreditCardData.PaymentSource) && IsAppleOrGooglePay(CreditCardData.PaymentSource) && builder.TransactionType != TransactionType.Refund) { var WalletData = et.SubElement(block1, "WalletData"); et.SubElement(WalletData, "PaymentSource", CreditCardData.PaymentSource); et.SubElement(WalletData, "Cryptogram", CreditCardData.Cryptogram); et.SubElement(WalletData, "ECI", CreditCardData.Eci); - if (CreditCardData.MobileType != null) + if (CreditCardData.Token != null) { et.SubElement(WalletData, "DigitalPaymentToken", CreditCardData.Token); block1.Remove("CardData"); @@ -371,6 +379,14 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { } } + if (builder.CommercialData != null) + { + var cd = builder.CommercialData; + var cpc = et.SubElement(block1, "CPCData"); + et.SubElement(cpc, "CardHolderPONbr", cd.PoNumber); + et.SubElement(cpc, "TaxType", cd.TaxType.ToString()); + et.SubElement(cpc, "TaxAmt", cd.TaxAmount); + } // dynamic descriptor et.SubElement(block1, "TxnDescriptor", builder.DynamicDescriptor); @@ -522,8 +538,8 @@ public Transaction ManageTransaction(ManagementBuilder builder) { et.SubElement(data, "DestinationCountryCode", cd.DestinationCountryCode); et.SubElement(data, "InvoiceRefNbr", cd.VAT_InvoiceNumber); et.SubElement(data, "OrderDate", cd.OrderDate?.ToString("yyyy-MM-ddTHH:mm:ss.FFFK")); - et.SubElement(data, "VATTaxAmtFreight", cd.AdditionalTaxDetails?.TaxAmount ?? cd.TaxAmount); - et.SubElement(data, "VATTaxRateFreight", cd.AdditionalTaxDetails?.TaxRate); + et.SubElement(data, "VATTaxAmtFreight", cd.VATTaxAmtFreight ?? cd.AdditionalTaxDetails?.TaxAmount ?? cd.TaxAmount); + et.SubElement(data, "VATTaxRateFreight", cd.VATTaxRateFreight ?? cd.AdditionalTaxDetails?.TaxRate ); // et.SubElement(data, "TaxTreatment", null); // et.SubElement(data, "DiscountTreatment", null); } @@ -581,6 +597,17 @@ public Transaction ManageTransaction(ManagementBuilder builder) { } } } + else + { + if (builder.TransactionType == TransactionType.BatchClose) + { + if (builder.batchDeviceId != null) + { + var batchDeviceId = builder.batchDeviceId.ToString(); + transaction.Set("deviceId", batchDeviceId); + } + } + } var response = DoTransaction(BuildEnvelope(et, transaction, builder.ClientTransactionId)); return MapResponse(response, builder.PaymentMethod); @@ -666,9 +693,12 @@ private string BuildEnvelope(ElementTree et, Element transaction, string clientT et.SubElement(header, "UniqueDeviceId", UniqueDeviceId); et.SubElement(header, "SDKNameVersion", SDKNameVersion != null ? SDKNameVersion : "net;version=" + getReleaseVersion()); - Element safData = et.SubElement(header, "SAFData"); - et.SubElement(safData, "SAFIndicator", IsSafDataSupported ? "Y" : "N"); - et.SubElement(safData, "SAFOrigDT", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.FFFK")); + + if (IsSafDataSupported != null) { + Element safData = et.SubElement(header, "SAFData"); + et.SubElement(safData, "SAFIndicator", (bool)IsSafDataSupported ? "Y" : "N"); + et.SubElement(safData, "SAFOrigDT", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.FFFK")); + } // Transaction var trans = et.SubElement(version1, "Transaction"); @@ -809,8 +839,11 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where var doc = new ElementTree(rawResponse).Get(MapReportType(reportType)); T rvalue = Activator.CreateInstance(); - if (reportType.HasFlag(ReportType.FindTransactions) | reportType.HasFlag(ReportType.Activity)) { - Func hydrateTransactionSummary = (root) => { + if (reportType.HasFlag(ReportType.FindTransactions) | reportType.HasFlag(ReportType.Activity)) { + Func hydrateTransactionSummary = (root) => + { + var headerElement = response.Get("Header"); + var summary = new TransactionSummary { AccountDataSource = root.GetValue("AcctDataSrc"), Amount = root.GetValue("Amt"), @@ -835,7 +868,7 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where PaymentType = root.GetValue("PaymentType"), PoNumber = root.GetValue("CardHolderPONbr"), ReferenceNumber = root.GetValue("RefNbr"), - ResponseDate = root.GetValue("RspDT"), + ResponseDate = headerElement.GetValue("RspDT"), ServiceName = root.GetValue("ServiceName"), SettlementAmount = root.GetValue("SettlementAmt"), ShippingAmount = root.GetValue("ShippingAmtInfo"), @@ -843,7 +876,7 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where Status = root.GetValue("Status", "TxnStatus"), TaxAmount = root.GetValue("TaxAmt", "TaxAmtInfo"), TaxType = root.GetValue("TaxType"), - TransactionDate = root.GetValue("TxnUtcDT", "ReqUtcDT"), + TransactionDate = root.GetValue("RspDT"), TransactionId = root.GetValue("GatewayTxnId"), TransactionStatus = root.GetValue("TxnStatus"), Username = root.GetValue("UserName"), @@ -939,7 +972,11 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where } if (reportType.HasFlag(ReportType.TransactionDetail)) { + var reportDetailsElement = response.Get("ReportTxnDetail"); + var summary = new TransactionSummary { + ResponseDate = response.GetValue("RspDT"), + TransactionDate = reportDetailsElement.GetValue("RspDT"), TransactionId = response.GetValue("GatewayTxnId"), SiteId = response.GetValue("SiteId"), MerchantName = response.GetValue("MerchName"), @@ -954,6 +991,7 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where MerchantAddr1 = response.GetValue("MerchAddr1"), MerchantAddr2 = response.GetValue("MerchAddr2"), MerchantCity = response.GetValue("MerchCity"), + MerchantState = response.GetValue("MerchState"), MerchantZip = response.GetValue("MerchZip"), MerchantPhone = response.GetValue("MerchPhone"), TransactionStatus = response.GetValue("TxnStatus"), @@ -970,14 +1008,92 @@ private T MapReportResponse(string rawResponse, ReportType reportType) where CashBackAmount = response.GetValue("CashbackAmtInfo"), CardHolderFirstName = response.GetValue("CardHolderFirstName"), CardHolderLastName = response.GetValue("CardHolderLastName"), + Email = response.GetValue("CardHolderEmail"), ConvenienceAmount = response.GetValue("ConvenienceAmtInfo"), IssuerResponseCode = response.GetValue("IssuerRspCode", "RspCode"), IssuerResponseMessage = response.GetValue("IssuerRspText", "RspText"), IssuerTransactionId = response.GetValue("IssTxnId"), SDKNameVersion = response.GetValue("SDKNameVersion"), - InvoiceNumber = response.GetValue("InvoiceNbr"), - + InvoiceNumber = response.GetValue("InvoiceNbr"), // not in the raw response + ShippingInvoiceNbr = response.GetValue("DirectMktInvoiceNbr"), + ShippingDay = response.GetValue("DirectMktShipDay"), + ShippingMonth = response.GetValue("DirectMktShipMonth"), + TaxAmount = response.GetValue("TaxAmt", "TaxAmtInfo", "CPCTaxAmt"), + SurchargeAmount = response.GetValue("SurchargeAmtInfo"), + Currency = response.GetValue("MerchCurrencyText"), + TaxType = response.GetValue("CPCTaxType"), + PoNumber = response.GetValue("CPCCardHolderPONbr"), }; + if (response.Has("CorporateData") && response.Has("CPCTaxType")) + { + + bool ignoreCase = true; + string taxType = response.GetValue("CPCTaxType").Substring(response.GetValue("CPCTaxType").IndexOf('-') + 1, response.GetValue("CPCTaxType").Length-(response.GetValue("CPCTaxType").IndexOf('-') + 1)); + if(response.Has("Visa")) + { + CommercialData commercialData = new CommercialData((TaxType)Enum.Parse(typeof(TaxType), taxType,ignoreCase)) + { + SummaryCommodityCode = response.GetValue("SummaryCommodityCode"), + FreightAmount = response.GetValue("FreightAmt"), + DutyAmount = response.GetValue("DutyAmt"), + DestinationPostalCode = response.GetValue("DestinationPostalZipCode"), + OriginPostalCode = response.GetValue("ShipFromPostalZipCode"), + DestinationCountryCode = response.GetValue("DestinationCountryCode"), + VAT_InvoiceNumber = response.GetValue("InvoiceRefNbr"), + OrderDate = response.GetValue("OrderDate"), + PoNumber = response.GetValue("CPCCardHolderPONbr"), + }; + summary.CommercialData = commercialData; + + foreach (var lineItemDetail in response.GetAll("LineItemDetail")) + { + var lid = new CommercialLineItem + { + Description = lineItemDetail.GetValue("ItemDescription"), + ProductCode = lineItemDetail.GetValue("ProductCode"), + Quantity = lineItemDetail.GetValue("Quantity"), + UnitOfMeasure = lineItemDetail.GetValue("UnitOfMeasure"), + DiscountDetails = new DiscountDetails + { + DiscountAmount = lineItemDetail.GetValue("DiscountAmt"), + }, + }; + + summary.CommercialData.AddLineItems(lid); + } + } + else //Mastercard + { + CommercialData commercialData = new CommercialData((TaxType)System.Enum.Parse(typeof(TaxType), taxType, ignoreCase)) + { + SummaryCommodityCode = response.GetValue("SummaryCommodityCode"), + FreightAmount = response.GetValue("FreightAmt"), + DutyAmount = response.GetValue("DutyAmt"), + DestinationPostalCode = response.GetValue("DestinationPostalZipCode"), + OriginPostalCode = response.GetValue("ShipFromPostalZipCode"), + DestinationCountryCode = response.GetValue("DestinationCountryCode"), + VAT_InvoiceNumber = response.GetValue("InvoiceRefNbr"), + OrderDate = response.GetValue("OrderDate"), + PoNumber = response.GetValue("CPCCardHolderPONbr"), + + }; + summary.CommercialData = commercialData; + + foreach (var lineItemDetail in response.GetAll("LineItemDetail")) + { + var lid = new CommercialLineItem + { + Description = lineItemDetail.GetValue("ItemDescription"), + ProductCode = lineItemDetail.GetValue("ProductCode"), + Quantity = lineItemDetail.GetValue("Quantity"), + UnitOfMeasure = lineItemDetail.GetValue("UnitOfMeasure"), + + }; + summary.CommercialData.AddLineItems(lid); + } + } + + } rvalue = summary as T; return rvalue; } @@ -1226,30 +1342,36 @@ private void BuildLineItems(ElementTree et, Element parent, bool isVisa, List("enabled"), - LimitRemaining = root.GetValue("limitRemaining"), - TransferFee = root.GetValue("transferFee"), - FeeType = root.GetValue("feeType"), - AccountLastFour = root.GetValue("accountLastFour") + return new AccountBalanceResponseData() { + Enabled = root.Get("achOut").GetValue("enabled"), + LimitRemaining = root.Get("achOut").GetValue("limitRemaining"), + TransferFee = root.Get("achOut").GetValue("transferFee"), + FeeType = root.Get("achOut").GetValue("feeType"), + AccountLastFour = root.Get("achOut").GetValue("accountLastFour") }; } return null; @@ -357,13 +356,12 @@ private AccountBalanceResponseData GetACHOutBalanceInfoFromResponse(Element root private AccountBalanceResponseData GetFlashFundsBalanceInfoFromResponse(Element root) { if (root.Has("flashFunds")) { - return new AccountBalanceResponseData() - { - Enabled = root.GetValue("enabled"), - LimitRemaining = root.GetValue("limitRemaining"), - TransferFee = root.GetValue("transferFee"), - FeeType = root.GetValue("feeType"), - AccountLastFour = root.GetValue("accountLastFour") + return new AccountBalanceResponseData() { + Enabled = root.Get("flashFunds").GetValue("enabled"), + LimitRemaining = root.Get("flashFunds").GetValue("limitRemaining"), + TransferFee = root.Get("flashFunds").GetValue("transferFee"), + FeeType = root.Get("flashFunds").GetValue("feeType"), + AccountLastFour = root.Get("flashFunds").GetValue("accountLastFour") }; } return null; @@ -704,8 +702,8 @@ private void HydrateDocumentUploadData(ElementTree xml, Element xmlTrans,Transac xml.SubElement(xmlTrans, docNameTag, docUploadData.DocumentName); xml.SubElement(xmlTrans, "TransactionReference", docUploadData.TransactionReference); - xml.SubElement(xmlTrans, "DocCategory", docUploadData.DocCategory); - xml.SubElement(xmlTrans, docTypeTag, docUploadData.DocType); + xml.SubElement(xmlTrans, "DocCategory", docUploadData.DocCategory.ToString().ToLower()); + xml.SubElement(xmlTrans, docTypeTag, docUploadData.DocType.ToString().ToLower()); xml.SubElement(xmlTrans, "Document", docUploadData.Document); } diff --git a/src/GlobalPayments.Api/Gateways/TransitConnector.cs b/src/GlobalPayments.Api/Gateways/TransitConnector.cs index f41c1850..4c06c60b 100644 --- a/src/GlobalPayments.Api/Gateways/TransitConnector.cs +++ b/src/GlobalPayments.Api/Gateways/TransitConnector.cs @@ -55,7 +55,7 @@ public Transaction ProcessAuthorization(AuthorizationBuilder builder) { .Set("developerID", DeveloperId) .Set("deviceID", DeviceId) .Set("transactionKey", TransactionKey) - .Set("transactionAmount", builder.Amount.ToCurrencyString()) + .Set("transactionAmount", builder.Amount.ToCurrencyString(true)) .Set("tokenRequired", builder.RequestMultiUseToken ? "Y" : "N") .Set("externalReferenceID", builder.ClientTransactionId); @@ -202,7 +202,7 @@ public Transaction ManageTransaction(ManagementBuilder builder) { .Set("developerID", DeveloperId) .Set("deviceID", DeviceId) .Set("transactionKey", TransactionKey) - .Set("transactionAmount", builder.Amount.ToCurrencyString()) + .Set("transactionAmount", builder.Amount.ToCurrencyString(true)) .Set("tip", builder.Gratuity.ToCurrencyString()) .Set("transactionID", builder.TransactionId) .Set("isPartialShipment", builder.MultiCapture ? "Y" : null) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index 120695d9..9175121b 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -36,6 +36,24 @@ ..\..\..\Certificates\BlueBridge.snk + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + 7.3 + prompt + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + @@ -51,17 +69,20 @@ + + + @@ -86,6 +107,7 @@ + @@ -101,10 +123,12 @@ + + @@ -113,8 +137,10 @@ + + @@ -132,12 +158,16 @@ + + + + @@ -146,6 +176,7 @@ + @@ -166,10 +197,13 @@ + + + @@ -198,10 +232,13 @@ + + + @@ -214,6 +251,7 @@ + @@ -298,8 +336,11 @@ - + + + + @@ -364,6 +405,7 @@ + @@ -560,8 +602,10 @@ + + @@ -574,6 +618,7 @@ + @@ -586,7 +631,11 @@ + + + + @@ -599,13 +648,32 @@ + + + + + + + + + + + + + + + + + + + @@ -624,12 +692,14 @@ + + @@ -641,6 +711,10 @@ + + + + @@ -665,15 +739,20 @@ + + + + + diff --git a/src/GlobalPayments.Api/Mapping/GpApiMapping.cs b/src/GlobalPayments.Api/Mapping/GpApiMapping.cs index dcd73055..7a906135 100644 --- a/src/GlobalPayments.Api/Mapping/GpApiMapping.cs +++ b/src/GlobalPayments.Api/Mapping/GpApiMapping.cs @@ -25,6 +25,10 @@ public class GpApiMapping { private const string ADDRESS_LIST = "ADDRESS_LIST"; private const string SPLIT = "SPLIT"; private const string TRANSFER = "TRANSFER"; + private const string DOCUMENT_UPLOAD = "DOCUMENT_UPLOAD"; + private const string FUNDS = "FUNDS"; + private const string FILE_CREATE = "FILE_CREATE"; + private const string FILE_SINGLE = "FILE_SINGLE"; public static Transaction MapResponse(string rawResponse) { Transaction transaction = new Transaction(); @@ -175,10 +179,10 @@ public static Transaction MapResponse(string rawResponse) { return transaction; } - private static List MapTransferFundsAccountDetails(JsonDoc json) { - var transferResponse = new List(); + private static List MapTransferFundsAccountDetails(JsonDoc json) { + var transferResponse = new List(); foreach (var item in json.GetArray("transfers") ?? Enumerable.Empty()) { - var transfer = new TransferFundsAccountDetails(); + var transfer = new FundsAccountDetails(); transfer.Id = item.GetValue("id"); transfer.Status = item.GetValue("status"); transfer.TimeCreated = item.GetValue("time_created"); @@ -231,8 +235,15 @@ public static Transaction MapResponseAPM(string rawResponse) JsonDoc json = JsonDoc.Parse(rawResponse); var paymentMethodApm = json.Get("payment_method")?.Get("apm"); - apm.RedirectUrl = json.Get("payment_method")?.GetValue("redirect_url"); - apm.ProviderName = paymentMethodApm?.GetValue("provider"); + apm.RedirectUrl = json.Get("payment_method")?.GetValue("redirect_url") ?? (paymentMethodApm.GetValue("redirect_url") ?? null); + if (paymentMethodApm.Has("provider")) { + apm.ProviderName = paymentMethodApm?.Get("provider")?.GetValue("name") ?? paymentMethodApm?.GetValue("provider") ?? null; + apm.ProviderReference = paymentMethodApm?.Get("provider")?.GetValue("merchant_identifier") ?? null; + apm.TimeCreatedReference = paymentMethodApm?.Get("provider")?.GetValue("time_created_reference") ?? null; + } + else { + apm.ProviderName = paymentMethodApm?.GetValue("provider"); + } apm.Ack = paymentMethodApm?.GetValue("ack"); apm.SessionToken = paymentMethodApm?.GetValue("session_token"); apm.CorrelationReference = paymentMethodApm?.GetValue("correlation_reference"); @@ -363,11 +374,13 @@ public static TransactionSummary MapTransactionSummary(JsonDoc doc) summary.PaymentType = EnumConverter.GetMapping(Target.GP_API, PaymentMethodName.BankPayment); var bankPaymentResponse = new BankPaymentResponse(); bankPaymentResponse.Iban = bankTransfer?.GetValue("iban"); + bankPaymentResponse.MaskedIbanLast4 = bankTransfer?.GetValue("masked_iban_last4"); bankPaymentResponse.AccountNumber = bankTransfer?.GetValue("account_number"); bankPaymentResponse.AccountName = bankTransfer?.Get("bank").GetValue("name"); bankPaymentResponse.SortCode = bankTransfer?.Get("bank").GetValue("code"); bankPaymentResponse.RemittanceReferenceValue = bankTransfer?.Get("remittance_reference")?.GetValue("value"); bankPaymentResponse.RemittanceReferenceType = bankTransfer?.Get("remittance_reference")?.GetValue("type"); + summary.AccountNumberLast4 = bankTransfer?.GetValue("masked_account_number_last4"); summary.BankPaymentResponse = bankPaymentResponse; } else { @@ -1012,6 +1025,24 @@ public static T MapMerchantEndpointResponse(string rawResponse) where T : cla JsonDoc json = JsonDoc.Parse(rawResponse); string actionType = json.Get("action")?.GetValue("type"); switch (actionType) { + case DOCUMENT_UPLOAD: + var userDoc = new User(); + userDoc.UserReference = new UserReference(); + userDoc.UserReference.UserId = json.GetValue("merchant_id") ?? null; + userDoc.UserReference.UserType = UserType.MERCHANT; + userDoc.Name = json.GetValue("merchant_name") ?? null; + var doc = new Document(); + doc.Name = json.GetValue("name"); + doc.Id = json.GetValue("id"); + doc.Status = json.GetValue("status"); + doc.TimeCreated = json.GetValue("time_created"); + doc.Format = (FileType)Enum.Parse(typeof(FileType),json.GetValue("format")); + doc.Category = (DocumentCategory)Enum.Parse(typeof(DocumentCategory), json.GetValue("function")); + userDoc.Document = doc; + if (json.Has("action")) { + userDoc.ResponseCode = json.Get("action").GetValue("result_code") ?? null; + } + return userDoc as T; case MERCHANT_CREATE: case MERCHANT_EDIT: case MERCHANT_EDIT_INITIATED: @@ -1056,6 +1087,25 @@ public static T MapMerchantEndpointResponse(string rawResponse) where T : cla } return user as T; + case FUNDS: + var userFunds = new User(); + userFunds.UserReference = new UserReference(); + userFunds.UserReference.UserId = json.GetValue("merchant_id"); + userFunds.Name = json.GetValue("merchant_name"); + var funds = new FundsAccountDetails(); + funds.Id = json.GetValue("id"); + funds.TimeCreated = json.GetValue("time_created"); + funds.TimeLastUpdated = json.GetValue("time_last_updated") ?? null; + funds.PaymentMethodType = json.GetValue("type") ?? null; + funds.PaymentMethodName = json.GetValue("payment_method") ?? null; + funds.Status = json.GetValue("status"); + funds.Amount = json.GetValue("amount"); + funds.Currency = json.GetValue("currency") ?? null; + funds.Account = new UserAccount(json.GetValue("account_id"), json.GetValue("account_name")); + userFunds.FundsAccountDetails = funds; + userFunds.ResponseCode = json.Get("action").GetValue("result_code"); + + return userFunds as T; default: throw new UnsupportedTransactionException("Unknown transaction type " + actionType); @@ -1065,6 +1115,49 @@ public static T MapMerchantEndpointResponse(string rawResponse) where T : cla return result; } + public static FileProcessor MapFileProcessingResponse(string rawResponse) { + var fp = new FileProcessor(); + if (!string.IsNullOrEmpty(rawResponse)) { + JsonDoc json = JsonDoc.Parse(rawResponse); + string actionType = json.Get("action")?.GetValue("type"); + switch (actionType) { + case FILE_CREATE: + MapGeneralFileProcessingResponse(json, fp); + fp.CreatedDate = json.GetValue("time_created"); + fp.UploadUrl = json.GetValue("url"); + fp.ExpirationDate = json.GetValue("expiration_date"); + break; + case FILE_SINGLE: + MapGeneralFileProcessingResponse(json, fp); + if (json.Has("response_files")) { + fp.FilesUploaded = new List(); + foreach (var responseFile in json.GetArray("response_files") ?? Enumerable.Empty()) { + var file = new FileUploaded(); + file.Url = responseFile.GetValue("url"); + file.TimeCreated = responseFile.GetValue("time_created"); + file.ExpirationDate = responseFile.GetValue("expiration_date"); + file.FileName = responseFile.GetValue("name"); + file.FileId = responseFile.GetValue("response_file_id"); + fp.FilesUploaded.Add(file); + } + } + break; + default: + throw new UnsupportedTransactionException($"Unknown action type {actionType}"); + } + } + + return fp; + } + + private static void MapGeneralFileProcessingResponse(JsonDoc json, FileProcessor fp) + { + fp.ResourceId = json.GetValue("id"); + fp.Status = fp.ResponseMessage = json.GetValue("status"); + fp.TotalRecordCount = json.GetValue("total_record_count") ?? null; + fp.ResponseCode = json.Get("action")?.GetValue("result_code"); + } + private static List MapMerchantPaymentMethod(JsonDoc json) { List merchantPaymentList = new List(); diff --git a/src/GlobalPayments.Api/PaymentMethods/AlternativePaymentMethod.cs b/src/GlobalPayments.Api/PaymentMethods/AlternativePaymentMethod.cs index a5d4ff64..ab3e000b 100644 --- a/src/GlobalPayments.Api/PaymentMethods/AlternativePaymentMethod.cs +++ b/src/GlobalPayments.Api/PaymentMethods/AlternativePaymentMethod.cs @@ -64,6 +64,7 @@ public class AlternativePaymentMethod: IPaymentMethod, IChargable, INotification /// AuthorizationBuilder public AuthorizationBuilder Charge(decimal? amount = null) { return new AuthorizationBuilder(TransactionType.Sale, this) + .WithModifier(TransactionModifier.AlternativePaymentMethod) .WithAmount(amount); } diff --git a/src/GlobalPayments.Api/PaymentMethods/TransactionReference.cs b/src/GlobalPayments.Api/PaymentMethods/TransactionReference.cs index 4c6062c9..fd1fb64b 100644 --- a/src/GlobalPayments.Api/PaymentMethods/TransactionReference.cs +++ b/src/GlobalPayments.Api/PaymentMethods/TransactionReference.cs @@ -20,7 +20,7 @@ public PaymentMethodType PaymentMethodType { public string CardType { get; set; } public string OrderId { get; set; } public string TransactionId { get; set; } - public List TransfersFundsAccounts { get; set; } + public List TransfersFundsAccounts { get; set; } public string ClientTransactionId { get; set; } public string AlternativePaymentType { get; set; } public string AcquiringInstitutionId{ get; set; } diff --git a/src/GlobalPayments.Api/Properties/AssemblyInfo.cs b/src/GlobalPayments.Api/Properties/AssemblyInfo.cs index a73e8dd7..4d225dd9 100644 --- a/src/GlobalPayments.Api/Properties/AssemblyInfo.cs +++ b/src/GlobalPayments.Api/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.2")] -[assembly: AssemblyFileVersion("6.0.2")] +[assembly: AssemblyVersion("8.0.3")] +[assembly: AssemblyFileVersion("8.0.3")] diff --git a/src/GlobalPayments.Api/ServiceConfigs/Gateways/GpApiConfig.cs b/src/GlobalPayments.Api/ServiceConfigs/Gateways/GpApiConfig.cs index b2727d71..dc74662e 100644 --- a/src/GlobalPayments.Api/ServiceConfigs/Gateways/GpApiConfig.cs +++ b/src/GlobalPayments.Api/ServiceConfigs/Gateways/GpApiConfig.cs @@ -62,6 +62,8 @@ public class GpApiConfig : GatewayConfig { public string MerchantId { get; set; } + public string StatusUrl { get; set; } + public Dictionary DynamicHeaders { get; set; } public GpApiConfig() : base(GatewayProvider.GP_API) { } @@ -97,6 +99,7 @@ internal override void ConfigureContainer(ConfiguredServices services) { MerchantId = MerchantId, AccessTokenInfo = AccessTokenInfo, Environment = Environment, + StatusUrl = StatusUrl }; var gateway = new GpApiConnector(gpApiConfig); @@ -107,6 +110,7 @@ internal override void ConfigureContainer(ConfiguredServices services) { services.GatewayConnector = gateway; services.ReportingService = gateway; services.FraudService = gateway; + services.FileProcessingService = gateway; services.SetPayFacProvider(gateway); services.SetSecure3dProvider(Secure3dVersion.One, gateway); diff --git a/src/GlobalPayments.Api/ServiceConfigs/Gateways/PorticoConfig.cs b/src/GlobalPayments.Api/ServiceConfigs/Gateways/PorticoConfig.cs index 70e7673e..a705c026 100644 --- a/src/GlobalPayments.Api/ServiceConfigs/Gateways/PorticoConfig.cs +++ b/src/GlobalPayments.Api/ServiceConfigs/Gateways/PorticoConfig.cs @@ -84,7 +84,7 @@ public class PorticoConfig : GatewayConfig { /// If false, use the Canadian ProPay endpoints /// public bool ProPayUS { get; set; } = true; - public bool IsSafDataSupported { get; set; } + public bool? IsSafDataSupported { get; set; } = null; private string PayPlanEndpoint { get { if ( diff --git a/src/GlobalPayments.Api/Services/CreditService.cs b/src/GlobalPayments.Api/Services/CreditService.cs index acb52a80..0af1072d 100644 --- a/src/GlobalPayments.Api/Services/CreditService.cs +++ b/src/GlobalPayments.Api/Services/CreditService.cs @@ -59,5 +59,16 @@ public ManagementBuilder Void(string transactionId = null) { TransactionId = transactionId }); } + + public AuthorizationBuilder AdditionalAuth(string transactionId = null) + { + return new AuthorizationBuilder(TransactionType.Auth) + .WithModifier(TransactionModifier.Additional) + .WithPaymentMethod(new TransactionReference + { + PaymentMethodType = PaymentMethodType.Credit, + TransactionId = transactionId + }); + } } } diff --git a/src/GlobalPayments.Api/Services/DeviceCloudService.cs b/src/GlobalPayments.Api/Services/DeviceCloudService.cs new file mode 100644 index 00000000..c61f2733 --- /dev/null +++ b/src/GlobalPayments.Api/Services/DeviceCloudService.cs @@ -0,0 +1,35 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Diamond.Responses; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Services { + public class DeviceCloudService { + private ConnectionConfig Config; + + public DeviceCloudService(ConnectionConfig config) { + this.Config = config; + ServicesContainer.ConfigureService(config); + } + + public ITerminalResponse ParseResponse(string response) { + if (string.IsNullOrEmpty(response)) { + throw new ApiException("Enable to parse : empty response"); + } + if (!JsonDoc.IsJson(response)) { + throw new ApiException("Unexpected response format!"); + } + + switch (this.Config.ConnectionMode) { + case ConnectionModes.DIAMOND_CLOUD: + return new DiamondCloudResponse(response); + default: + throw new UnsupportedTransactionException("The selected gateway does not support this response type!"); + } + } + } +} diff --git a/src/GlobalPayments.Api/Services/DeviceService.cs b/src/GlobalPayments.Api/Services/DeviceService.cs index 41ab9a69..8a53b171 100644 --- a/src/GlobalPayments.Api/Services/DeviceService.cs +++ b/src/GlobalPayments.Api/Services/DeviceService.cs @@ -9,5 +9,10 @@ public static IDeviceInterface Create(ConnectionConfig config, string configName } return ServicesContainer.Instance.GetDeviceInterface(configName); } + + public static IDeviceInterface FindDeviceController(string configName = "default") + { + return ServicesContainer.Instance.GetDeviceInterface(configName); + } } } diff --git a/src/GlobalPayments.Api/Services/FileProcessingService.cs b/src/GlobalPayments.Api/Services/FileProcessingService.cs new file mode 100644 index 00000000..6a3ffa71 --- /dev/null +++ b/src/GlobalPayments.Api/Services/FileProcessingService.cs @@ -0,0 +1,21 @@ +using GlobalPayments.Api.Builders; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Services { + public class FileProcessingService { + public FileProcessor Initiate() { + return new FileProcessingBuilder(FileProcessingActionType.CREATE_UPLOAD_URL) + .Execute(); + } + + public FileProcessor GetDetails(string resourceId) { + return new FileProcessingBuilder(FileProcessingActionType.GET_DETAILS) + .WithResourceId(resourceId) + .Execute(); + } + } +} diff --git a/src/GlobalPayments.Api/Services/GpApiService.cs b/src/GlobalPayments.Api/Services/GpApiService.cs index 41769de5..ba4dce38 100644 --- a/src/GlobalPayments.Api/Services/GpApiService.cs +++ b/src/GlobalPayments.Api/Services/GpApiService.cs @@ -27,12 +27,14 @@ public static AccessTokenInfo GenerateTransactionKey(GpApiConfig gpApiConfig) { TransactionProcessingAccountName = data.TransactionProcessingAccountName, RiskAssessmentAccountName = data.RiskAssessmentAccountName, MerchantManagementAccountName = data.MerchantManagementAccountName, + FileProcessingAccountName = data.FileProcessingAccountName, DataAccountID = data.DataAccountID, DisputeManagementAccountID = data.DisputeManagementAccountID, TokenizationAccountID = data.TokenizationAccountID, TransactionProcessingAccountID = data.TransactionProcessingAccountID, RiskAssessmentAccountID = data.RiskAssessmentAccountID, - MerchantManagementAccountID = data.MerchantManagementAccountID + MerchantManagementAccountID = data.MerchantManagementAccountID, + FileProcessingAccountID = data.FileProcessingAccountID }; } } diff --git a/src/GlobalPayments.Api/Services/HostedService.cs b/src/GlobalPayments.Api/Services/HostedService.cs index e28ea950..84aefc69 100644 --- a/src/GlobalPayments.Api/Services/HostedService.cs +++ b/src/GlobalPayments.Api/Services/HostedService.cs @@ -28,15 +28,20 @@ public AuthorizationBuilder Verify(decimal? amount = null) { public Transaction ParseResponse(string json, bool encoded = false) { var response = JsonDoc.Parse(json, encoded ? JsonEncoders.Base64Encoder : null); + if (response.Has("MERCHANT_RESPONSE_URL")) { + MapTransactionStatusResponse(ref response); + } + var timestamp = response.GetValue("TIMESTAMP"); var merchantId = response.GetValue("MERCHANT_ID"); var orderId = response.GetValue("ORDER_ID"); var result = response.GetValue("RESULT"); var message = response.GetValue("MESSAGE"); var transactionId = response.GetValue("PASREF"); - var authCode = response.GetValue("AUTHCODE"); + var authCode = response.GetValue("AUTHCODE") ?? null; + var paymentMethod = response.GetValue("PAYMENTMETHOD") ?? null; var sha1Hash = response.GetValue("SHA1HASH"); - var hash = GenerationUtils.GenerateHash(_config.SharedSecret, timestamp, merchantId, orderId, result, message, transactionId, authCode); + var hash = GenerationUtils.GenerateHash(_config.SharedSecret, timestamp, merchantId, orderId, result, message, transactionId, response.Has("MERCHANT_RESPONSE_URL") ? paymentMethod != null ? paymentMethod : authCode : authCode); if (!hash.Equals(sha1Hash)) throw new ApiException("Incorrect hash. Please check your code and the Developers Documentation."); @@ -48,22 +53,57 @@ public Transaction ParseResponse(string json, bool encoded = false) { rvalues.Add(key, value); } - return new Transaction { - AuthorizedAmount = response.GetValue("AMOUNT"), - AutoSettleFlag = response.GetValue("AUTO_SETTLE_FLAG"), - CvnResponseCode = response.GetValue("CVNRESULT"), - ResponseCode = result, - ResponseMessage = message, - AvsResponseCode = response.GetValue("AVSPOSTCODERESULT"), - Timestamp = timestamp, - TransactionReference = new TransactionReference { - AuthCode = authCode, - OrderId = orderId, - PaymentMethodType = PaymentMethodType.Credit, - TransactionId = transactionId - }, - ResponseValues = rvalues + var transaction = new Transaction(); + transaction.AuthorizedAmount = response.GetValue("AMOUNT"); + transaction.AutoSettleFlag = response.GetValue("AUTO_SETTLE_FLAG"); + transaction.CvnResponseCode = response.GetValue("CVNRESULT"); + transaction.ResponseCode = result; + transaction.ResponseMessage = message; + transaction.AvsResponseCode = response.GetValue("AVSPOSTCODERESULT"); + transaction.Timestamp = timestamp; + transaction.TransactionReference = new TransactionReference { + AuthCode = authCode, + OrderId = orderId, + PaymentMethodType = response.Has("PAYMENTMETHOD") ? PaymentMethodType.APM : PaymentMethodType.Credit, + TransactionId = transactionId }; + transaction.ResponseValues = rvalues; + + if (response.Has("PAYMENTMETHOD")) { + var apm = new AlternativePaymentResponse(); + apm.Country = response.GetValue("COUNTRY") ?? ""; + apm.ProviderName = response.GetValue("PAYMENTMETHOD"); + apm.PaymentStatus = response.GetValue("TRANSACTION_STATUS") ?? null; + apm.ReasonCode = response.GetValue("PAYMENT_PURPOSE") ?? null; + apm.AccountHolderName = response.GetValue("ACCOUNT_HOLDER_NAME") ?? null; + + transaction.AlternativePaymentResponse = apm; + } + + return transaction; + } + + private void MapTransactionStatusResponse(ref JsonDoc response) + { + response.Set("ACCOUNT_HOLDER_NAME", response.GetValue("accountholdername")) + .Set("ACCOUNT_NUMBER", response.GetValue("accountnumber")) + .Set("TIMESTAMP", response.GetValue("timestamp")) + .Set("MERCHANT_ID", response.GetValue("merchantid")) + .Set("BANK_CODE", response.GetValue("bankcode")) + .Set("BANK_NAME", response.GetValue("bankname")) + .Set("HPP_CUSTOMER_BIC", response.GetValue("bic")) + .Set("COUNTRY", response.GetValue("country")) + .Set("HPP_CUSTOMER_EMAIL", response.GetValue("customeremail")) + .Set("TRANSACTION_STATUS", response.GetValue("fundsstatus")) + .Set("IBAN", response.GetValue("iban")) + .Set("MESSAGE", response.GetValue("message")) + .Set("ORDER_ID", response.GetValue("orderid")) + .Set("PASREF", response.GetValue("pasref")) + .Set("PAYMENTMETHOD", response.GetValue("paymentmethod")) + .Set("PAYMENT_PURPOSE", response.GetValue("paymentpurpose")) + .Set("RESULT", response.GetValue("result")) + .Set("SHA1HASH", response.GetValue("sha1hash")); + } } } diff --git a/src/GlobalPayments.Api/Services/PayFacService.cs b/src/GlobalPayments.Api/Services/PayFacService.cs index dcb03dd8..1d3cfbc5 100644 --- a/src/GlobalPayments.Api/Services/PayFacService.cs +++ b/src/GlobalPayments.Api/Services/PayFacService.cs @@ -40,7 +40,7 @@ public PayFacBuilder UploadDocumentChargeback() { public PayFacBuilder UploadDocument() { return new PayFacBuilder(TransactionType.UploadDocument); } - + public PayFacBuilder ObtainSSOKey() { return new PayFacBuilder(TransactionType.ObtainSSOKey); } diff --git a/src/GlobalPayments.Api/ServicesContainer.cs b/src/GlobalPayments.Api/ServicesContainer.cs index 16885d55..8d7c0a85 100644 --- a/src/GlobalPayments.Api/ServicesContainer.cs +++ b/src/GlobalPayments.Api/ServicesContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using GlobalPayments.Api.Entities; using GlobalPayments.Api.Gateways; +using GlobalPayments.Api.Gateways.Interfaces; using GlobalPayments.Api.Services; using GlobalPayments.Api.Terminals; @@ -43,6 +44,8 @@ internal DeviceController DeviceController { internal IFraudCheckService FraudService { get; set; } + internal IFileProcessingService FileProcessingService { get; set; } + internal ISecure3dProvider GetSecure3DProvider(Secure3dVersion version) { if (_secure3dProviders.ContainsKey(version)) { return _secure3dProviders[version]; @@ -245,6 +248,14 @@ internal IFraudCheckService GetFraudCheckClient(string configName) { throw new ApiException("The specified configuration has not been configured for fraud check."); } + internal IFileProcessingService GetFileProcessingClient(string configName) + { + if (_configurations.ContainsKey(configName)) { + return _configurations[configName].FileProcessingService; + } + + throw new ApiException("The specified configuration has not been configured for file processing."); + } internal void removeConfiguration(String configName) { if(_configurations.ContainsKey(configName)) { ConfiguredServices config = new ConfiguredServices(); diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IBatchClearResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IBatchClearResponse.cs new file mode 100644 index 00000000..6e83bcfd --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IBatchClearResponse.cs @@ -0,0 +1,7 @@ +namespace GlobalPayments.Api.Terminals.Abstractions +{ public interface IBatchClearResponse : IDeviceResponse { + string SequenceNumber { get; set; } + string TotalCount { get; set; } + string TotalAmount { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs index 2113d5bb..e9c909bf 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs @@ -9,5 +9,7 @@ public interface IDeviceCommInterface { byte[] Send(IDeviceMessage message); event MessageSentEventHandler OnMessageSent; + + event MessageReceivedEventHandler OnMessageReceived; } } diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs index 9afd1926..18296bd5 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs @@ -1,12 +1,16 @@ using System; using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Entities.UPA; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Genius.Enums; using GlobalPayments.Api.Terminals.Messaging; namespace GlobalPayments.Api.Terminals { public interface IDeviceInterface : IDisposable { event MessageSentEventHandler OnMessageSent; + event MessageReceivedEventHandler OnMessageReceived; string EcrId { get; set; } #region Admin Calls void Cancel(); @@ -22,8 +26,21 @@ public interface IDeviceInterface : IDisposable { string SendCustomMessage(DeviceMessage message); IDeviceResponse SendFile(SendFileType fileType, string filePath); ISAFResponse SendStoreAndForward(); + ISafDeleteFileResponse DeleteStoreAndForwardFile(SafIndicator safIndicator); + ISAFResponse DeleteSaf(string safreferenceNumer, string tranNo=null); + IDeviceResponse RegisterPOS(string appName, int launchOrder = 0, bool remove = false, int silent = 0); IDeviceResponse SetStoreAndForwardMode(bool enabled); + IDeviceResponse SetStoreAndForwardMode(SafMode safMode); + IDeviceResponse SetStoreAndForwardMode(SafMode safMode, string startDateTime = null + , string endDateTime = null, string durationInDays = null, string maxNumber = null, string totalCeilingAmount = null + , string ceilingAmountPerCardType = null, string haloPerCardType = null, string safUploadMode = null + , string autoUploadIntervalTimeInMilliseconds = null, string deleteSafConfirmation = null); + ISafParamsResponse GetStoreAndForwardParams(); + ISafSummaryReport GetSafSummaryReport(SafIndicator safIndicator); + ISafUploadResponse SafUpload(SafIndicator safUploadIndicator); IDeviceResponse StartCard(PaymentMethodType paymentMethodType); + IDeviceResponse StartCardTransaction(UpaParam param, ProcessingIndicator indicator, UpaTransactionData transData); + ISignatureResponse PromptAndGetSignatureFile(string prompt1, string prompt2, int? displayOption); #endregion #region reporting @@ -35,23 +52,29 @@ public interface IDeviceInterface : IDisposable { #region Batch Calls IBatchCloseResponse BatchClose(); + IBatchClearResponse BatchClear(); IEODResponse EndOfDay(); #endregion #region Credit Calls //TerminalAuthBuilder CreditAuth(decimal? amount = null); //TerminalManageBuilder CreditCapture(decimal? amount = null); - //TerminalAuthBuilder CreditRefund(decimal? amount = null); - //TerminalAuthBuilder CreditSale(decimal? amount = null); + TerminalAuthBuilder CreditRefund(decimal amount); + TerminalAuthBuilder CreditSale(decimal amount); //TerminalAuthBuilder CreditVerify(); - //TerminalManageBuilder CreditVoid(); + TerminalManageBuilder CreditVoid(); #endregion #region Debit Calls - //TerminalAuthBuilder DebitSale(decimal? amount = null); + TerminalAuthBuilder DebitSale(decimal amount); //TerminalAuthBuilder DebitRefund(decimal? amount = null); #endregion + #region Voids + TerminalManageBuilder DebitVoid(); + TerminalManageBuilder VoidRefund(); + #endregion + #region Gift Calls //TerminalAuthBuilder GiftSale(decimal? amount = null); //TerminalAuthBuilder GiftAddValue(decimal? amount = null); @@ -72,14 +95,20 @@ public interface IDeviceInterface : IDisposable { TerminalAuthBuilder Balance(); TerminalManageBuilder Capture(decimal? amount = null); TerminalAuthBuilder Refund(decimal? amount = null); + TerminalManageBuilder RefundById(decimal? amount = null); TerminalAuthBuilder Sale(decimal? amount = null); TerminalAuthBuilder Verify(); TerminalManageBuilder Void(); TerminalAuthBuilder Withdrawal(decimal? amount = null); - TerminalAuthBuilder TipAdjust(decimal? amount = null); + TerminalManageBuilder TipAdjust(decimal? amount = null); TerminalAuthBuilder Tokenize(); TerminalAuthBuilder AuthCompletion(); TerminalManageBuilder DeletePreAuth(); + TerminalManageBuilder IncreasePreAuth(decimal amount); + #endregion + + #region Report Calls + TerminalReportBuilder GetTransactionDetails(TransactionType transactionType, string transactionId, TransactionIdType transactionIdType); #endregion } } diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs index a5d0fda4..dee676d3 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceResponse.cs @@ -49,7 +49,6 @@ public interface ITerminalResponse : IDeviceResponse { string CardHolderVerificationMethod { get; set; } string TerminalVerificationResults { get; set; } decimal? MerchantFee { get; set; } - string RequestId { get; } } public interface ITerminalReport : IDeviceResponse { } diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/ISafDeleteFileResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/ISafDeleteFileResponse.cs new file mode 100644 index 00000000..c739cb24 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Abstractions/ISafDeleteFileResponse.cs @@ -0,0 +1,7 @@ +namespace GlobalPayments.Api.Terminals.Abstractions +{ + public interface ISafDeleteFileResponse : IDeviceResponse { + string TotalCount { get; set; } + string TorInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/ISafParamsResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/ISafParamsResponse.cs new file mode 100644 index 00000000..c012b224 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Abstractions/ISafParamsResponse.cs @@ -0,0 +1,17 @@ +using GlobalPayments.Api.Entities.Enums; + +namespace GlobalPayments.Api.Terminals.Abstractions { + public interface ISafParamsResponse : IDeviceResponse { + SafMode SAFMode { get; set; } + string StartDateTime { get; set; } + string EndDateTime { get; set; } + string DurationInDays { get; set; } + string MaxNumberOfRecord { get; set; } + string TotalCeilingAmount { get; set; } + string CeilingAmountPerCardType { get; set; } + string HALOPerCardType { get; set; } + string UploadMode { get; set; } + string AutoUploadIntervalTime { get; set; } + string DeleteSAFConfirmation { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/ISafSummaryReport.cs b/src/GlobalPayments.Api/Terminals/Abstractions/ISafSummaryReport.cs new file mode 100644 index 00000000..c756b674 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Abstractions/ISafSummaryReport.cs @@ -0,0 +1,8 @@ +namespace GlobalPayments.Api.Terminals.Abstractions +{ + public interface ISafSummaryReport : IDeviceResponse + { + string TotalCount { get; set; } + string TotalAmount { get; set; } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/ISafUploadResponse.cs b/src/GlobalPayments.Api/Terminals/Abstractions/ISafUploadResponse.cs new file mode 100644 index 00000000..5797fa60 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Abstractions/ISafUploadResponse.cs @@ -0,0 +1,13 @@ +namespace GlobalPayments.Api.Terminals.Abstractions +{ + public interface ISafUploadResponse : IDeviceResponse { + string TotalCount { get; set; } + string TotalAmount { get; set; } + string TimeStamp { get; set; } + string UploadedCount { get; set; } + string UploadedAmount { get; set; } + string FailedCount { get; set; } + string FailedTotal { get; set; } + string TorInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs index 8f0340b9..82442818 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs @@ -1,5 +1,6 @@ using GlobalPayments.Api.Builders; using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; using GlobalPayments.Api.PaymentMethods; using GlobalPayments.Api.Terminals.Abstractions; using System; @@ -17,6 +18,7 @@ internal string AuthCode { } } internal AutoSubstantiation AutoSubstantiation { get; set; } + internal Lodging Lodging { get; set; } internal decimal? CashBackAmount { get; set; } internal string ClientTransactionId { get; set; } internal CurrencyType? Currency { get; set; } @@ -42,6 +44,9 @@ internal string AuthCode { internal int? ProcessCPC { get; set; } public string Token { get; set; } public DateTime ShippingDate { get; set; } + public decimal? PreAuthAmount { get; set; } + public AcquisitionType? CardAcquisition { get; set; } + internal bool AllowPartialAuth { get; set; } internal string TransactionId { get { @@ -60,17 +65,28 @@ public TerminalAuthBuilder WithLineItemRight(string lineItemRight) { LineItemRight = lineItemRight; return this; } + public TerminalAuthBuilder WithAllowPartialAuth(bool value) + { + AllowPartialAuth = value; + return this; + } public TerminalAuthBuilder WithAddress(Address address) { Address = address; return this; } - public TerminalAuthBuilder WithEcrId(string ecrId) { + public TerminalAuthBuilder WithEcrId(int ecrId) { EcrId = ecrId; return this; } + public TerminalAuthBuilder WithEcrId(string ecrId) + { + EcrId = int.Parse(ecrId); + return this; + } + public TerminalAuthBuilder WithAllowDuplicates(bool allowDuplicates) { AllowDuplicates = allowDuplicates; return this; @@ -79,6 +95,17 @@ public TerminalAuthBuilder WithAmount(decimal? amount) { Amount = amount; return this; } + + public TerminalAuthBuilder WithPreAuthAmount(decimal? preAuthAmount) { + PreAuthAmount = preAuthAmount; + return this; + } + + public TerminalAuthBuilder WithCardAcquisition(AcquisitionType cardAcquisition) { + CardAcquisition = cardAcquisition; + return this; + } + public TerminalAuthBuilder WithAuthCode(string value) { if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) PaymentMethod = new TransactionReference(); @@ -95,6 +122,11 @@ public TerminalAuthBuilder WithAutoSubstantiation(AutoSubstantiation value) { AutoSubstantiation = value; return this; } + + public TerminalAuthBuilder WithLodging(Lodging value) { + Lodging = value; + return this; + } public TerminalAuthBuilder WithCashBack(decimal? amount) { CashBackAmount = amount; return this; @@ -155,6 +187,7 @@ public TerminalAuthBuilder WithTaxType(TaxType taxType, string taxExemptId = nul TaxExemptId = taxExemptId; return this; } + public TerminalAuthBuilder WithToken(string value) { if (PaymentMethod == null || !(PaymentMethod is CreditCardData)) PaymentMethod = new CreditCardData(); @@ -234,6 +267,7 @@ protected override void SetupValidations() { Validations.For(PaymentMethodType.EBT).With(TransactionType.Balance) .When(() => Currency).IsNotNull() .Check(() => Currency).DoesNotEqual(CurrencyType.VOUCHER); + Validations.For(PaymentMethodType.EBT).With(TransactionType.Refund).Check(() => AllowDuplicates).Equals(false); Validations.For(PaymentMethodType.EBT).With(TransactionType.BenefitWithdrawal).Check(() => AllowDuplicates).Equals(false); @@ -246,8 +280,8 @@ protected override void SetupValidations() { Validations.For(TransactionType.Refund) .With(PaymentMethodType.Credit) .When(() => TransactionId).IsNotNull() - .Check(() => AuthCode).IsNotNull(); - Validations.For(TransactionType.AddValue).Check(() => Amount).IsNotNull(); + .Check(() => AuthCode).IsNotNull(); + Validations.For(TransactionType.AddValue).Check(() => Amount).IsNotNull(); Validations.For(TransactionType.BenefitWithdrawal) .When(() => Currency).IsNotNull() .Check(() => Currency).Equals(CurrencyType.CASH_BENEFITS); diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs index 01825fb8..5c0dd54e 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalBuilder.cs @@ -6,7 +6,8 @@ namespace GlobalPayments.Api.Terminals.Builders { public abstract class TerminalBuilder : TransactionBuilder where T : TerminalBuilder { internal PaymentMethodType PaymentMethodType { get; set; } internal int ReferenceNumber { get; set; } - internal string EcrId { get; set; } + internal int EcrId { get; set; } + internal string ClerkNumber { get; set; } public T WithPaymentMethodType(PaymentMethodType value) { PaymentMethodType = value; @@ -20,6 +21,11 @@ public T WithRequestId(int value) { ReferenceNumber = value; return this as T; } + public T WithClerkNumber(string value) + { + ClerkNumber = value; + return this as T; + } internal TerminalBuilder(TransactionType type, PaymentMethodType paymentType) : base(type) { PaymentMethodType = paymentType; diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs index 2eba6fb7..2f1a4a11 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs @@ -5,10 +5,21 @@ namespace GlobalPayments.Api.Terminals.Builders { public class TerminalManageBuilder : TerminalBuilder { internal decimal? Amount { get; set; } - internal string ClientTransactionId { get; set; } + public string ClientTransactionId { get; set; } internal CurrencyType? Currency { get; set; } internal decimal? Gratuity { get; set; } public string TerminalRefNumber { get; set; } + internal string OrigECRRefNumber { get; set; } + public Customer Customer { get; set; } + public string MessageAuthCode { get; set; } + public string ReasonCode { get; set; } + public string TrackingId { get; set; } + public string SignatureImage { get; set; } + public string SignatureFormat { get; set; } + public string SignatureLine { get; set; } + public string SoftDescriptor { get; set; } + + internal string TransactionId { get { if (PaymentMethod is TransactionReference) @@ -26,12 +37,20 @@ public TerminalManageBuilder WithTerminalRefNumber(string terminalRefNumber) { TerminalRefNumber = terminalRefNumber; return this; } - - public TerminalManageBuilder WithEcrId(string ecrId) { + 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; + return this; + } public TerminalManageBuilder WithAmount(decimal? amount) { Amount = amount; return this; @@ -81,6 +100,49 @@ public TerminalManageBuilder WithTransactionModifier(TransactionModifier modifie return this; } + public TerminalManageBuilder WithCustomer(Customer customer) + { + Customer = customer; + return this; + } + public TerminalManageBuilder WithMessageAuthCode(string messageAuthCode) + { + MessageAuthCode = messageAuthCode; + return this; + } + public TerminalManageBuilder WithReasonCode(string reasonCode) + { + ReasonCode = reasonCode; + return this; + } + public TerminalManageBuilder WithTrackingId(string trackingId) + { + TrackingId = trackingId; + return this; + } + + public TerminalManageBuilder WithSignatureImage(string signatureImage) + { + SignatureImage = signatureImage; + return this; + } + public TerminalManageBuilder WithSignatureFormat(string signatureFormat) + { + SignatureFormat = signatureFormat; + return this; + } + public TerminalManageBuilder WithSignatureLine(string signatureLine) + { + SignatureLine = signatureLine; + return this; + } + public TerminalManageBuilder WithSoftDescriptor(string softDescriptor) + { + SoftDescriptor = softDescriptor; + return this; + } + + internal TerminalManageBuilder(TransactionType type, PaymentMethodType paymentType) : base(type, paymentType) { } @@ -107,8 +169,11 @@ protected override void SetupValidations() { #endregion - Validations.For(TransactionType.Capture).Check(() => TransactionId).IsNotNull(); + Validations.For(TransactionType.Capture).Check(() => TransactionId).IsNotNull() + .Check(() => Amount).IsNotNull(); + Validations.For(TransactionType.Auth).With(TransactionModifier.Incremental).Check(() => TransactionId).IsNotNull(); Validations.For(TransactionType.Void).When(() => ClientTransactionId).IsNull().Check(() => TransactionId).IsNotNull(); + Validations.For(TransactionType.Refund).Check(() => TransactionId).IsNotNull(); } } diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs index f3d29f0b..0dc7d13c 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs @@ -1,4 +1,7 @@ -using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Genius.Enums; +using GlobalPayments.Api.Terminals.Diamond.Entities.Enums; using GlobalPayments.Api.Terminals.PAX; using GlobalPayments.Api.Terminals.UPA; using System; @@ -8,6 +11,9 @@ namespace GlobalPayments.Api.Terminals.Builders { public class TerminalReportBuilder { internal TerminalReportType ReportType { get; set; } + internal string TransactionId { get; set; } + internal TransactionType TransactionType { get; set; } + internal TransactionIdType TransactionIdType { get; set; } private TerminalSearchBuilder _searchBuilder; internal TerminalSearchBuilder SearchBuilder { @@ -22,11 +28,18 @@ internal TerminalSearchBuilder SearchBuilder { public TerminalReportBuilder(TerminalReportType reportType) { ReportType = reportType; } + public TerminalReportBuilder(TransactionType transactionType, string transactionId, TransactionIdType transactionIdType) { + TransactionType = transactionType; + TransactionId = transactionId; + TransactionIdType = transactionIdType; + } public TerminalSearchBuilder Where(PaxSearchCriteria criteria, T value) { return SearchBuilder.And(criteria, value); } - + public TerminalSearchBuilder Where(DiamondCloudSearchCriteria criteria, T value) { + return SearchBuilder.And(criteria, value); + } public TerminalSearchBuilder Where(UpaSearchCriteria criteria, T value) { return SearchBuilder.And(criteria, value); } @@ -44,7 +57,7 @@ public class TerminalSearchBuilder { internal int? RecordNumber { get; set; } internal int? TerminalReferenceNumber { get; set; } internal string AuthCode { get; set; } - internal string ReferenceNumber { get; set; } + public string ReferenceNumber { get; set; } internal int? MerchantId { get; set; } internal string MerchantName { get; set; } internal int Batch { get; set; } @@ -64,6 +77,11 @@ public TerminalSearchBuilder And(UpaSearchCriteria criteria, T value) { return this; } + public TerminalSearchBuilder And(DiamondCloudSearchCriteria criteria, T value) { + SetProperty(criteria.ToString(), value); + return this; + } + public ITerminalReport Execute(string configName = "default") { return _reportBuilder.Execute(configName); } diff --git a/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs b/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs index 727401c5..3755cfc3 100644 --- a/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs +++ b/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs @@ -4,14 +4,18 @@ using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Genius; using GlobalPayments.Api.Terminals.UPA; +using GlobalPayments.Api.Terminals.Genius.ServiceConfigs; +using GlobalPayments.Api.Terminals.Diamond; namespace GlobalPayments.Api.Terminals { public enum ConnectionModes { SERIAL, TCP_IP, SSL_TCP, - HTTP, - MIC + HTTP, + MIC, + MEET_IN_THE_CLOUD, + DIAMOND_CLOUD } public enum BaudRate { @@ -68,6 +72,7 @@ public class ConnectionConfig : Configuration, ITerminalConfiguration { public string Port { get; set; } public IRequestIdProvider RequestIdProvider { get; set; } public GatewayConfig GatewayConfig { get; set; } + public MitcConfig GeniusMitcConfig { get; set; } public ConnectionConfig() { Timeout = -1; @@ -88,6 +93,17 @@ internal override void ConfigureContainer(ConfiguredServices services) { case DeviceType.UPA_DEVICE: services.DeviceController = new UpaController(this); break; + case DeviceType.PAX_ARIES8: + case DeviceType.PAX_A80: + case DeviceType.PAX_A35: + case DeviceType.PAX_A920: + case DeviceType.PAX_A77: + case DeviceType.NEXGO_N5: + services.DeviceController = new DiamondController(this as DiamondCloudConfig); + break; + case DeviceType.GENIUS_VERIFONE_P400: + services.DeviceController = new GeniusController(this); + break; default: break; } @@ -102,10 +118,11 @@ internal override void Validate() { if(string.IsNullOrEmpty(Port)) throw new ApiException("Port is required for TCP or HTTP communication modes."); } - - if (ConnectionMode == ConnectionModes.MIC) { - if (GatewayConfig == null) { - throw new ApiException("gateway Config is required for the Meet In the Cloud Service."); + else if(ConnectionMode == ConnectionModes.MEET_IN_THE_CLOUD) + { + if(this.GeniusMitcConfig== null) + { + throw new ConfigurationException("meetInTheCloudConfig object is required for this connection method"); } } } diff --git a/src/GlobalPayments.Api/Terminals/DeviceController.cs b/src/GlobalPayments.Api/Terminals/DeviceController.cs index 88b0e0e1..ad35a6a6 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceController.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceController.cs @@ -34,12 +34,19 @@ public IRequestIdProvider RequestIdProvider { public event MessageSentEventHandler OnMessageSent; - internal DeviceController(ITerminalConfiguration settings) { + public event MessageReceivedEventHandler OnMessageReceived; + + internal DeviceController(ITerminalConfiguration settings) + { _settings = settings; _connector = ConfigureConnector(); - _connector.OnMessageSent += (message) => { + _connector.OnMessageSent += (message) => + { OnMessageSent?.Invoke(message); }; + _connector.OnMessageReceived += (message) => { + OnMessageReceived?.Invoke(message); + }; } public byte[] Send(IDeviceMessage message) { diff --git a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs index bdce73d2..a8d0c1cd 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs @@ -1,20 +1,27 @@ using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Entities.UPA; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Genius.Enums; using GlobalPayments.Api.Terminals.Messaging; -namespace GlobalPayments.Api.Terminals { +namespace GlobalPayments.Api.Terminals +{ public abstract class DeviceInterface : IDeviceInterface where T : DeviceController { protected T _controller; protected IRequestIdProvider _requestIdProvider; - public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; public string EcrId { get; set; } internal DeviceInterface(T controller) { _controller = controller; _controller.OnMessageSent += (message) => { OnMessageSent?.Invoke(message); }; + _controller.OnMessageReceived += (message) => { + OnMessageReceived?.Invoke(message); + }; _requestIdProvider = _controller.RequestIdProvider; } @@ -22,77 +29,103 @@ internal DeviceInterface(T controller) { public virtual void Cancel() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse Cancel(int? displayOption = null) { throw new System.NotImplementedException(); } public virtual IDeviceResponse CloseLane() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse DisableHostResponseBeep() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual ISignatureResponse GetSignatureFile() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - + public virtual ISignatureResponse PromptAndGetSignatureFile(string prompt1, string prompt2, int? displayOption) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } public virtual IInitializeResponse Initialize() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse LineItem(string leftText, string rightText = null, string runningLeftText = null, string runningRightText = null) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse OpenLane() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual ISignatureResponse PromptForSignature(string transactionId = null) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse Reboot() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse Reset() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual string SendCustomMessage(DeviceMessage message) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse SendFile(SendFileType fileType, string filePath) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual ISAFResponse SendStoreAndForward() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - + public virtual ISAFResponse DeleteSaf(string safRefNumber, string tranNo="") { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual IDeviceResponse RegisterPOS(string appName, int launchOrder = 0, bool remove = false, int silent = 0) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } public virtual IDeviceResponse SetStoreAndForwardMode(bool enabled) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - + public virtual IDeviceResponse SetStoreAndForwardMode(SafMode safMode) + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual ISafDeleteFileResponse DeleteStoreAndForwardFile(SafIndicator safIndicator) + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual ISafUploadResponse SafUpload(SafIndicator safUploadIndicator) + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual ISafSummaryReport GetSafSummaryReport(SafIndicator safUploadIndicator) + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual IDeviceResponse SetStoreAndForwardMode(SafMode safMode, string startDateTime + , string endDateTime,string durationInDays, string maxNumber, string totalCeilingAmount + , string ceilingAmountPerCardType, string haloPerCardType, string safUploadMode + , string autoUploadIntervalTimeInMilliseconds, string deleteSafConfirmation) + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public virtual ISafParamsResponse GetStoreAndForwardParams() + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } public virtual IDeviceResponse StartCard(PaymentMethodType paymentMethodType) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual IDeviceResponse SendSaf() { - throw new System.NotImplementedException(); + 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."); } - #endregion #region Batching public virtual IBatchCloseResponse BatchClose() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - + public virtual IBatchClearResponse BatchClear() + { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } public virtual IEODResponse EndOfDay() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } @@ -102,15 +135,12 @@ public virtual IEODResponse EndOfDay() { public virtual TerminalReportBuilder LocalDetailReport() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual TerminalReportBuilder GetSAFReport() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual TerminalReportBuilder GetBatchReport() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } - public virtual TerminalReportBuilder GetOpenTabDetails() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } @@ -150,30 +180,58 @@ public virtual TerminalAuthBuilder Withdrawal(decimal? amount = null) { return new TerminalAuthBuilder(TransactionType.BenefitWithdrawal, PaymentMethodType.EBT) .WithAmount(amount); } - - public virtual TerminalAuthBuilder TipAdjust(decimal? amount) { - throw new System.NotImplementedException(); + public virtual TerminalManageBuilder TipAdjust(decimal? amount) { + return new TerminalManageBuilder(TransactionType.Edit, PaymentMethodType.Credit) + .WithGratuity(amount); } public virtual TerminalAuthBuilder EodProcessing() { - throw new System.NotImplementedException(); + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); } - public virtual TerminalAuthBuilder Tokenize() { - throw new System.NotImplementedException(); + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); } - public virtual TerminalAuthBuilder AuthCompletion() { - throw new System.NotImplementedException(); + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); } public virtual TerminalManageBuilder DeletePreAuth() { - throw new System.NotImplementedException(); + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); } + public virtual TerminalManageBuilder IncreasePreAuth(decimal amount) { + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); + } #endregion #region IDisposable public void Dispose() { _controller.Dispose(); } + public virtual TerminalManageBuilder RefundById(decimal? amount) { + throw new UnsupportedTransactionException("This method is not supported by the currently configured device."); + } + public TerminalAuthBuilder CreditSale(decimal amount) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public TerminalAuthBuilder CreditRefund(decimal amount) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public TerminalManageBuilder CreditVoid() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public TerminalAuthBuilder DebitSale(decimal amount) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public TerminalManageBuilder DebitVoid() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public TerminalManageBuilder VoidRefund() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + public DeviceResponse GetTransactionDetails(TransactionType transactionType, string transactionId, TransactionIdType transactionIdType) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + TerminalReportBuilder IDeviceInterface.GetTransactionDetails(TransactionType transactionType, string transactionId, TransactionIdType transactionIdType) { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } #endregion } } diff --git a/src/GlobalPayments.Api/Terminals/DeviceResponse.cs b/src/GlobalPayments.Api/Terminals/DeviceResponse.cs index 446ad962..8888d254 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceResponse.cs @@ -1,7 +1,8 @@ -using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; namespace GlobalPayments.Api.Terminals { - public class DeviceResponse : IDeviceResponse { + public class DeviceResponse : IDeviceResponse, IBatchCloseResponse, ITerminalReport { /// /// device status at the time of transaction /// @@ -17,6 +18,11 @@ public class DeviceResponse : IDeviceResponse { /// public string Version { get; set; } + /// + /// the unique id of the terminal - Serial Number for Pax + /// + public string DeviceId { get; set; } + // Functional /// /// response code returned by the device @@ -32,5 +38,11 @@ public class DeviceResponse : IDeviceResponse { /// ECR reference number for the transaction /// public string ReferenceNumber { get; set; } + + public string SequenceNumber { get; set; } + + public string TotalCount { get; set; } + + public string TotalAmount { get; set; } } } diff --git a/src/GlobalPayments.Api/Terminals/Diamond/DiamondController.cs b/src/GlobalPayments.Api/Terminals/Diamond/DiamondController.cs new file mode 100644 index 00000000..bac40db3 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/DiamondController.cs @@ -0,0 +1,292 @@ +using GlobalPayments.Api.Builders; +using GlobalPayments.Api.Builders.RequestBuilder; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Diamond.Entities; +using GlobalPayments.Api.Terminals.Diamond.Interfaces; +using GlobalPayments.Api.Terminals.Diamond.Responses; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Diamond { + public class DiamondController : DeviceController { + + private DiamondCloudConfig _settings; + private Dictionary EndpointExceptions = new Dictionary() { + { DiamondCloudRequest.CAPTURE_EU, Region.EU.ToString() }, + { DiamondCloudRequest.CANCEL_AUTH, Region.EU.ToString() }, + { DiamondCloudRequest.INCREASE_AUTH, Region.EU.ToString() }, + { DiamondCloudRequest.RECONCILIATION, Region.EU.ToString() }, + { DiamondCloudRequest.CAPTURE, Region.US.ToString() }, + { DiamondCloudRequest.EBT_FOOD, Region.US.ToString() }, + { DiamondCloudRequest.EBT_BALANCE, Region.US.ToString() }, + { DiamondCloudRequest.EBT_RETURN, Region.US.ToString() }, + { DiamondCloudRequest.GIFT_RELOAD, Region.US.ToString() }, + { DiamondCloudRequest.GIFT_BALANCE, Region.US.ToString() }, + { DiamondCloudRequest.GIFT_REDEEM, Region.US.ToString() } + }; + public DiamondController(DiamondCloudConfig settings) : base(settings) { + _settings = settings; + } + + internal override ITerminalResponse ProcessTransaction(TerminalAuthBuilder builder) { + var request = BuildProcessTransaction(builder); + return DoTransaction(request); + } + + internal override ITerminalResponse ManageTransaction(TerminalManageBuilder builder) { + var request = BuildManageTransaction(builder); + return DoTransaction(request); + } + + internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { + new RequestBuilderValidations(SetupValidationsReport(builder)) + .Validate(builder, builder.ReportType); + + JsonDoc request = new JsonDoc(); + switch (builder.ReportType) { + case TerminalReportType.LocalDetailReport: + request.Set("endpoint", $"/{_settings.PosID}/details/{builder.SearchBuilder.ReferenceNumber}"); + + JsonDoc param = new JsonDoc(); + param.Set("POS_ID", _settings.PosID) + .Set("cloud_id", builder.SearchBuilder.ReferenceNumber); + request.Set("queryParams", param); + + request.Set("verb", HttpMethod.Get); + + break; + default: + throw new GatewayException("Report type not defined!"); + } + byte[] payload = Encoding.UTF8.GetBytes(request.ToString()); + var requestToDo = new DeviceMessage(payload); + + return DoTransaction(requestToDo); + } + + internal override IDeviceInterface ConfigureInterface() { + if (_interface == null) + _interface = new DiamondInterface(this); + return _interface; + } + + internal override IDeviceCommInterface ConfigureConnector() { + switch (base._settings.ConnectionMode) { + case ConnectionModes.DIAMOND_CLOUD: + return new DiamondHttpInterface(base._settings); + default: + throw new NotImplementedException(); + } + } + + private DiamondCloudResponse DoTransaction(IDeviceMessage request) { + request.AwaitResponse = true; + var response = _connector.Send(request); + + if (response == null) { + return null; + } + + string jsonObject = Encoding.UTF8.GetString(response); + + return new DiamondCloudResponse(jsonObject); + } + + private IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { + JsonDoc body = new JsonDoc(); + JsonDoc request = new JsonDoc(); + JsonDoc param = new JsonDoc(); + string endpoint = string.Empty; + param.Set("POS_ID", _settings.PosID); + switch (builder.TransactionType) { + case TransactionType.Sale: + request.Set("verb", HttpMethod.Post); + switch (builder.PaymentMethodType) { + case PaymentMethodType.EBT: + endpoint = DiamondCloudRequest.EBT_FOOD; + body.Set("amount", builder.Amount.ToNumericCurrencyString()); + break; + + case PaymentMethodType.Gift: + endpoint = DiamondCloudRequest.GIFT_REDEEM; + body.Set("amount", builder.Amount.ToNumericCurrencyString()); + break; + + default: + endpoint = DiamondCloudRequest.SALE; + body.Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("panDataToken", "") + .Set("tip_amount", builder.Gratuity.ToNumericCurrencyString()) + .Set("cash_back", builder.CashBackAmount.ToNumericCurrencyString()); + break; + } + break; + case TransactionType.Refund: + request.Set("verb", HttpMethod.Post); + if (builder.PaymentMethodType == PaymentMethodType.EBT) { + endpoint = DiamondCloudRequest.EBT_RETURN; + body.Set("amount", builder.Amount.ToNumericCurrencyString()); + } + else { + endpoint = DiamondCloudRequest.SALE_RETURN; + body.Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("panDataToken", ""); + } + break; + case TransactionType.Auth: + endpoint = DiamondCloudRequest.AUTH; + request.Set("verb", HttpMethod.Post); + body.Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("panDataToken", ""); + break; + case TransactionType.Balance: + if (_settings.Region != Region.EU.ToString()) { + request.Set("verb", HttpMethod.Post); + if (builder.PaymentMethodType == PaymentMethodType.EBT) { + endpoint = DiamondCloudRequest.EBT_BALANCE; + } + if (builder.PaymentMethodType == PaymentMethodType.Gift) { + endpoint = DiamondCloudRequest.GIFT_BALANCE; + } + } + else { + throw new GatewayException($"Transaction type {TransactionType.Balance.ToString()} for payment type not supported " + + $"in {_settings.Region}"); + } + break; + case TransactionType.BatchClose: + request.Set("verb", HttpMethod.Post); + endpoint = DiamondCloudRequest.RECONCILIATION; + break; + case TransactionType.AddValue: + if (builder.PaymentMethodType == PaymentMethodType.Gift) { + request.Set("verb", HttpMethod.Post); + endpoint = DiamondCloudRequest.GIFT_RELOAD; + body.Set("amount", builder.Amount.ToNumericCurrencyString()); + } + break; + default: + throw new GatewayException($"Transaction type {builder.TransactionType} not supported!"); + } + + if (!ValidateEndpoint(endpoint)) { + throw new GatewayException($"This feature is not supported in {_settings.Region} region!"); + } + + request.Set("endpoint", $"/{ _settings.PosID}{endpoint}") + .Set("body", body) + .Set("queryParams", param); + + byte[] payload = Encoding.UTF8.GetBytes(request.ToString()); + + return new DeviceMessage(payload); + } + + private IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { + JsonDoc body = new JsonDoc(); + JsonDoc request = new JsonDoc(); + JsonDoc param = new JsonDoc(); + string endpoint = string.Empty; + param.Set("POS_ID", _settings.PosID); + switch (builder.TransactionType) { + case TransactionType.Void: + endpoint = DiamondCloudRequest.VOID; + request.Set("verb", HttpMethod.Post); + body.Set("transaction_id", builder.TransactionId); + break; + case TransactionType.Edit: + endpoint = DiamondCloudRequest.TIP_ADJUST; + request.Set("verb", HttpMethod.Post); + body.Set("tip_amount", builder.Gratuity.ToNumericCurrencyString()) + .Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("transaction_id", builder.TransactionId); + break; + case TransactionType.Capture: + endpoint = DiamondCloudRequest.CAPTURE; + if (_settings.Region == Region.EU.ToString()) { + endpoint = DiamondCloudRequest.CAPTURE_EU; + } + request.Set("verb", HttpMethod.Post); + body.Set("tip_amount", builder.Gratuity.ToNumericCurrencyString()) + .Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("transaction_id", builder.TransactionId); + break; + case TransactionType.Delete: + if (builder.TransactionModifier == TransactionModifier.DeletePreAuth) { + endpoint = DiamondCloudRequest.CANCEL_AUTH; + request.Set("verb", HttpMethod.Post); + body.Set("transaction_id", builder.TransactionId); + } + break; + case TransactionType.Auth: + if (builder.TransactionModifier == TransactionModifier.Incremental) { + endpoint = DiamondCloudRequest.INCREASE_AUTH; + request.Set("verb", HttpMethod.Post); + body.Set("amount", builder.Amount.ToNumericCurrencyString()) + .Set("transaction_id", builder.TransactionId); + } + break; + case TransactionType.Refund: + endpoint = DiamondCloudRequest.SALE_RETURN; + request.Set("verb", HttpMethod.Post); + body.Set("transaction_id", builder.TransactionId); + break; + default: + throw new GatewayException($"Transaction type {builder.TransactionType} not supported!"); + } + + if (!ValidateEndpoint(endpoint)) { + throw new GatewayException($"This feature is not supported in {_settings.Region} region!"); + } + + request.Set("endpoint", $"/{_settings.PosID}{endpoint}") + .Set("body", body) + .Set("queryParams", param); + + byte[] payload = Encoding.UTF8.GetBytes(request.ToString()); + + return new DeviceMessage(payload); + } + + internal override byte[] SerializeRequest(TerminalAuthBuilder builder) { + throw new UnsupportedTransactionException(); + } + + internal override byte[] SerializeRequest(TerminalManageBuilder builder) { + throw new UnsupportedTransactionException(); + } + + internal override byte[] SerializeRequest(TerminalReportBuilder builder) { + throw new UnsupportedTransactionException(); + } + + #region Private methods + private Validations SetupValidationsReport(TerminalReportBuilder _builder) { + var validations = new Validations(); + validations.For(TerminalReportType.LocalDetailReport).Check(() => _builder.SearchBuilder).PropertyOf(nameof(TerminalSearchBuilder.ReferenceNumber)).IsNotNull(); + + return validations; + } + + private bool ValidateEndpoint(string endpoint) { + if (string.IsNullOrEmpty(endpoint)) { + throw new GatewayException("Payment type not supported!"); + } + + if (EndpointExceptions.ContainsKey(endpoint)) { + if (EndpointExceptions.GetValue(endpoint) != _settings.Region) { + return false; + } + } + + return true; + } + #endregion + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/DiamondCloudRequest.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/DiamondCloudRequest.cs new file mode 100644 index 00000000..908fa999 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/DiamondCloudRequest.cs @@ -0,0 +1,20 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities { + internal class DiamondCloudRequest { + internal const string SALE = "/sale"; + internal const string SALE_RETURN = "/return"; + internal const string AUTH = "/auth"; + internal const string VOID = "/void"; + internal const string CAPTURE_EU = "/authComplete"; + internal const string CAPTURE = "/capture"; + internal const string CANCEL_AUTH = "/authCancel"; + internal const string INCREASE_AUTH = "/authIncreasing"; + internal const string TIP_ADJUST = "/tip"; + internal const string EBT_FOOD = "/ebtFood"; + internal const string EBT_RETURN = "/ebtReturn"; + internal const string EBT_BALANCE = "/ebtBalance"; + internal const string GIFT_REDEEM = "/giftRedeem"; + internal const string GIFT_BALANCE = "/giftBalance"; + internal const string GIFT_RELOAD = "/giftReload"; + internal const string RECONCILIATION = "/reconciliation"; + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationMethod.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationMethod.cs new file mode 100644 index 00000000..7df2ae92 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationMethod.cs @@ -0,0 +1,9 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum AuthorizationMethod { + PIN = 'A', + SIGNATURE = '@', + PIN_AND_SIGNATURE = 'B', + NO_AUTH_METHOD = '?' + } +} + diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationType.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationType.cs new file mode 100644 index 00000000..407d36df --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/AuthorizationType.cs @@ -0,0 +1,7 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum AuthorizationType { + ONLINE = '1', + OFFLINE = '3', + REFERRAL = '4' + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/CardSource.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/CardSource.cs new file mode 100644 index 00000000..b9640fd9 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/CardSource.cs @@ -0,0 +1,9 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum CardSource { + CONTACTLESS = 'B', + MANUAL = 'M', + MAGSTRIPE = 'C', + ICC = 'P', + UNKNOWN = '?' + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudSearchCriteria.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudSearchCriteria.cs new file mode 100644 index 00000000..426a3cee --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudSearchCriteria.cs @@ -0,0 +1,6 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum DiamondCloudSearchCriteria { + TerminalReferenceNumber, + ReferenceNumber, + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudTransactionType.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudTransactionType.cs new file mode 100644 index 00000000..8ca04b52 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/DiamondCloudTransactionType.cs @@ -0,0 +1,13 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum DiamondCloudTransactionType { + UNKNOWN = 0, + SALE = 1, + PREAUTH = 4, + CAPTURE = 5, + REFUND = 6, + VOID = 10, + REPORT = 66, + PREAUTH_CANCEL = 82, + INCR_AUTH = 86 + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/TransactionResult.cs b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/TransactionResult.cs new file mode 100644 index 00000000..452bb1ab --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Entities/Enums/TransactionResult.cs @@ -0,0 +1,8 @@ +namespace GlobalPayments.Api.Terminals.Diamond.Entities.Enums { + public enum TransactionResult { + ACCEPTED = '0', + REFUSED = '1', + NO_CONNECTION = '2', + CANCELED = '7' + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondHttpInterface.cs b/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondHttpInterface.cs new file mode 100644 index 00000000..a371385e --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondHttpInterface.cs @@ -0,0 +1,118 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Messaging; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Diamond.Interfaces { + internal class DiamondHttpInterface : IDeviceCommInterface { + private DiamondCloudConfig _settings; + protected Dictionary Headers { get; set; } + protected string ContentType = "application/json"; + private string AuthorizationId; + + public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + + public DiamondHttpInterface(ITerminalConfiguration settings) { + _settings = settings as DiamondCloudConfig; + } + + public void Connect() { + char[] stringArray = _settings.IsvID.ToCharArray(); + Array.Reverse(stringArray); + string reversedStr = new string(stringArray); + var data = reversedStr + AuthorizationId; + var authorizationToken = GenerationUtils.HMACSHA256Hash(data, string.Concat(System.Linq.Enumerable.Repeat(_settings.SecretKey, 7))); + if (Headers == null) { + Headers = new Dictionary(); + } + Headers["Authorization"] = $"Bearer {authorizationToken}"; + } + + public void Disconnect() { + // TODO: Implement disconnect() method. + } + + public byte[] Send(IDeviceMessage message) { + OnMessageSent?.Invoke(message.ToString()); + + var buffer = Encoding.UTF8.GetString(message.GetSendBuffer()); + var queryParams = JsonDoc.Parse(buffer).Get("queryParams") ?? null; + if (string.IsNullOrEmpty(queryParams.GetValue("cloud_id"))) { + AuthorizationId = queryParams.GetValue("POS_ID"); + } + else { + AuthorizationId = queryParams.GetValue("cloud_id"); + } + + Connect(); + var queryString = BuildQueryString(queryParams); + var data = JsonDoc.Parse(buffer).Get("body") ?? null; + var verbData =JsonDoc.Parse(buffer).Get("verb") ?? null; + if (verbData == null) { + throw new GatewayException("Payment type not supported!"); + } + var verb = new HttpMethod(verbData.GetValue("Method")) ?? HttpMethod.Post; + Dictionary mandatoryHeaders = new Dictionary(); + + try { + var url = _settings.ServiceUrl + JsonDoc.Parse(buffer).GetValue("endpoint") + queryString; + + HttpClient httpClient = new HttpClient(); + + HttpRequestMessage request = new HttpRequestMessage(verb, url); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8"); + foreach (var header in Headers) { + request.Headers.Add(header.Key, header.Value); + } + + if (data != null) { + var content = new StringContent(data.ToString(), Encoding.UTF8, "application/json"); + request.Content = content; + } + + var response = httpClient.SendAsync(request); + if(response.Result.StatusCode != HttpStatusCode.OK) { + throw new ApiException($"ERROR: status code {response.Result.StatusCode.ToString()}"); + } + var body = response.Result.Content.ReadAsStringAsync().Result; + if (JsonDoc.IsJson(body)) { + var parsed = JsonDoc.Parse(body); + if (parsed.Has("status") && parsed.GetValue("status") == "error") { + throw new GatewayException($"Status Code: {parsed.GetValue("code")} - {parsed.GetValue("message")}"); + } + return Encoding.UTF8.GetBytes(body); + } + else { + return Encoding.UTF8.GetBytes(body); + } + } + catch (Exception e) { + throw new GatewayException($"Device {_settings.DeviceType} error: {e.Message}"); + } + } + + private string BuildQueryString(JsonDoc queryStringParams = null) { + if (queryStringParams == null) { + return ""; + } + List query = new List(); + foreach (var param in queryStringParams.Keys) { + query.Add($"{param}={queryStringParams.GetValue(param)}"); + } + + return string.Format("?{0}", string.Join("&", query.Select(x => x))); + } + + private Dictionary PrepareHeaders(Dictionary mandatoryHeaders) { + Headers = Headers.Concat(mandatoryHeaders).ToDictionary(x => x.Key, x => x.Value); + return Headers; + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondInterface.cs b/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondInterface.cs new file mode 100644 index 00000000..518fbc52 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Interfaces/DiamondInterface.cs @@ -0,0 +1,41 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Builders; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Diamond.Interfaces { + internal class DiamondInterface : DeviceInterface { + public DiamondInterface(DiamondController controller) : base(controller) { + } + + public override TerminalManageBuilder TipAdjust(decimal? tipAmount = null) { + return (new TerminalManageBuilder(TransactionType.Edit, PaymentMethodType.Credit)) + .WithGratuity(tipAmount); + } + + public override TerminalReportBuilder LocalDetailReport() { + return new TerminalReportBuilder(TerminalReportType.LocalDetailReport); + } + + public override TerminalManageBuilder DeletePreAuth() { + return (new TerminalManageBuilder(TransactionType.Delete, PaymentMethodType.Credit)) + .WithTransactionModifier(TransactionModifier.DeletePreAuth); + } + + public override TerminalManageBuilder IncreasePreAuth(decimal amount) { + return (new TerminalManageBuilder(TransactionType.Auth, PaymentMethodType.Credit)) + .WithTransactionModifier(TransactionModifier.Incremental); + } + + public override IBatchCloseResponse BatchClose() { + return (new TerminalAuthBuilder(TransactionType.BatchClose, PaymentMethodType.Credit)) + .Execute() as DeviceResponse; + } + + public override TerminalManageBuilder RefundById(decimal? amount = null) { + return new TerminalManageBuilder(TransactionType.Refund, PaymentMethodType.Credit); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Diamond/Responses/DiamondCloudResponse.cs b/src/GlobalPayments.Api/Terminals/Diamond/Responses/DiamondCloudResponse.cs new file mode 100644 index 00000000..c34f5979 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Diamond/Responses/DiamondCloudResponse.cs @@ -0,0 +1,265 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Diamond.Entities.Enums; +using GlobalPayments.Api.Utils; +using System; + +namespace GlobalPayments.Api.Terminals.Diamond.Responses { + public class DiamondCloudResponse : DeviceResponse, ITerminalResponse { + /// + /// For Visa Contactless cards: Visa available offline spending amount For Erzsebet cards: remaining + /// balance of the Voucher Type used NOTE: should be printed on the customer receipt only, not on a + /// merchant/control receipt. + /// + public string Aosa { get; set; } + + /// + /// Authorization message number – usually equal to transaction number. + /// + public string AuthorizationMessage { get; set; } + + /// + /// Cardholder authorization method, possible values enum class AuthorizationMethod + /// + public string AuthorizationMethod { get; set; } + + /// + /// Authorization type, possible values enum class AuthorizationType + /// + public string AuthorizationType { get; set; } + + /// + /// Brand name of the card – application label(EMV)or cardset name + /// + public string CardBrandName { get; set; } + + /// + /// Reader used to read card data. This character depends on the acquirer values in enum class CardSource + /// + public string CardSource { get; set; } + + /// + /// Transaction date in format YYYY.MM.DD + /// + public string Date { get; set; } + + /// + /// currencyExchangeRate float Currency exchange rate. Should be set only for DCC transaction. + /// Uses dot ‘.’ as a separator. + /// + public decimal? CurrencyExchangeRate { get; set; } + + /// + /// DCC currency exponent. + /// + public int? DccCurrencyExponent { get; set; } + + /// + /// DCC text 1. Should be set only for DCC transaction. + /// + public string DccText1 { get; set; } + + /// + /// DCC text 2. Should be set only for DCC transaction. + /// + public string DccText2 { get; set; } + + /// + /// Optional descriptive information about intent or android specific error + /// + public string ErrorMessage { get; set; } + + /// + /// Merchant ID + /// + public string MerchantId { get; set; } + + /// + /// Transaction number + /// + public string ClientTransactionId { get; set; } + + /// + /// Terminal currency + /// + public string TerminalCurrency { get; set; } + + /// + /// Terminal identifier + /// + public string TerminalId { get; set; } + + /// + /// Terminal printing indicator (value not equal 0 means that printout has been made by the terminal). + /// + public string TerminalPrintingIndicator { get; set; } + + /// + /// Transaction time format hh:mm:ss + /// + public string Time { get; set; } + + /// + /// Transaction amount in terminal currency. Should be set only for DCC transaction + /// + public decimal? TransactionAmountInTerminalCurrency { get; set; } + + /// + /// Transaction currency. Should be always set. In DCC transaction this currency is selected by user. + /// + public string TransactionCurrency { get; set; } + + /// + /// Transaction title + /// + public string TransactionTitle { get; set; } + + /// + /// EMV Application Identifier + /// + public string EmvApplicationId { get; set; }//AID + + /// + /// TVR for EMV + /// + public string EmvTerminalVerificationResults { get; set; } //TVR + + /// + /// TSI for EMV + /// + public string EmvCardHolderVerificationMethod { get; set; } //TSI + + /// + /// EMV Transaction Cryptogram + /// + public string EmvCryptogram { get; set; } //AC + + /// + /// EMV card transaction counter + /// + public string EmvCardTransactionCounter { get; set; } //ATC + + + public string InvoiceNumber { get; set; } + + public string ResultId { get; set; } + public string BatchNumber { get; set; } + public string ResponseCode { get; set; } + public string ResponseText { get; set; } + public string TransactionId { get; set; } + public string TerminalRefNumber { get; set; } + public string Token { get; set; } + public string SignatureStatus { get; set; } + public byte[] SignatureData { get; set; } + public string TransactionType { get; set; } + public string MaskedCardNumber { get; set; } + public string EntryMethod { get; set; } + public string AuthorizationCode { get; set; } + public string ApprovalCode { get; set; } + public decimal? TransactionAmount { get; set; } + public decimal? AmountDue { get; set; } + public decimal? BalanceAmount { get; set; } + public string CardHolderName { get; set; } + public string CardBIN { get; set; } + public bool CardPresent { get; set; } + public string ExpirationDate { get; set; } + public decimal? TipAmount { get; set; } + public decimal? CashBackAmount { get; set; } + public string AvsResponseCode { get; set; } + public string AvsResponseText { get; set; } + public string CvvResponseCode { get; set; } + public string CvvResponseText { get; set; } + public bool TaxExempt { get; set; } + public string TaxExemptId { get; set; } + public string TicketNumber { get; set; } + public string PaymentType { get; set; } + public string ApplicationPreferredName { get; set; } + public string ApplicationLabel { get; set; } + public string ApplicationId { get; set; } + public ApplicationCryptogramType ApplicationCryptogramType { get; set; } + public string ApplicationCryptogram { get; set; } + public string CardHolderVerificationMethod { get; set; } + public string TerminalVerificationResults { get; set; } + public decimal? MerchantFee { get; set; } + + public DiamondCloudResponse(string rawResponse) { + if (JsonDoc.IsJson(rawResponse)) { + var jsonParse = JsonDoc.Parse(rawResponse); + var paymentDetails = jsonParse.Get("PaymentResponse")?.Get("PaymentResponse") ?? (jsonParse.Get("PaymentResponse") ?? null); + TransactionId = jsonParse.GetValue("CloudTxnId") ?? (jsonParse.GetValue("cloudTxnId") ?? null); + InvoiceNumber = jsonParse.GetValue("InvoiceId") ?? null; + ReferenceNumber = jsonParse.GetValue("Device") ?? null; + TerminalRefNumber = jsonParse.GetValue("PosId") ?? null; + ResultId = jsonParse.Get("PaymentResponse")?.GetValue("ResultId") ?? null; + + if (paymentDetails != null) { + if (string.IsNullOrEmpty(TransactionId)) { + TransactionId = jsonParse.GetValue("transactionId ") ?? null; + } + Status = paymentDetails.GetValue("transactionStatus ") ?? null; + ResponseCode = paymentDetails.GetValue("resultCode") ?? null; + if (string.IsNullOrEmpty(ResponseCode)) { + ResponseCode = paymentDetails.Has("result") ? EnumConverter.FromCharToObject(paymentDetails.GetValue("result")).ToString() + : null; + } + ResponseText = paymentDetails.GetValue("hostMessage") ?? null; + if (string.IsNullOrEmpty(ResponseText)) { + ResponseText = paymentDetails.GetValue("serverMessage") ?? null; + } + Aosa = paymentDetails.GetValue("aosa") ?? null; + Version = paymentDetails.GetValue("applicationVersion") ?? null; + AuthorizationCode = paymentDetails.GetValue("authorizationCode") ?? null; + AuthorizationMessage = paymentDetails.GetValue("authorizationMessage") ?? null; + AuthorizationMethod = paymentDetails.Has("authorizationMethod") ? EnumConverter.FromCharToObject(paymentDetails.GetValue("authorizationMethod")).ToString() : null; + AuthorizationType = paymentDetails.Has("authorizationType") ? EnumConverter.FromCharToObject(paymentDetails.GetValue("authorizationType")).ToString() : null; + CardBrandName = paymentDetails.GetValue("cardBrandName") ?? (paymentDetails.GetValue("cardBrand") ?? null); + CardSource = paymentDetails.Has("cardSource") ? EnumConverter.FromCharToObject(paymentDetails.GetValue("cardSource")).ToString() : null; + EntryMethod = paymentDetails.GetValue("entryMethod") ?? null; + CashBackAmount = paymentDetails.GetValue("cashback").ToDecimal() ?? null; + CurrencyExchangeRate = paymentDetails.GetValue("currencyExchangeRate").ToDecimal() ?? null; + Date = paymentDetails.GetValue("date") ?? null; + DccCurrencyExponent = paymentDetails.GetValue("dccCurrencyExponent").ToInt32() ?? null; + DccText1 = paymentDetails.GetValue("dccText1") ?? null; + DccText2 = paymentDetails.GetValue("dccText2") ?? null; + ErrorMessage = paymentDetails.GetValue("errorMessage") ?? null; + MaskedCardNumber = paymentDetails.GetValue("maskedCardNumber") ?? (paymentDetails.GetValue("maskedCard") ?? null); + MerchantId = paymentDetails.GetValue("merchantId") ?? null; + + ClientTransactionId = paymentDetails.GetValue("slipNumber") ?? null; + TerminalCurrency = paymentDetails.GetValue("terminalCurrency") ?? null; + TerminalId = paymentDetails.GetValue("terminalId") ?? null; + TerminalPrintingIndicator = paymentDetails.GetValue("terminalPrintingIndicator") ?? null; + Time = paymentDetails.GetValue("time") ?? null; + Date = paymentDetails.GetValue("dateTime") ?? null; + TipAmount = paymentDetails.GetValue("tipAmount").ToAmount() ?? (paymentDetails.GetValue("tip").ToAmount() ?? null); + Token = paymentDetails.GetValue("token") ?? null; + TransactionAmount = paymentDetails.GetValue("transactionAmount").ToAmount() ?? (paymentDetails.GetValue("requestAmount").ToAmount() ?? null); + TransactionAmountInTerminalCurrency = paymentDetails.GetValue("transactionAmountInTerminalCurrency").ToAmount() ?? null; + TransactionCurrency = paymentDetails.GetValue("transactionCurrency") ?? null; + TransactionTitle = paymentDetails.GetValue("transactionTitle") ?? null; + TransactionType = paymentDetails.Has("type") ? EnumConverter.GetEnumFromValue(paymentDetails.GetValue("type")).ToString() : null; + + EmvCardTransactionCounter = paymentDetails.GetValue("ATC") ?? null; + EmvCryptogram = paymentDetails.GetValue("AC") ?? null; + EmvApplicationId = paymentDetails.GetValue("AID") ?? null; + EmvTerminalVerificationResults = paymentDetails.GetValue("TVR") ?? null; + EmvCardHolderVerificationMethod = paymentDetails.GetValue("TSI") ?? null; + Token = paymentDetails.GetValue("paymentToken") ?? (paymentDetails.GetValue("token") ?? null); + BatchNumber = paymentDetails.GetValue("batchNumber") ?? null; + } + + if (jsonParse.Get("PaymentResponse")?.Has("CloudInfo") ?? false) { + Command = jsonParse.Get("PaymentResponse").Get("CloudInfo").GetValue("Command"); + } + else { + Command = jsonParse.Get("CloudInfo")?.GetValue("Command") ?? null; + } + DeviceResponseCode = "00"; + } + else { + DeviceResponseCode = "00"; + TransactionId = rawResponse; + } + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/DiamondCloudConfig.cs b/src/GlobalPayments.Api/Terminals/DiamondCloudConfig.cs new file mode 100644 index 00000000..811b583b --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/DiamondCloudConfig.cs @@ -0,0 +1,32 @@ +using GlobalPayments.Api.Entities; + +namespace GlobalPayments.Api.Terminals { + public class DiamondCloudConfig : ConnectionConfig { + public string statusUrl { get; set; } + public string IsvID { get; set; } + public string SecretKey { get; set; } + public string Region { get; set; } + public string PosID { get; set; } + internal override void ConfigureContainer(ConfiguredServices services) { + if (string.IsNullOrEmpty(ServiceUrl)) { + ServiceUrl = (Environment == Environment.PRODUCTION) ? + (Region == Entities.Enums.Region.EU.ToString() ? + ServiceEndpoints.DIAMOND_CLOUD_PROD_EU : ServiceEndpoints.DIAMOND_CLOUD_PROD) : ServiceEndpoints.DIAMOND_CLOUD_TEST; + } + + Region = this.Region ?? Entities.Enums.Region.US.ToString(); + base.ConfigureContainer(services); + + } + + internal override void Validate() { + base.Validate(); + + if (ConnectionMode == ConnectionModes.DIAMOND_CLOUD) { + if (string.IsNullOrEmpty(IsvID) || string.IsNullOrEmpty(SecretKey)) { + throw new ConfigurationException("ISV ID and secretKey is required for " + ConnectionModes.DIAMOND_CLOUD.ToString()); + } + } + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/Builders/MitcManageBuilder.cs b/src/GlobalPayments.Api/Terminals/Genius/Builders/MitcManageBuilder.cs new file mode 100644 index 00000000..8359af24 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Builders/MitcManageBuilder.cs @@ -0,0 +1,66 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Genius.Responses; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Genius.Builders +{ + public class MitcManageBuilder : TerminalManageBuilder + { + public TransactionType FollowOnTransactionType; + public bool Receipt = false; + public bool AllowDuplicates = false; + public TransactionType OriginalTransType; + internal decimal? Amount { get; set; } + + internal MitcManageBuilder(TransactionType originalTransType, PaymentMethodType paymentType, TransactionType followOnTransType) : base(originalTransType, paymentType) + { + + FollowOnTransactionType = followOnTransType; + this.OriginalTransType = originalTransType; + } + public MitcManageBuilder WithAmount(decimal amount) + { + Amount = amount; + return this; + } + public MitcManageBuilder WithAllowDuplicates(bool value) + { + AllowDuplicates = value; + return this; + } + + public bool isAllowDuplicates() + { + return AllowDuplicates; + } + + public MitcManageBuilder WithReceipt(bool value) + { + Receipt = value; + return this; + } + + public override byte[] Serialize(string configName = "default") + { + throw new NotImplementedException(); + } + + public override ITerminalResponse Execute(string configName) + { + GeniusController device = (GeniusController)ServicesContainer.Instance.GetDeviceController(configName); + try + { + return device.ManageTransaction(this); + } + catch (ApiException e) + { + throw e; + } + + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/Enums/MitcRequestType.cs b/src/GlobalPayments.Api/Terminals/Genius/Enums/MitcRequestType.cs new file mode 100644 index 00000000..83070081 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Enums/MitcRequestType.cs @@ -0,0 +1,74 @@ +using GlobalPayments.Api.Terminals.Genius.Request; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Genius.Enums +{ + + public enum MitcRequestType + { + CARD_PRESENT_SALE, + CARD_PRESENT_REFUND, + REPORT_SALE_GATEWAY_ID, + REPORT_SALE_CLIENT_ID, + REPORT_REFUND_GATEWAY_ID, + REPORT_REFUND_CLIENT_ID, + REFUND_BY_CLIENT_ID, + VOID_CREDIT_SALE, + VOID_DEBIT_SALE, + VOID_REFUND + } + + public static class MitcRequestTypeExtensions + { + public static GeniusMitcRequest.HttpMethod GetVerb(this MitcRequestType mitcRequest) + { + switch (mitcRequest) + { + case MitcRequestType.CARD_PRESENT_SALE: + return GeniusMitcRequest.HttpMethod.POST; + case MitcRequestType.CARD_PRESENT_REFUND: + return GeniusMitcRequest.HttpMethod.POST; + case MitcRequestType.REPORT_SALE_CLIENT_ID: + return GeniusMitcRequest.HttpMethod.GET; + case MitcRequestType.REPORT_REFUND_CLIENT_ID: + return GeniusMitcRequest.HttpMethod.GET; + case MitcRequestType.REFUND_BY_CLIENT_ID: + return GeniusMitcRequest.HttpMethod.POST; + case MitcRequestType.VOID_CREDIT_SALE: + return GeniusMitcRequest.HttpMethod.PUT; + case MitcRequestType.VOID_DEBIT_SALE: + return GeniusMitcRequest.HttpMethod.PUT; + case MitcRequestType.VOID_REFUND: + return GeniusMitcRequest.HttpMethod.PUT; + default: + throw new NotImplementedException(); + } + } + public static string GetURLEndpoint(this MitcRequestType mitcRequest) + { + switch (mitcRequest) + { + case MitcRequestType.CARD_PRESENT_SALE: + return "/cardpresent/sales"; + case MitcRequestType.CARD_PRESENT_REFUND: + return "/cardpresent/returns"; + case MitcRequestType.REPORT_SALE_CLIENT_ID: + return "/card/sales/reference_id/%s"; + case MitcRequestType.REPORT_REFUND_CLIENT_ID: + return "/card/returns/reference_id/%s"; + case MitcRequestType.REFUND_BY_CLIENT_ID: + return "/creditsales/reference_id/%s/creditreturns"; + case MitcRequestType.VOID_CREDIT_SALE: + return "/creditsales/reference_id/%s/voids"; + case MitcRequestType.VOID_DEBIT_SALE: + return "/debitsales/reference_id/%s/voids"; + case MitcRequestType.VOID_REFUND: + return "/creditreturns/reference_id/%s/voids"; + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/Enums/TransactionIdType.cs b/src/GlobalPayments.Api/Terminals/Genius/Enums/TransactionIdType.cs new file mode 100644 index 00000000..985f955e --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Enums/TransactionIdType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Genius.Enums +{ + public enum TransactionIdType + { + CLIENT_TRANSACTION_ID, + GATEWAY_TRANSACTION_ID + } + +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/GeniusController.cs b/src/GlobalPayments.Api/Terminals/Genius/GeniusController.cs index e7808aba..aebfac27 100644 --- a/src/GlobalPayments.Api/Terminals/Genius/GeniusController.cs +++ b/src/GlobalPayments.Api/Terminals/Genius/GeniusController.cs @@ -1,24 +1,26 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Genius.Builders; +using GlobalPayments.Api.Terminals.Genius.Enums; using GlobalPayments.Api.Terminals.Genius.Interfaces; +using GlobalPayments.Api.Terminals.Genius.Request; +using GlobalPayments.Api.Terminals.Genius.Responses; using GlobalPayments.Api.Utils; using System; -using System.Text; -namespace GlobalPayments.Api.Terminals.Genius { - public class GeniusController : DeviceController { - GeniusConfig _gatewayConfig; - - internal override IDeviceInterface ConfigureInterface() { - if (_interface == null) { - _interface = new GeniusInterface(this); - } - return _interface; - } - internal override IDeviceCommInterface ConfigureConnector() { - switch (_settings.ConnectionMode) { - case ConnectionModes.HTTP: +namespace GlobalPayments.Api.Terminals.Genius +{ + public class GeniusController : DeviceController + { + private IDeviceInterface _device; + private MitcGateway _mitcGateway; + + internal override IDeviceCommInterface ConfigureConnector() + { + switch (_settings.ConnectionMode) + { + case ConnectionModes.MEET_IN_THE_CLOUD: return new GeniusHttpInterface(_settings); case ConnectionModes.SERIAL: case ConnectionModes.SSL_TCP: @@ -28,114 +30,359 @@ internal override IDeviceCommInterface ConfigureConnector() { } } - internal GeniusController(ITerminalConfiguration settings) : base(settings) { - if (settings.GatewayConfig is GeniusConfig) { - _gatewayConfig = settings.GatewayConfig as GeniusConfig; + internal GeniusController(ConnectionConfig settings) : base(settings) + { + if (settings.ConnectionMode == ConnectionModes.MEET_IN_THE_CLOUD) + { + _mitcGateway = new MitcGateway(settings); + } + else + { + throw new ConfigurationException("Unsupported device configuration."); } - else throw new ConfigurationException("GatewayConfig must be of type GeniusConfig for this device type."); } - internal override ITerminalResponse ProcessTransaction(TerminalAuthBuilder builder) { - IDeviceMessage message = BuildProcessTransaction(builder); - var response = _connector.Send(message); - throw new NotImplementedException(); - } + internal override ITerminalResponse ProcessTransaction(TerminalAuthBuilder builder) + { + JsonDoc healthcareAmount = new JsonDoc(); - internal override ITerminalResponse ManageTransaction(TerminalManageBuilder builder) { - throw new NotImplementedException(); - } + if (builder.AutoSubstantiation != null) + { + AutoSubstantiation autoSub = builder.AutoSubstantiation; + healthcareAmount.Set("copay_amount", autoSub.CopaySubTotal.ToString()) + .Set("clinical_amount", autoSub.ClinicSubTotal.ToString()) + .Set("dental_amount", autoSub.DentalSubTotal.ToString()) + .Set("prescription_amount", autoSub.PrescriptionSubTotal.ToString()) + .Set("vision_amount", autoSub.VisionSubTotal.ToString()) + .Set("healthcare_total_amount", autoSub.TotalHealthcareAmount.ToString()); + } + JsonDoc purchaseOrder = new JsonDoc(); - internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { - throw new NotImplementedException(); - } + if (builder.Address != null && builder.Address.PostalCode != null) + purchaseOrder.Set("destination_postal_code", builder.Address.PostalCode); - private IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { - ElementTree et = new ElementTree(); - - var create = et.Element("CreateTransaction").Set("xmlns", "http://transport.merchantware.net/v4/"); - et.SubElement(create, "merchantName").Text(_gatewayConfig.MerchantName); - et.SubElement(create, "merchantSiteId").Text(_gatewayConfig.MerchantSiteId); - et.SubElement(create, "merchantKey").Text(_gatewayConfig.MerchantKey); - - var request = et.SubElement(create, "request"); - et.SubElement(request, "TransactionType", MapRequestType(builder)); - et.SubElement(request, "Amount", builder.Amount.ToCurrencyString()); - et.SubElement(request, "ClerkId", _gatewayConfig.ClerkId ?? _gatewayConfig.RegisterNumber); // TODO: This should come from the config somewhere - et.SubElement(request, "OrderNumber", builder.InvoiceNumber); - et.SubElement(request, "Dba", _gatewayConfig.DBA); // TODO: This should come from the config somewhere - et.SubElement(request, "SoftwareName", "GP SDK"); // TODO: This should come from the config somewhere - et.SubElement(request, "SoftwareVersion", "2.*"); // TODO: This should come from the config somewhere - et.SubElement(request, "TransactionId", builder.ClientTransactionId); - et.SubElement(request, "TerminalId", _gatewayConfig.TerminalId); - et.SubElement(request, "PoNumber", builder.PoNumber); - et.SubElement(request, "ForceDuplicate", builder.AllowDuplicates); - - byte[] payload = Encoding.UTF8.GetBytes(BuildEnvelope(et, create)); - return new DeviceMessage(payload); - } + purchaseOrder + .Set("po_number", builder.PoNumber) + .Set("tax_amount", builder.TaxAmount.ToString()); - internal override byte[] SerializeRequest(TerminalAuthBuilder builder) { - return BuildProcessTransaction(builder).GetSendBuffer(); - } + JsonDoc payment = new JsonDoc(); + payment.Set("amount", builder.Amount.ToString()) + .Set("currency_code", "840") + .Set("invoice_number", builder.InvoiceNumber); - internal override byte[] SerializeRequest(TerminalManageBuilder builder) { - throw new NotImplementedException(); - } + if (builder.TransactionType == TransactionType.Sale) + { + payment + .Set("healthcare_amounts", healthcareAmount); - internal override byte[] SerializeRequest(TerminalReportBuilder builder) { - throw new NotImplementedException(); - } + } + + JsonDoc receipt = new JsonDoc(); - private string BuildEnvelope(ElementTree et, Element transaction) { - var envelope = et.Element("soap:Envelope"); - var body = et.SubElement(envelope, "soap:Body"); + if (builder.ClerkNumber != null) + { + receipt.Set("clerk_id", builder.ClerkNumber); + } + else + { + receipt.Set("clerk_id", "NA"); + } + + JsonDoc processingIndicator = new JsonDoc(); + processingIndicator.Set("allow_duplicate", builder.AllowDuplicates); + if (builder.TransactionType == TransactionType.Sale) + { + processingIndicator.Set("create_token", builder.RequestMultiUseToken) + .Set("partial_approval", builder.AllowPartialAuth); + } + + JsonDoc terminal = new JsonDoc().Set("terminal_id", _mitcGateway.Config.GeniusMitcConfig.TerminalId); - body.Append(transaction); + JsonDoc transaction = new JsonDoc(); + if (_mitcGateway.Config.GeniusMitcConfig.AllowKeyEntry) + { + transaction.Set("keyed_entry_mode", "allowed"); + } + + transaction.Set("country_code", "840") + .Set("language", "en-US"); + + if (processingIndicator.Keys.Count>0) + transaction.Set("processing_indicators", processingIndicator); + + if (builder.RequestMultiUseToken) + { + if (builder.CardOnFileIndicator == StoredCredentialInitiator.CardHolder) + { + transaction.Set("create_token_reason", "unscheduled_customer_initiated_transaction"); + } + else + { + transaction.Set("create_token_reason", "unscheduled_merchant_initiated_transaction"); + } + } + + transaction.Set("terminal", terminal); + + JsonDoc request = new JsonDoc(); + // Generating the reference ID. + request.Set("reference_id", builder.ClientTransactionId); + + request.Set("payment", payment); + + request.Set("receipt", receipt); + + request.Set("transaction", transaction); + + MitcRequestType requestType = new MitcRequestType(); + + if (builder.TransactionType == TransactionType.Sale) + { + requestType = MitcRequestType.CARD_PRESENT_SALE; + } + else if (builder.TransactionType == TransactionType.Refund) + { + requestType = MitcRequestType.CARD_PRESENT_REFUND; + } - return et.ToString(envelope); + try + { + return Send(request.ToString(), requestType, null); + } + catch (GatewayException e) + { + throw (e); + } } + internal ITerminalResponse ManageTransaction(MitcManageBuilder builder) + { + try + { + JsonDoc request = new JsonDoc(); + MitcRequestType requestType = new MitcRequestType(); + string targetId = builder.ClientTransactionId; - private string MapRequestType(TerminalAuthBuilder builder) { - TransactionType transType = builder.TransactionType; + /* VoidCredit, VoidCreditRefund && RefundSale Request*/ + if ((builder.FollowOnTransactionType == TransactionType.Void || builder.FollowOnTransactionType == TransactionType.Refund) + && (builder.PaymentMethodType != PaymentMethodType.Debit)) + { - switch (transType) { - case TransactionType.Auth: { - if (builder.TransactionModifier.Equals(TransactionModifier.Offline)) { - return "ForceCapture"; - } - return "Authorize"; - } - case TransactionType.BatchClose: { - return "SettleBatch"; - } - case TransactionType.Capture: { - return "Capture"; - } - case TransactionType.Edit: { - return "AdjustTip"; + JsonDoc payment = new JsonDoc(); + if (builder.Amount != null) + { + payment.Set("amount", builder.Amount.ToString()); } - //AttachSignature - //FindBoardedCard - case TransactionType.Refund: { - return "Refund"; - } - case TransactionType.Sale: { - return "Sale"; - } - case TransactionType.TokenDelete: { - return "UnboardCard"; + request.Set("payment", payment); + + /* RefundSale Request*/ + if (builder.OriginalTransType == TransactionType.Sale && builder.FollowOnTransactionType == TransactionType.Refund) + { + payment.Set("invoice_number", builder.InvoiceNumber); + JsonDoc customer = new JsonDoc(); + if (builder.CompanyId != null) + { + customer.Set("id", builder.Customer.Id); + customer.Set("title", builder.Customer.Title); + customer.Set("first_name", builder.Customer.FirstName); + customer.Set("middle_name", builder.Customer.MiddleName); + customer.Set("last_name", builder.Customer.LastName); + customer.Set("business_name", builder.Customer.Company); + customer.Set("email", builder.Customer.Email); + customer.Set("phone", builder.Customer.HomePhone); + JsonDoc address = new JsonDoc(); + address.Set("line1", builder.Customer.Address.StreetAddress1); + address.Set("line2", builder.Customer.Address.StreetAddress2); + address.Set("city", builder.Customer.Address.City); + address.Set("state", builder.Customer.Address.State); + address.Set("country", builder.Customer.Address.Country); + address.Set("postal_code", builder.Customer.Address.PostalCode); + customer.Set("billing_address", address); + } + request.Set("customer", customer); } - case TransactionType.TokenUpdate: { - return "UpdateBoardedCard"; + } + + JsonDoc transaction = new JsonDoc(); + JsonDoc processingIndicators = new JsonDoc(); + + /* VoidDebit Request*/ + if (builder.OriginalTransType == TransactionType.Sale && builder.PaymentMethodType == PaymentMethodType.Debit + && builder.FollowOnTransactionType == TransactionType.Void) + { + transaction.Set("message_authentication_code", builder.MessageAuthCode); + transaction.Set("reason_code", builder.ReasonCode); + transaction.Set("tracking_id", builder.TrackingId); + + JsonDoc receipt = new JsonDoc(); + receipt.Set("signature_image", builder.SignatureImage); + receipt.Set("signature_format", builder.SignatureFormat); + receipt.Set("signature_line", builder.SignatureLine); + request.Set("receipt", receipt); + } + + if (builder.Receipt) + { + processingIndicators.Set("generate_receipt", true); + } + else + { + processingIndicators.Set("generate_receipt", false); + } + + /* RefundSale Request*/ + if (builder.OriginalTransType == TransactionType.Sale && builder.FollowOnTransactionType == TransactionType.Refund) + { + transaction.Set("soft_descriptor", builder.SoftDescriptor); + if (builder.isAllowDuplicates()) + { + processingIndicators.Set("allow_duplicate", builder.isAllowDuplicates()); } - case TransactionType.Verify: { - return "BoardCard"; + } + transaction.Set("processing_indicators", processingIndicators); + request.Set("transaction", transaction); + + if (builder.FollowOnTransactionType == TransactionType.Void) + { + if (builder.OriginalTransType == TransactionType.Refund) + { + requestType = MitcRequestType.VOID_REFUND; } - case TransactionType.Void: { - return "Void"; + else + { + if (builder.PaymentMethodType == PaymentMethodType.Credit) + { + requestType = MitcRequestType.VOID_CREDIT_SALE; + } + else + { + requestType = MitcRequestType.VOID_DEBIT_SALE; + } } - default: { throw new UnsupportedTransactionException(); } + } + else if (builder.FollowOnTransactionType == TransactionType.Refund) + { + requestType = MitcRequestType.REFUND_BY_CLIENT_ID; + } + try + { + return Send(request.ToString(), requestType, targetId); + } + catch (GatewayException ex) + { + throw ex; + } + } + catch (BuilderException ex) + { + throw ex; + } + } + + internal override byte[] SerializeRequest(TerminalAuthBuilder builder) + { + throw new NotImplementedException(); + } + + public MitcResponse Send(string message, MitcRequestType requestType, string targetId) + { + string endpoint = ""; + GeniusMitcRequest.HttpMethod verb = new GeniusMitcRequest.HttpMethod(); + switch (requestType) + { + case MitcRequestType.CARD_PRESENT_SALE: + endpoint = requestType.GetURLEndpoint(); + verb = requestType.GetVerb(); + break; + case MitcRequestType.CARD_PRESENT_REFUND: + endpoint = requestType.GetURLEndpoint(); + verb = requestType.GetVerb(); + break; + case MitcRequestType.REPORT_SALE_CLIENT_ID: + endpoint = requestType.GetURLEndpoint(); + endpoint = endpoint.Replace("%s", targetId); + verb = requestType.GetVerb(); + break; + case MitcRequestType.REPORT_REFUND_CLIENT_ID: + endpoint = requestType.GetURLEndpoint(); + endpoint = endpoint.Replace("%s", targetId); + verb = requestType.GetVerb(); + break; + case MitcRequestType.REFUND_BY_CLIENT_ID: + endpoint = "/creditsales/reference_id/" + targetId + "/creditreturns"; + verb = requestType.GetVerb(); + break; + case MitcRequestType.VOID_CREDIT_SALE: + endpoint = requestType.GetURLEndpoint(); + endpoint = endpoint.Replace("%s", targetId); + verb = requestType.GetVerb(); + break; + case MitcRequestType.VOID_DEBIT_SALE: + endpoint = requestType.GetURLEndpoint(); + endpoint = endpoint.Replace("%s", targetId); + verb = requestType.GetVerb(); + break; + case MitcRequestType.VOID_REFUND: + endpoint = requestType.GetURLEndpoint(); + endpoint = endpoint.Replace("%s", targetId); + verb = requestType.GetVerb(); + break; + } + return _mitcGateway.DoTransaction(verb, endpoint, message, requestType); + } + + internal override IDeviceInterface ConfigureInterface() + { + if (_device == null) + _device = new GeniusInterface(this); + return _device; + } + internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) + { + MitcRequestType requestType; + + if (builder.TransactionType == TransactionType.Sale) + { + if (builder.TransactionIdType == TransactionIdType.CLIENT_TRANSACTION_ID) + { + requestType = MitcRequestType.REPORT_SALE_CLIENT_ID; + } + else + { + requestType = MitcRequestType.REPORT_SALE_GATEWAY_ID; + } + } + else if (builder.TransactionType == TransactionType.Refund) + { + if (builder.TransactionIdType == TransactionIdType.CLIENT_TRANSACTION_ID) + { + requestType = MitcRequestType.REPORT_REFUND_CLIENT_ID; + + } + else + { + requestType = MitcRequestType.REPORT_REFUND_GATEWAY_ID; + } + } + else + { + throw new BuilderException("Target transaction type must be either a sale or refund"); } + + return Send(null, requestType, builder.TransactionId); + } + internal override ITerminalResponse ManageTransaction(TerminalManageBuilder builder) + { + throw new NotImplementedException(); + } + + internal override byte[] SerializeRequest(TerminalManageBuilder builder) + { + throw new NotImplementedException(); + } + + internal override byte[] SerializeRequest(TerminalReportBuilder builder) + { + throw new NotImplementedException(); } } } diff --git a/src/GlobalPayments.Api/Terminals/Genius/GeniusInterface.cs b/src/GlobalPayments.Api/Terminals/Genius/GeniusInterface.cs index d38d4741..f787c21c 100644 --- a/src/GlobalPayments.Api/Terminals/Genius/GeniusInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Genius/GeniusInterface.cs @@ -1,11 +1,199 @@ using System; using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Entities.UPA; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Genius.Builders; +using GlobalPayments.Api.Terminals.Genius.Enums; +using GlobalPayments.Api.Terminals.Messaging; namespace GlobalPayments.Api.Terminals.Genius { - internal class GeniusInterface : DeviceInterface { - internal GeniusInterface(GeniusController controller) : base(controller) { + internal class GeniusInterface : IDeviceInterface { + private GeniusController _controller; + public event MessageReceivedEventHandler OnMessageReceived; + string IDeviceInterface.EcrId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + internal GeniusInterface(GeniusController controller) { + _controller = controller; + } + + event MessageSentEventHandler IDeviceInterface.OnMessageSent { + add { + throw new NotImplementedException(); + } + + remove { + throw new NotImplementedException(); + } + } + public TerminalAuthBuilder CreditSale(decimal amount) { + return new TerminalAuthBuilder(TransactionType.Sale, PaymentMethodType.Credit).WithAmount(amount); + } + public TerminalAuthBuilder CreditRefund(decimal amount) { + return new TerminalAuthBuilder(TransactionType.Refund, PaymentMethodType.Credit).WithAmount(amount); + } + public TerminalManageBuilder RefundById(decimal? amount) { + return new MitcManageBuilder(TransactionType.Sale, PaymentMethodType.Credit, TransactionType.Refund).WithAmount(amount); + } + public TerminalReportBuilder GetTransactionDetails(TransactionType transactionType, string transactionId, TransactionIdType transactionIdType) { + return new TerminalReportBuilder(transactionType, transactionId, transactionIdType); + } + public TerminalManageBuilder CreditVoid() { + return new MitcManageBuilder(TransactionType.Sale, PaymentMethodType.Credit, TransactionType.Void); + } + public TerminalManageBuilder DebitVoid() { + return new MitcManageBuilder(TransactionType.Sale, PaymentMethodType.Debit, TransactionType.Void); + } + public TerminalManageBuilder VoidRefund() { + return new MitcManageBuilder(TransactionType.Refund, PaymentMethodType.Credit, TransactionType.Void); + } + public TerminalAuthBuilder DebitSale(decimal amount) { + return new TerminalAuthBuilder(TransactionType.Sale, PaymentMethodType.Debit).WithAmount(amount); + } + void IDeviceInterface.Cancel() { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.CloseLane() { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.DisableHostResponseBeep() { + throw new NotImplementedException(); + } + ISignatureResponse IDeviceInterface.GetSignatureFile() { + throw new NotImplementedException(); + } + IInitializeResponse IDeviceInterface.Initialize() { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.LineItem(string leftText, string rightText, string runningLeftText, string runningRightText) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.OpenLane() { + throw new NotImplementedException(); + } + ISignatureResponse IDeviceInterface.PromptForSignature(string transactionId) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.Reboot() { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.Reset() { + throw new NotImplementedException(); + } + string IDeviceInterface.SendCustomMessage(DeviceMessage message) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.SendFile(SendFileType fileType, string filePath) { + throw new NotImplementedException(); + } + ISAFResponse IDeviceInterface.SendStoreAndForward() { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.SetStoreAndForwardMode(bool enabled) { + throw new NotImplementedException(); + } + ISafDeleteFileResponse IDeviceInterface.DeleteStoreAndForwardFile(SafIndicator safIndicator) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.SetStoreAndForwardMode(SafMode safMode) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.SetStoreAndForwardMode(SafMode safMode, string startDateTime + , string endDateTime, string durationInDays, string maxNumber, string totalCeilingAmount + , string ceilingAmountPerCardType, string haloPerCardType, string safUploadMode + , string autoUploadIntervalTimeInMilliseconds, string deleteSafConfirmation) { + throw new NotImplementedException(); + } + ISafParamsResponse IDeviceInterface.GetStoreAndForwardParams() { + throw new NotImplementedException(); + } + ISafUploadResponse IDeviceInterface.SafUpload(SafIndicator safUploadIndicator) { + throw new NotImplementedException(); + } + ISafSummaryReport IDeviceInterface.GetSafSummaryReport(SafIndicator safIndicator) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.StartCard(PaymentMethodType paymentMethodType) { + throw new NotImplementedException(); + } + TerminalReportBuilder IDeviceInterface.LocalDetailReport() { + throw new NotImplementedException(); + } + TerminalReportBuilder IDeviceInterface.GetSAFReport() { + throw new NotImplementedException(); + } + TerminalReportBuilder IDeviceInterface.GetBatchReport() { + throw new NotImplementedException(); + } + TerminalReportBuilder IDeviceInterface.GetOpenTabDetails() { + throw new NotImplementedException(); + } + IBatchCloseResponse IDeviceInterface.BatchClose() { + throw new NotImplementedException(); + } + IEODResponse IDeviceInterface.EndOfDay() { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.AddValue(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Authorize(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Balance() { + throw new NotImplementedException(); + } + TerminalManageBuilder IDeviceInterface.Capture(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Refund(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Sale(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Verify() { + throw new NotImplementedException(); + } + TerminalManageBuilder IDeviceInterface.Void() { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Withdrawal(decimal? amount) { + throw new NotImplementedException(); + } + TerminalManageBuilder IDeviceInterface.TipAdjust(decimal? amount) { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.Tokenize() { + throw new NotImplementedException(); + } + TerminalAuthBuilder IDeviceInterface.AuthCompletion() { + throw new NotImplementedException(); + } + TerminalManageBuilder IDeviceInterface.DeletePreAuth() { + throw new NotImplementedException(); + } + void IDisposable.Dispose() { + throw new NotImplementedException(); + } + public IBatchClearResponse BatchClear() { + throw new NotImplementedException(); + } + ISAFResponse IDeviceInterface.DeleteSaf(string safreferenceNumer, string tranNo) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.RegisterPOS(string appName, int launchOrder, bool remove, int silent) { + throw new NotImplementedException(); + } + IDeviceResponse IDeviceInterface.StartCardTransaction(UpaParam param, ProcessingIndicator indicator, UpaTransactionData transData) { + throw new NotImplementedException(); + } + ISignatureResponse IDeviceInterface.PromptAndGetSignatureFile(string prompt1, string prompt2, int? displayOption) { + throw new NotImplementedException(); + } + public TerminalManageBuilder IncreasePreAuth(decimal amount) { + throw new NotImplementedException(); } } } + diff --git a/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs index 55eaea1c..97f62a74 100644 --- a/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs @@ -18,6 +18,8 @@ internal class GeniusHttpInterface : IDeviceCommInterface { public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + public GeniusHttpInterface(ITerminalConfiguration settings) { _settings = settings; _gatewayConfig = settings.GatewayConfig as GeniusConfig; diff --git a/src/GlobalPayments.Api/Terminals/Genius/Interfaces/MitcGateway.cs b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/MitcGateway.cs new file mode 100644 index 00000000..58a077ee --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/MitcGateway.cs @@ -0,0 +1,132 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Gateways; +using GlobalPayments.Api.Terminals.Genius.Enums; +using GlobalPayments.Api.Terminals.Genius.Request; +using GlobalPayments.Api.Terminals.Genius.Responses; +using GlobalPayments.Api.Utils; +using Newtonsoft.Json; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Genius.Interfaces +{ + internal class MitcGateway : RestGateway + { + private static readonly Encoding UTF8_CHARSET = Encoding.UTF8; + public ConnectionConfig Config { get; } + private readonly string accountCredential; + private static readonly string TRANSACTION_API_VERSION = "2021-04-08"; + private string _requestId; + public string TargetDevice; + + public MitcGateway(ConnectionConfig settings) + { + Config = settings; + TargetDevice = Config.GeniusMitcConfig.TargetDevice; + _requestId = Config.GeniusMitcConfig.RequestId; + ServiceUrl = Config.Environment == Entities.Environment.PRODUCTION ? ServiceEndpoints.GENIUS_MITC_PRODUCTION : ServiceEndpoints.GENIUS_MITC_TEST; + + accountCredential = + $"{Config.GeniusMitcConfig.xWebId}:{Config.GeniusMitcConfig.TerminalId}:{Config.GeniusMitcConfig.AuthKey}"; + + Headers["X-GP-Version"] = TRANSACTION_API_VERSION; + Headers["X-GP-Api-Key"] = Config.GeniusMitcConfig.ApiKey; + Headers["X-GP-Target-Device"] = Config.GeniusMitcConfig.TargetDevice; + Headers["X-GP-Partner-App-Name"] = Config.GeniusMitcConfig.AppName; + Headers["X-GP-Partner-App-Version"] = Config.GeniusMitcConfig.AppVersion; + } + public MitcResponse DoTransaction(GeniusMitcRequest.HttpMethod verb, string endpoint, string requestBody, MitcRequestType requestType) + { + try + { + Headers["Authorization"] = "AuthToken " + GenerateToken(); + Headers["X-GP-Request-Id"] = _requestId; + Headers["X-GP_Version"] = "2021-04-08"; + Timeout = 30000; + var response = SendRequest(verb.GetVerb(), endpoint, requestBody, null, null, false); + return new MitcResponse((int)response.StatusCode, "", response.RawResponse); + } + catch (GatewayException ex) + { + // Handling error response messages + if (ex.ResponseCode != null && + (ex.ResponseCode.Equals("471") || + ex.ResponseCode.Equals("470") || + ex.ResponseCode.Equals("404") || + ex.ResponseCode.Equals("400") || + ex.ResponseCode.Equals("403") || + ex.ResponseCode.Equals("401"))) + { + JsonDoc responseObj = JsonDoc.Parse(ex.ResponseMessage); + } + throw ex; + } + catch (Exception ex) + { + throw new GatewayException(ex.Message); + } + } + + #region Private methods for AuthToken + private string GenerateToken() + { + try + { + string region = Config.GeniusMitcConfig.Region; + string apiSecret = Config.GeniusMitcConfig.ApiSecret; + + string encodedHeaderJSON = generateEncodedJSONHeader(); + string encodedPayLoadJSON = generateEncodedPayloadJSON(this.accountCredential, region); + string encodedSignature = generateHashSignature(encodedHeaderJSON, encodedPayLoadJSON, apiSecret); + + /** + * Create the JWT AuthToken by concatenating Base64 URL encoded header, + * payload and signature + */ + string GeneratedAuthTokenV2 = encodedHeaderJSON + "." + encodedPayLoadJSON + "." + encodedSignature; + return GeneratedAuthTokenV2; + } + catch (Exception err) + { + throw new GatewayException(err.Message, err); + } + } + + private string generateHashSignature(string encodedHeaderJSON, string encodedPayLoadJSON, string apiSecret) + { + string data = encodedHeaderJSON + "." + encodedPayLoadJSON; + byte[] hash = new HMACSHA256(System.Text.Encoding.UTF8.GetBytes(apiSecret)).ComputeHash(System.Text.Encoding.UTF8.GetBytes(data)); + return UrlSafe(Convert.ToBase64String(hash)); + } + private string UrlSafe(string input) + { + return input.Replace('+', '-').Replace('/', '_'); + } + private string objectToString(object obj) + { + return UrlSafe(Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj)))); + } + private string generateEncodedPayloadJSON(string accountCredential, string region) + { + object payLoadObj = new + { + type = "AuthTokenV2", + account_credential = accountCredential, + region = region, + ts = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds() + }; + return objectToString(payLoadObj); + } + private string generateEncodedJSONHeader() + { + object headerObj = new + { + alg = "HS256", + typ = "JWT" + }; + return objectToString(headerObj); + } + #endregion + } +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/Request/GeniusMitcRequest.cs b/src/GlobalPayments.Api/Terminals/Genius/Request/GeniusMitcRequest.cs new file mode 100644 index 00000000..f667c275 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Request/GeniusMitcRequest.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using static GlobalPayments.Api.Terminals.Genius.Request.GeniusMitcRequest; + +namespace GlobalPayments.Api.Terminals.Genius.Request +{ + public class GeniusMitcRequest + { + public HttpMethod Verb { get; set; } = HttpMethod.GET; + public string Endpoint { get; set; } + public string RequestBody { get; set; } = ""; + public Dictionary QueryStringParams { get; } + public GeniusMitcRequest() + { + QueryStringParams = new Dictionary(); + } + + public enum HttpMethod + { + GET, + POST, + PUT + } + } + public static class GeniusMitcRequestExtensions + { + public static System.Net.Http.HttpMethod GetVerb(this HttpMethod httpMethod) + { + switch (httpMethod) + { + case HttpMethod.POST: + return System.Net.Http.HttpMethod.Post; + case HttpMethod.PUT: + return System.Net.Http.HttpMethod.Put; + case HttpMethod.GET: + return System.Net.Http.HttpMethod.Get; + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Genius/Responses/MitcResponse.cs b/src/GlobalPayments.Api/Terminals/Genius/Responses/MitcResponse.cs new file mode 100644 index 00000000..9ae92cb9 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/Responses/MitcResponse.cs @@ -0,0 +1,357 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace GlobalPayments.Api.Terminals.Genius.Responses +{ + public class MitcResponse : ITerminalResponse, ITerminalReport + { + public string GatewayResponseCode { get; set; } + public string GatewayResponseMessage { get; set; } + public string InvoiceNumber { get; set; } + public string ResponseDateTime { get; set; } + public decimal GratuityAmount { get; set; } + public string Status { get; set; } + public string ApprovalCode { get; set; } + public string CvvResultCode { get; set; } + public string CashbackAmount { get; set; } + public string AuthorizedAmount { get; set; } + public string TokenResponseCode { get; set; } + public string TokenResponseMsg { get; set; } + public ICC ICC { get; set; } + public string TraceNumber { get; set; } + public string CurrencyCode { get; set; } + public string TenderType { get; set; } + public string ClientTransactionId { get; set; } + public string ExpMonth { get; set; } + public string ExpYear { get; set; } + public string Type { get; set; } + public string PostalCode { get; set; } + public string CustomerId { get; set; } + public string TransactionId { get; set; } + public string MaskedCardNumber { get; set; } + public string CardHolderName { get; set; } + public string EntryMethod { get; set; } + public string Token { get; set; } + public string DeviceResponseCode { get; set; } + public string DeviceResponseText { get; set; } + public string ResponseCode { get; set; } + public string ResponseText { get; set; } + public string TerminalRefNumber { get; set; } + public string SignatureStatus { get; set; } + public byte[] SignatureData { get; set; } + public string TransactionType { get; set; } + public string AuthorizationCode { get; set; } + public decimal? TransactionAmount { get; set; } + public decimal? AmountDue { get; set; } + public decimal? BalanceAmount { get; set; } + public string CardBIN { get; set; } + public bool CardPresent { get; set; } + public string ExpirationDate { get; set; } + public decimal? TipAmount { get; set; } + public decimal? CashBackAmount { get; set; } + public string AvsResponseCode { get; set; } + public string AvsResponseText { get; set; } + public string CvvResponseCode { get; set; } + public string CvvResponseText { get; set; } + public bool TaxExempt { get; set; } + public string TaxExemptId { get; set; } + public string TicketNumber { get; set; } + public string PaymentType { get; set; } + public string ApplicationPreferredName { get; set; } + public string ApplicationLabel { get; set; } + public string ApplicationId { get; set; } + public ApplicationCryptogramType ApplicationCryptogramType { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string ApplicationCryptogram { get; set; } + public string CardHolderVerificationMethod { get; set; } + public string TerminalVerificationResults { get; set; } + public decimal? MerchantFee { get; set; } + public string Command { get; set; } + public string Version { get; set; } + public string ReferenceNumber { get; set; } + + public MitcResponse(int responseCode, string responseMessage, string responseData) + { + try + { + MapStatusCode(responseCode, responseMessage); + var values = JsonConvert.DeserializeObject>(responseData); + HashMapper(values); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + private void MapStatusCode(int statusCode, string statusMessage) + { + switch (statusCode.ToString()) + { + case "200": + case "201": + case "473": + ResponseCode = "00"; + ResponseText = "success"; + break; + case "470": + case "472": + ResponseCode = "05"; + ResponseText = "declined"; + break; + case "471": + case "474": + ResponseCode = "10"; + ResponseText = "partial approval"; + break; + case "400": + case "401": + case "402": + case "403": + case "404": + case "409": + case "429": + case "500": + case "503": + ResponseCode = "ER"; + break; + default: + break; + } + + GatewayResponseCode = statusCode.ToString(); + GatewayResponseMessage = statusMessage; + } + + private void HashMapper(Dictionary keyValues) + { + foreach (KeyValuePair entry in keyValues) + { + string key = entry.Key; + object value = entry.Value; + Type valueType = entry.Value.GetType(); + + if (value is string) + { + AssignValues((string)value, key); + } + else if (valueType.Name == "JArray") + { + Dictionary dict = new Dictionary(); + foreach (JObject content in ((JArray)value).Children()) + { + foreach (JProperty prop in content.Properties()) + { + Type type = prop.Value.GetType(); + + if (type.Name == "JValue") + { + dict.Add(prop.Name, prop.Value.ToString()); + } + else if (type.Name == "JObject") + { + dict.Add(prop.Name, prop.Value); + } + } + } + HashMapper(dict); + } + else if (valueType.Name == "JObject") + { + Dictionary dict = new Dictionary(); + if (key.ToLower() == "icc") + { + ICC =JsonConvert.DeserializeObject(value.ToString()); + continue; + } + foreach (var content in ((JObject)value).Children()) + { + foreach (var prop in content.Children()) + { + if (prop.Children().Count() > 1) + { + foreach (var item in (JsonConvert.DeserializeObject>(prop.ToString()))) + { + if (!dict.ContainsKey(item.Key)) + { + dict.Add(item.Key, item.Value); + } + } + } + else + { + if (!dict.ContainsKey(content.Name)) + { + dict.Add(content.Name, content.Value.ToString()); + } + } + } + } + + HashMapper(dict); + } + else + { + throw new ArgumentException(value.ToString()); + } + } + } + + private void AssignValues(string value, string key) + { + if (key == "invoice_number") + { + InvoiceNumber = value; + } + if (key == "amount") + { + TransactionAmount = decimal.Parse(value); + } + if (key == "currency_code") + { + CurrencyCode = value; + } + if (key == "gratuity_amount") + { + GratuityAmount = decimal.Parse(value); + } + if (key == "tender_type") + { + TenderType = value; + } + if (key == "entry_type") + { + EntryMethod = value; + } + if (key == "id") + { + TransactionId = value; + } + if (key == "reference_id") + { + ClientTransactionId = value; + } + if (key == "transaction_datetime") + { + ResponseDateTime = value; + } + if (key == "approval_code") + { + ApprovalCode = value; + } + if (key == "avs_response") + { + AvsResponseCode = value; + } + if (key == "avs_response_description") + { + AvsResponseText = value; + } + if (key == "cardsecurity_response") + { + CvvResultCode = value; + } + if (key == "cashback_amount") + { + CashbackAmount = value; + } + if (key == "type") + { + PaymentType = value; + } + if (key == "masked_card_number") + { + MaskedCardNumber = value; + } + if (key == "cardholder_name") + { + CardHolderName = value; + } + if (key == "expiry_month") + { + ExpMonth = value; + } + if (key == "expiry_year") + { + ExpYear = value; + } + if (key == "token") + { + Token = value; + } + if (key == "balance") + { + BalanceAmount = decimal.Parse(value); + } + if (key == "postal_code") + { + PostalCode = value; + } + if (key == "rfmiq") + { + CustomerId = value; + } + if (key == "debit_trace_number") + { + TraceNumber = value; + } + if (key == "tokenization_error_code") + { + TokenResponseCode = value; + } + if (key == "tokenization_error_message") + { + TokenResponseMsg = value; + } + if (key == "amount_authorized") + { + AuthorizedAmount = value; + } + + } + + } + + + public class ICC + { + public ICC() + { + + } + public string Cardholder_Verification_Method_Results { get; set; } + public string Issuer_Application_Data { get; set; } + public string Terminal_Verification_Results { get; set; } + public string Unpredictable_number { get; set; } + public string Pos_entry_mode { get; set; } + public string Cryptogram_information_data { get; set; } + public string Cvm_method { get; set; } + public string Iac_default { get; set; } + public string Iac_denial { get; set; } + public string Authorization_response_code { get; set; } + public string Dedicated_file_name { get; set; } + public string Application_label { get; set; } + public string Application_interchange_profile { get; set; } + public string Application_version_number { get; set; } + public string Application_transaction_counter { get; set; } + public string Application_usage_control { get; set; } + public string Aapplication_preferred_name { get; set; } + public string Application_display_name { get; set; } + public string Application_cryptogram { get; set; } + public string Terminal_type { get; set; } + public string Terminal_country_code { get; set; } + public string Tac_default { get; set; } + public string Tac_denial { get; set; } + public string Tac_online { get; set; } + public string Transaction_type { get; set; } + public string Transaction_currency_code { get; set; } + public string Transaction_status_information { get; set; } + + } +} + diff --git a/src/GlobalPayments.Api/Terminals/Genius/ServiceConfigs/MitcConfig.cs b/src/GlobalPayments.Api/Terminals/Genius/ServiceConfigs/MitcConfig.cs new file mode 100644 index 00000000..75ad9780 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Genius/ServiceConfigs/MitcConfig.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Genius.ServiceConfigs +{ + public class MitcConfig + { + public string xWebId; + public string TerminalId; + public string AuthKey; + public string ApiSecret; + public string ApiKey; + public string TargetDevice; + + + /** Optional * + * Version-4 UUID you generate to identify each request you send. + * Add a prefix of MER- to your ID. For example: + * MER-ba96b9c5-828c-434c-be74-d73c8e853526 * + * Note: If you don’t send a value for this parameter, we generate a value with a prefix of API- and return it in the header of the response. + */ + public string RequestId; + + /// + /// The default environment + /// + public Entities.Environment Environment = Entities.Environment.PRODUCTION; + + /// + /// *Required + /// Name is given to integration by the integrators. + /// Will default to '.NET-SDK' if none is provided. + /// + public string AppName = ".NET-SDK"; + + /// + /// Version number given to the integration by the integrators. + /// + public string AppVersion = ""; + + /// + /// * Required + /// Currently supported regions: + /// US - United States + ///CA - Canada + /// AU - Australia + /// NZ - New Zealand + /// + public string Region = "US"; + + /// + /// *Optional + ///'true' will allow card number entry on device + /// + public Boolean AllowKeyEntry = true; + + /// + /// *Opional + /// To enable the logging request and responses. + /// + private Boolean EnableLogging = false; + + public MitcConfig(string xWebId, string terminalId, string authKey, string apiSecret, string apiKey, string targetDevice, string requestId=null) + { + this.xWebId = xWebId; + this.TerminalId = terminalId; + this.AuthKey = authKey; + this.ApiSecret = apiSecret; + this.ApiKey = apiKey; + this.TargetDevice = targetDevice; + this.RequestId = requestId; + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/HPA/HpaController.cs b/src/GlobalPayments.Api/Terminals/HPA/HpaController.cs index 4f4aae80..5a076115 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/HpaController.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/HpaController.cs @@ -70,8 +70,9 @@ internal override ITerminalResponse ManageTransaction(TerminalManageBuilder buil return response; } - internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { - throw new NotImplementedException(); + internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) + { + return SendAdminMessage(new HpaAdminBuilder(HPA_MSG_ID.GET_SAF_REPORT)); } internal string BuildProcessTransaction(TerminalAuthBuilder builder) { @@ -119,6 +120,9 @@ internal string BuildManageTransaction(TerminalManageBuilder builder) { if ((builder.TransactionType == TransactionType.Capture)||(builder.TransactionType == TransactionType.Void)) et.SubElement(request, "RequestId", requestId); et.SubElement(request, "TransactionId", builder.TransactionId); + + if (builder.Amount != null) + et.SubElement(request, "TotalAmount").Text(builder.Amount.ToNumericCurrencyString()); if (builder.Gratuity != null) et.SubElement(request, "TipAmount").Text(builder.Gratuity.ToNumericCurrencyString()); diff --git a/src/GlobalPayments.Api/Terminals/HPA/HpaEnums.cs b/src/GlobalPayments.Api/Terminals/HPA/HpaEnums.cs index fd88c414..dfa4b924 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/HpaEnums.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/HpaEnums.cs @@ -13,6 +13,7 @@ internal class HPA_MSG_ID { public const string REBOOT = "Reboot"; public const string BATCH_CLOSE = "BatchClose"; public const string GET_BATCH_REPORT = "GetBatchReport"; + public const string GET_SAF_REPORT = "GetSAFReport"; public const string CREDIT_SALE = "Sale"; public const string CREDIT_REFUND = "Refund"; public const string CREDIT_VOID = "Void"; diff --git a/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs b/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs index 71be05bd..82cfe8e5 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs @@ -117,6 +117,12 @@ public override IDeviceResponse SendFile(SendFileType imageType, string filePath public override TerminalReportBuilder LocalDetailReport() { throw new NotImplementedException(); } + + public override TerminalReportBuilder GetSAFReport() + { + return new TerminalReportBuilder(TerminalReportType.GetSAFReport); + } + #endregion #region Batching diff --git a/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs index e6e0260a..62eb74d3 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs @@ -7,6 +7,7 @@ using GlobalPayments.Api.Terminals.Messaging; using GlobalPayments.Api.Terminals.Extensions; using GlobalPayments.Api.Utils; +using System.Text; namespace GlobalPayments.Api.Terminals.HPA.Interfaces { internal class HpaTcpInterface : IDeviceCommInterface { @@ -17,6 +18,8 @@ internal class HpaTcpInterface : IDeviceCommInterface { public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + public HpaTcpInterface(ITerminalConfiguration settings) { this._settings = settings; } @@ -85,11 +88,11 @@ public void Disconnect() { } public byte[] Send(IDeviceMessage message) { - Connect(); - var str_message = message.ToString(); message_queue = new List(); try { + Connect(); + byte[] buffer = message.GetSendBuffer(); if (_stream != null) { @@ -110,7 +113,16 @@ public byte[] Send(IDeviceMessage message) { } finally { OnMessageSent?.Invoke(message.ToString().Substring(2)); - if (message.KeepAlive) { + if (message_queue.ToArray().Length > 0) + { + OnMessageReceived?.Invoke(Encoding.UTF8.GetString(message_queue.ToArray(), 0, message_queue.ToArray().Length)); + } + else + { + OnMessageReceived?.Invoke("Terminal did not respond"); + } + if (!message.KeepAlive) + { Disconnect(); } } diff --git a/src/GlobalPayments.Api/Terminals/HPA/Responses/EODResponse.cs b/src/GlobalPayments.Api/Terminals/HPA/Responses/EODResponse.cs index e30ef32b..10c2e4c4 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Responses/EODResponse.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Responses/EODResponse.cs @@ -128,6 +128,11 @@ public IBatchReportResponse getBatchReportResponse() { return BatchReportResponse; } + public IBatchReportResponse getSafReportResponse() + { + return BatchReportResponse; + } + public ISAFResponse getSAFResponse() { return SAFResponse; } @@ -173,6 +178,14 @@ internal override void MapResponse(Element response) { } _batchReportMessageBuilder.Append(currentMessage).Append('\r'); } + else if (Command.Equals(EODCommandType.GET_SAF_REPORT, StringComparison.OrdinalIgnoreCase)) + { + if (_batchReportMessageBuilder == null) + { + _batchReportMessageBuilder = new StringBuilder(); + } + _batchReportMessageBuilder.Append(currentMessage).Append('\r'); + } else if (Command.Equals(EODCommandType.HEARTBEAT, StringComparison.OrdinalIgnoreCase)) { try { HeartBeatResponse = new HeartBeatResponse(Encoding.UTF8.GetBytes(currentMessage), EODCommandType.HEARTBEAT); diff --git a/src/GlobalPayments.Api/Terminals/HPA/Responses/SAFResponse.cs b/src/GlobalPayments.Api/Terminals/HPA/Responses/SAFResponse.cs index e841d3fd..7c14d3ef 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Responses/SAFResponse.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Responses/SAFResponse.cs @@ -6,7 +6,7 @@ using GlobalPayments.Api.Utils; namespace GlobalPayments.Api.Terminals.HPA.Responses { - public class SAFResponse : SipBaseResponse, ISAFResponse { + public class SAFResponse : SipBaseResponse, ISAFResponse, ITerminalReport { public decimal? TotalAmount { get; set; } public int TotalCount { get; set; } @@ -14,7 +14,7 @@ public class SAFResponse : SipBaseResponse, ISAFResponse { public Dictionary Pending { get; set; } public Dictionary Declined { get; set; } - private string LastCategory; + private string LastCategory = ""; private TransactionSummary LastTransactionSummary; public SAFResponse(byte[] buffer, params string[] messageIds) : base(buffer, messageIds) { } @@ -23,7 +23,7 @@ internal override void MapResponse(Element response) { base.MapResponse(response); string category = response.GetValue("TableCategory") ?? ""; - if (category == null) { + if (String.IsNullOrEmpty(category)) { category = LastCategory; } var fieldValues = new Dictionary(); @@ -32,32 +32,44 @@ internal override void MapResponse(Element response) { } if (category.EndsWith("SUMMARY", StringComparison.OrdinalIgnoreCase)) { - var summary = new SummaryResponse { - SummaryType = MapSummaryType(category), - Count = fieldValues.GetValue("Count"), - Amount = fieldValues.GetAmount("Amount"), - TotalAmount = fieldValues.GetAmount("Total Amount"), - AuthorizedAmount = fieldValues.GetAmount("Authorized Amount"), - AmountDue = fieldValues.GetAmount("Balance Due Amount"), - }; + SummaryType ReportSummaryType = MapSummaryType(category); - if (category.Contains("APPROVED")) { - if(Approved==null) { - Approved = new Dictionary(); + if (ReportSummaryType != SummaryType.Unknown) + { + var summary = new SummaryResponse + { + SummaryType = MapSummaryType(category), + Count = fieldValues.GetValue("Count"), + Amount = fieldValues.GetAmount("Amount"), + TotalAmount = fieldValues.GetAmount("Total Amount"), + AuthorizedAmount = fieldValues.GetAmount("Authorized Amount"), + AmountDue = fieldValues.GetAmount("Balance Due Amount"), + }; + + if (category.Contains("APPROVED")) + { + if (Approved == null) + { + Approved = new Dictionary(); + } + Approved.Add(summary.SummaryType, summary); } - Approved.Add(summary.SummaryType, summary); - } - else if (category.StartsWith("PENDING")) { - if (Pending == null) { - Pending = new Dictionary(); + else if (category.StartsWith("PENDING")) + { + if (Pending == null) + { + Pending = new Dictionary(); + } + Pending.Add(summary.SummaryType, summary); } - Pending.Add(summary.SummaryType, summary); - } - else if (category.StartsWith("DECLINED")) { - if (Declined == null) { - Declined = new Dictionary(); + else if (category.StartsWith("DECLINED")) + { + if (Declined == null) + { + Declined = new Dictionary(); + } + Declined.Add(summary.SummaryType, summary); } - Declined.Add(summary.SummaryType, summary); } } else if (category.EndsWith("RECORD", StringComparison.OrdinalIgnoreCase)) { @@ -77,7 +89,6 @@ internal override void MapResponse(Element response) { AuthCode = fieldValues.GetValue("ApprovalCode"), IssuerResponseCode = fieldValues.GetValue("ResponseCode"), IssuerResponseMessage = fieldValues.GetValue("ResponseText"), - HostTimeout = (bool)fieldValues.GetBoolean("HostTimeOut"), // BaseAmount - Not doing this one TaxAmount = fieldValues.GetAmount("TaxAmount"), GratuityAmount = fieldValues.GetAmount("TipAmount"), @@ -85,6 +96,9 @@ internal override void MapResponse(Element response) { AuthorizedAmount = fieldValues.GetAmount("Authorized Amount"), AmountDue = fieldValues.GetAmount("Balance Due Amount") }; + if (fieldValues.GetBoolean("HostTimeOut") != null) { + trans.HostTimeout = (bool)fieldValues.GetBoolean("HostTimeOut"); + } } if (!(category.Equals(LastCategory))) { if (category.StartsWith("APPROVED")) { @@ -130,7 +144,35 @@ private SummaryType MapSummaryType(string category) { else if (category.Equals(SAFReportType.DECLINED_VOID, StringComparison.OrdinalIgnoreCase)) { return SummaryType.VoidDeclined; } - else throw new ApiException(string.Format("Unknown Category Value: {0}", category)); + else if (category.Equals(SAFReportType.PROVISIONAL, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.Provsional; + } + else if (category.Equals(SAFReportType.DISCARDED, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.Discarded; + } + else if (category.Equals(SAFReportType.REVERSAL, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.Reversal; + } + else if (category.Equals(SAFReportType.EMV_DECLINED, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.EmvDeclined; + } + else if (category.Equals(SAFReportType.ATTACHMENT, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.Attachment; + } + else if (category.Equals(SAFReportType.PROVISIONAL_VOID, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.VoidProvisional; + } + else if (category.Equals(SAFReportType.DISCARDED_VOID, StringComparison.OrdinalIgnoreCase)) + { + return SummaryType.VoidDiscarded; + } + return SummaryType.Unknown; } } } diff --git a/src/GlobalPayments.Api/Terminals/HPA/Responses/SipBaseResponse.cs b/src/GlobalPayments.Api/Terminals/HPA/Responses/SipBaseResponse.cs index be847576..d5a0e596 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Responses/SipBaseResponse.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Responses/SipBaseResponse.cs @@ -43,6 +43,7 @@ public SipBaseResponse(byte[] buffer, params string[] messageIds) { Status = root.GetValue("MultipleMessage"); DeviceResponseCode = NormalizeResponse(root.GetValue("Result")); DeviceResponseText = root.GetValue("ResultText"); + DeviceId = root.GetValue("DeviceId"); if ((DeviceResponseCode.Equals("00", StringComparison.OrdinalIgnoreCase)) || (DeviceResponseCode.Equals("2501", StringComparison.OrdinalIgnoreCase))){ MapResponse(root); @@ -128,6 +129,11 @@ public class SipTerminalResponse : SipBaseResponse, ITerminalResponse { /// public string ApprovalCode { get; set; } + /// + /// transaction was approved by the terminal in SAF mode + /// + public string StoreAndForward { get; set; } + /// /// the amount of the transaction /// diff --git a/src/GlobalPayments.Api/Terminals/HPA/Responses/SipDeviceResponse.cs b/src/GlobalPayments.Api/Terminals/HPA/Responses/SipDeviceResponse.cs index 7e643652..ac628f96 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Responses/SipDeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Responses/SipDeviceResponse.cs @@ -27,10 +27,11 @@ internal override void MapResponse(Element response) { TransactionId = response.GetValue("ResponseId", "TransactionId"); ResponseText = response.GetValue("ResponseText", "ResultText"); SignatureStatus = response.GetValue("SignatureLine"); - // StoreAndForward + StoreAndForward = response.GetValue("StoreAndForward"); // TipAdjustAllowed TerminalRefNumber = response.GetValue("ReferenceNumber"); TransactionAmount = response.GetValue("AuthorizedAmount").ToAmount(); + TipAmount = response.GetValue("TipAmount").ToAmount(); // EMV ApplicationId = response.GetValue("EMV_AID"); diff --git a/src/GlobalPayments.Api/Terminals/Messaging/MessageReceivedEventHandler.cs b/src/GlobalPayments.Api/Terminals/Messaging/MessageReceivedEventHandler.cs new file mode 100644 index 00000000..c88b6ab9 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Messaging/MessageReceivedEventHandler.cs @@ -0,0 +1,4 @@ +namespace GlobalPayments.Api.Terminals.Messaging +{ + public delegate void MessageReceivedEventHandler(string message); +} diff --git a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs index 5f8229ea..e66f58e9 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs @@ -6,6 +6,7 @@ using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Messaging; using System.Threading.Tasks; +using System.Text; namespace GlobalPayments.Api.Terminals.PAX { internal class PaxHttpInterface : IDeviceCommInterface { @@ -14,6 +15,8 @@ internal class PaxHttpInterface : IDeviceCommInterface { public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + public PaxHttpInterface(ITerminalConfiguration settings) { _settings = settings; } @@ -43,6 +46,14 @@ public byte[] Send(IDeviceMessage message) { foreach (char c in rec_buffer) buffer.Add((byte)c); } + if (buffer.ToArray().Length > 0) + { + OnMessageReceived?.Invoke(Encoding.UTF8.GetString(buffer.ToArray(), 0, buffer.ToArray().Length)); + } + else + { + OnMessageReceived?.Invoke("Terminal did not respond"); + } return buffer.ToArray(); }).Result; } diff --git a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs index e152efcf..ab8b7e2b 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs @@ -4,6 +4,8 @@ using GlobalPayments.Api.Utils; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Messaging; +using Newtonsoft.Json.Linq; +using System.Text; namespace GlobalPayments.Api.Terminals.PAX { internal class PaxTcpInterface : IDeviceCommInterface { @@ -15,6 +17,8 @@ internal class PaxTcpInterface : IDeviceCommInterface { public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + public PaxTcpInterface(ITerminalConfiguration settings) { _settings = settings; } @@ -30,7 +34,9 @@ public void Connect() { } public void Disconnect() { - _connectionCount--; + if (_connectionCount > 0) { + _connectionCount--; + } if (_connectionCount == 0) { _stream?.Dispose(); _stream = null; @@ -42,20 +48,26 @@ public void Disconnect() { public byte[] Send(IDeviceMessage message) { byte[] buffer = message.GetSendBuffer(); + byte[] rvalue = null; - Connect(); - try { + try + { + Connect(); + for (int i = 0; i < 3; i++) { _stream.WriteAsync(buffer, 0, buffer.Length).Wait(); - var rvalue = _stream.GetTerminalResponseAsync(); + rvalue = _stream.GetTerminalResponseAsync(); if (rvalue != null) { byte lrc = rvalue[rvalue.Length - 1]; // Should be the LRC + Array.Resize(ref rvalue, rvalue.Length - 1); if (lrc != TerminalUtilities.CalculateLRC(rvalue)) { SendControlCode(ControlCodes.NAK); } else { SendControlCode(ControlCodes.ACK); + Array.Resize(ref rvalue, rvalue.Length + 1); + rvalue[rvalue.Length - 1] = lrc; return rvalue; } } @@ -72,6 +84,14 @@ public byte[] Send(IDeviceMessage message) { } finally { OnMessageSent?.Invoke(message.ToString()); + if (rvalue != null) + { + OnMessageReceived?.Invoke(Encoding.UTF8.GetString(rvalue, 0, rvalue.Length)); + } + else + { + OnMessageReceived?.Invoke("Terminal did not respond"); + } Disconnect(); } } diff --git a/src/GlobalPayments.Api/Terminals/PAX/PaxController.cs b/src/GlobalPayments.Api/Terminals/PAX/PaxController.cs index e34ef9dc..d435ac32 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/PaxController.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/PaxController.cs @@ -203,7 +203,8 @@ internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { var account = new AccountRequest(); var trace = new TraceRequest { ReferenceNumber = requestId.ToString(), - ClientTransactionId = builder.ClientTransactionId + ClientTransactionId = builder.ClientTransactionId, + OrigECRRefNumber = builder.OrigECRRefNumber, }; var extData = new ExtDataSubGroup(); @@ -213,9 +214,18 @@ internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { amounts.TransactionAmount = "{0:c}".FormatWith(_amount).ToNumeric(); } - // ADDING THIS HERE CAUSES IT TO FAIL SKIPPING IT HERE - //if (gratuity.HasValue) - // amounts.TipAmount = "{0:c}".FormatWith(gratuity).ToNumeric(); + if (builder.TransactionType == TransactionType.Edit && builder.Gratuity.HasValue) + { + /* + - Transaction Type 06 : ADJUST: Used for additional charges or gratuity. + - Typically used for tip adjustment. + - Set the amount to Transaction Amount, not the Tip Amount */ + var _tipRequest = 1; + var _gratuityAmount = builder.Gratuity; + amounts.TipAmount = null; + amounts.TransactionAmount = "{0:c}".FormatWith(_gratuityAmount).ToNumeric(); + extData[EXT_DATA.TIP_REQUEST] = _tipRequest.ToString(); + } if (builder.PaymentMethod != null) { if (builder.PaymentMethod is TransactionReference) { @@ -320,6 +330,8 @@ private string MapTransactionType(TransactionType type, bool requestToken = fals return requestToken ? PAX_TXN_TYPE.TOKENIZE : PAX_TXN_TYPE.VERIFY; case TransactionType.Void: return PAX_TXN_TYPE.VOID; + case TransactionType.Edit: + return PAX_TXN_TYPE.ADJUST; default: throw new UnsupportedTransactionException(); } diff --git a/src/GlobalPayments.Api/Terminals/PAX/PaxEnums.cs b/src/GlobalPayments.Api/Terminals/PAX/PaxEnums.cs index 57393455..97ccc665 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/PaxEnums.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/PaxEnums.cs @@ -31,6 +31,7 @@ internal class PAX_MSG_ID { public const string A52_COMPLETE_CONTACTLESS_EMV = "A52"; public const string A54_SET_SAF_PARAMETERS = "A54"; public const string A56_SHOW_TEXTBOX = "A56"; + public const string A78_GET_SAF_PARAMETERS = "A78"; // TRANSACTION REQUESTS public const string T00_DO_CREDIT = "T00"; @@ -86,6 +87,7 @@ internal class PAX_MSG_ID { public const string A53_RSP_COMPLETE_CONTACTLESS_EMV = "A53"; public const string A55_RSP_SET_SAF_PARAMETERS = "A55"; public const string A57_RSP_SHOW_TEXTBOX = "A57"; + public const string A79_RSP_GET_SAF_PARAMETERS = "A79"; // TRANSACTION RESPONSES public const string T01_RSP_DO_CREDIT = "T01"; diff --git a/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs index 29b52734..ce89f49f 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs @@ -3,18 +3,18 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.Terminals.Abstractions; using System.Text; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Terminals.PAX.Responses; namespace GlobalPayments.Api.Terminals.PAX { public class PaxInterface : DeviceInterface, IDeviceInterface { internal PaxInterface(PaxController controller) : base(controller) { } - #region Administration Messages public override IInitializeResponse Initialize() { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A00_INITIALIZE)); return new InitializeResponse(response); } - public override ISignatureResponse GetSignatureFile() { var response = _controller.Send(TerminalUtilities.BuildRequest( PAX_MSG_ID.A08_GET_SIGNATURE, @@ -23,7 +23,6 @@ public override ISignatureResponse GetSignatureFile() { )); return new SignatureResponse(response, _controller.DeviceType.Value); } - public override void Cancel() { if (_controller.ConnectionMode == ConnectionModes.HTTP) { throw new MessageException("The cancel command is not available in HTTP mode"); @@ -38,12 +37,76 @@ public override void Cancel() { } } } - public override IDeviceResponse Reset() { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A16_RESET)); return new PaxTerminalResponse(response, PAX_MSG_ID.A17_RSP_RESET); } - + public override ISafDeleteFileResponse DeleteStoreAndForwardFile(SafIndicator safIndicator) + { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.B10_DELETE_SAF_FILE, ((int)safIndicator).ToString())); + return new SafDeleteFileResponse(response); + } + public override IDeviceResponse SetStoreAndForwardMode(SafMode safMode) { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A54_SET_SAF_PARAMETERS + , ((int)safMode).ToString() + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + , ControlCodes.FS + )); + return new PaxTerminalResponse(response, PAX_MSG_ID.A55_RSP_SET_SAF_PARAMETERS); + } + public override IDeviceResponse SetStoreAndForwardMode(SafMode safMode, string startDateTime = null + , string endDateTime = null, string durationInDays = null, string maxNumber = null, string totalCeilingAmount = null + , string ceilingAmountPerCardType = null, string haloPerCardType = null, string safUploadMode = null + , string autoUploadIntervalTimeInMilliseconds = null, string deleteSafConfirmation = null) + { + + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A54_SET_SAF_PARAMETERS + ,((int)safMode).ToString() + ,ControlCodes.FS + ,startDateTime + ,ControlCodes.FS + ,endDateTime + ,ControlCodes.FS + ,durationInDays + ,ControlCodes.FS + ,maxNumber + ,ControlCodes.FS + ,totalCeilingAmount + ,ControlCodes.FS + ,ceilingAmountPerCardType + ,ControlCodes.FS + ,haloPerCardType + ,ControlCodes.FS + ,safUploadMode + ,ControlCodes.FS + ,autoUploadIntervalTimeInMilliseconds + ,ControlCodes.FS + ,deleteSafConfirmation)); + return new PaxTerminalResponse(response, PAX_MSG_ID.A55_RSP_SET_SAF_PARAMETERS); + } + public override ISafUploadResponse SafUpload(SafIndicator safUploadIndicator) + { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.B08_SAF_UPLOAD, ((int)safUploadIndicator).ToString())); + return new SafUploadResponse(response); + } + public override ISafParamsResponse GetStoreAndForwardParams() + { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A78_GET_SAF_PARAMETERS)); + return new SafParamsResponse(response); + } + public override ISafSummaryReport GetSafSummaryReport(SafIndicator safReportIndicator) + { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.R10_SAF_SUMMARY_REPORT, ((int)safReportIndicator).ToString())); + return new SafSummaryReport(response); + } public override ISignatureResponse PromptForSignature(string transactionId = null) { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A20_DO_SIGNATURE, (transactionId != null) ? 1 : 0, @@ -59,12 +122,10 @@ public override ISignatureResponse PromptForSignature(string transactionId = nul return GetSignatureFile(); return signatureResponse; } - public override IDeviceResponse Reboot() { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A26_REBOOT)); return new PaxTerminalResponse(response, PAX_MSG_ID.A27_RSP_REBOOT); } - public override IDeviceResponse DisableHostResponseBeep() { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A04_SET_VARIABLE, "01", @@ -84,7 +145,6 @@ public override IDeviceResponse DisableHostResponseBeep() { )); return new PaxTerminalResponse(response, PAX_MSG_ID.A05_RSP_SET_VARIABLE); } - public override string SendCustomMessage(DeviceMessage message) { var response = _controller.Send(message); return Encoding.UTF8.GetString(response); @@ -102,13 +162,18 @@ public override IBatchCloseResponse BatchClose() { var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.B00_BATCH_CLOSE, DateTime.Now.ToString("YYYYMMDDhhmmss"))); return new BatchCloseResponse(response); } + public override IBatchClearResponse BatchClear() + { + var response = _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.B04_BATCH_CLEAR, "01")); //Sending specific Credit EDC type. + return new BatchClearResponse(response); + } #endregion #region Credit Methods //public TerminalAuthBuilder CreditAuth(decimal? amount = null) { // return new TerminalAuthBuilder(TransactionType.Auth, PaymentMethodType.Credit).WithAmount(amount); //} - + //public TerminalManageBuilder CreditCapture(decimal? amount = null) { // return new TerminalManageBuilder(TransactionType.Capture, PaymentMethodType.Credit).WithAmount(amount); //} @@ -182,6 +247,6 @@ public override IBatchCloseResponse BatchClose() { //public TerminalAuthBuilder GiftBalance() { // return new TerminalAuthBuilder(TransactionType.Balance, PaymentMethodType.Gift).WithCurrency(CurrencyType.CURRENCY); //} - #endregion + #endregion } } \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/BatchClearResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/BatchClearResponse.cs new file mode 100644 index 00000000..a6d25214 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/BatchClearResponse.cs @@ -0,0 +1,34 @@ +using System.IO; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; + +namespace GlobalPayments.Api.Terminals.PAX +{ + public class BatchClearResponse : PaxTerminalResponse, IBatchClearResponse { + private HostResponse hostResponse; + + public string TotalCount { get; set; } + public string TotalAmount { get; set; } + public string TimeStamp { get; set; } + public string TID { get; set; } + public string MID { get; set; } + public string SequenceNumber { get; set; } + + public BatchClearResponse(byte[] buffer) + : base(buffer, PAX_MSG_ID.B05_RSP_BATCH_CLEAR) + { + } + + protected override void ParseResponse(BinaryReader br) + { + base.ParseResponse(br); + + this.hostResponse = new HostResponse(br); + this.TotalCount = br.ReadToCode(ControlCodes.FS); + this.TotalAmount = br.ReadToCode(ControlCodes.FS); + this.TimeStamp = br.ReadToCode(ControlCodes.FS); + this.TID = br.ReadToCode(ControlCodes.FS); + this.MID = br.ReadToCode(ControlCodes.ETX); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs index 3e2924ce..3eb47fe9 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/DeviceResponse.cs @@ -61,7 +61,7 @@ public override string ToString() { } public class PaxTerminalResponse : PaxBaseResponse, ITerminalResponse { - protected List acceptedCodes = new List { "000000", "100011", "000002" }; + protected List acceptedCodes = new List { "000000", "000100", "100011", "000002" }; /// /// response code returned by the gateway @@ -246,9 +246,6 @@ public class PaxTerminalResponse : PaxBaseResponse, ITerminalResponse { public string TerminalVerificationResults { get; set; } public decimal? MerchantFee { get; set; } - - public string RequestId { get; } - public int TranNo { get; set; } internal PaxTerminalResponse(byte[] buffer, params string[] messageIds) : base(buffer, messageIds) { } diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/SafDeleteFileResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafDeleteFileResponse.cs new file mode 100644 index 00000000..d9a50183 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafDeleteFileResponse.cs @@ -0,0 +1,25 @@ +using System.IO; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; + +namespace GlobalPayments.Api.Terminals.PAX.Responses +{ + public class SafDeleteFileResponse : PaxTerminalResponse, ISafDeleteFileResponse + { + public string TotalCount { get; set; } + public string TorInfo { get; set; } + + public SafDeleteFileResponse(byte[] buffer) + : base(buffer, PAX_MSG_ID.B11_RSP_DELETE_SAF_FILE) + { + } + + protected override void ParseResponse(BinaryReader br) + { + base.ParseResponse(br); + TotalCount = br.ReadToCode(ControlCodes.FS); + TorInfo = br.ReadToCode(ControlCodes.FS); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/SafParamsResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafParamsResponse.cs new file mode 100644 index 00000000..22330f7a --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafParamsResponse.cs @@ -0,0 +1,46 @@ +using System.IO; +using GlobalPayments.Api.Entities.Enums; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; + +namespace GlobalPayments.Api.Terminals.PAX.Responses +{ + public class SafParamsResponse : PaxTerminalResponse , ISafParamsResponse + { + //private HostResponse hostResponse; + + public SafMode SAFMode { get; set; } + public string StartDateTime { get; set; } + public string EndDateTime { get; set; } + public string DurationInDays { get; set; } + + public string MaxNumberOfRecord { get; set; } + public string TotalCeilingAmount { get; set; } + public string CeilingAmountPerCardType { get; set; } + public string HALOPerCardType { get; set; } + public string UploadMode { get; set; } + public string AutoUploadIntervalTime { get; set; } + public string DeleteSAFConfirmation { get; set; } + + + public SafParamsResponse(byte[] buffer) + : base(buffer, PAX_MSG_ID.A79_RSP_GET_SAF_PARAMETERS) + { + } + + protected override void ParseResponse(BinaryReader br) + { + base.ParseResponse(br); + + SAFMode = (SafMode)int.Parse(br.ReadToCode(ControlCodes.FS)); + StartDateTime = br.ReadToCode(ControlCodes.FS); + EndDateTime = br.ReadToCode(ControlCodes.FS); + DurationInDays = br.ReadToCode(ControlCodes.FS); + MaxNumberOfRecord =br.ReadToCode(ControlCodes.FS); + TotalCeilingAmount = br.ReadToCode(ControlCodes.FS); + CeilingAmountPerCardType = br.ReadToCode(ControlCodes.FS); + HALOPerCardType = br.ReadToCode(ControlCodes.FS); + UploadMode = br.ReadToCode(ControlCodes.FS); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/SafSummaryReport.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafSummaryReport.cs new file mode 100644 index 00000000..7cdcce92 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafSummaryReport.cs @@ -0,0 +1,23 @@ +using System.IO; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; + +namespace GlobalPayments.Api.Terminals.PAX.Responses +{ + public class SafSummaryReport : PaxTerminalResponse, ISafSummaryReport + { + public string TotalCount { get; set; } + public string TotalAmount { get; set; } + public SafSummaryReport(byte[] buffer) + : base(buffer, PAX_MSG_ID.R11_RSP_SAF_SUMMARY_REPORT) + { + } + + protected override void ParseResponse(BinaryReader br) + { + base.ParseResponse(br); + TotalCount = br.ReadToCode(ControlCodes.FS); + TotalAmount = br.ReadToCode(ControlCodes.FS); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/PAX/Responses/SafUploadResponse.cs b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafUploadResponse.cs new file mode 100644 index 00000000..0d7911ed --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/PAX/Responses/SafUploadResponse.cs @@ -0,0 +1,35 @@ +using System.IO; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; + +namespace GlobalPayments.Api.Terminals.PAX.Responses +{ + public class SafUploadResponse : PaxTerminalResponse, ISafUploadResponse + { + public string TotalCount { get; set; } + public string TotalAmount { get; set; } + public string TimeStamp { get; set; } + public string UploadedCount { get; set; } + public string UploadedAmount { get; set; } + public string FailedCount { get; set; } + public string FailedTotal { get; set; } + public string TorInfo { get; set; } + public SafUploadResponse(byte[] buffer) + : base(buffer, PAX_MSG_ID.B09_RSP_SAF_UPLOAD) + { + } + + protected override void ParseResponse(BinaryReader br) + { + base.ParseResponse(br); + TotalCount = br.ReadToCode(ControlCodes.FS); + TotalAmount = br.ReadToCode(ControlCodes.FS); + TimeStamp = br.ReadToCode(ControlCodes.FS); + UploadedCount = br.ReadToCode(ControlCodes.FS); + UploadedAmount = br.ReadToCode(ControlCodes.FS); + FailedCount = br.ReadToCode(ControlCodes.FS); + FailedTotal = br.ReadToCode(ControlCodes.FS); + TorInfo = br.ReadToCode(ControlCodes.FS); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/PAX/SubGroups/TraceSubGroups.cs b/src/GlobalPayments.Api/Terminals/PAX/SubGroups/TraceSubGroups.cs index ffaa5153..e6d889a7 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/SubGroups/TraceSubGroups.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/SubGroups/TraceSubGroups.cs @@ -13,6 +13,7 @@ internal class TraceRequest : IRequestSubGroup { public string TransactionNumber { get; set; } public string TimeStamp { get; set; } public string ClientTransactionId { get; set; } + public string OrigECRRefNumber { get; set; } public string GetElementString() { var sb = new StringBuilder(); @@ -20,6 +21,8 @@ public string GetElementString() { sb.Append((char)ControlCodes.US); sb.Append(InvoiceNumber); sb.Append((char)ControlCodes.US); + sb.Append(OrigECRRefNumber); + sb.Append((char)ControlCodes.US); sb.Append(AuthCode); sb.Append((char)ControlCodes.US); sb.Append(TransactionNumber); diff --git a/src/GlobalPayments.Api/Terminals/SummaryResponse.cs b/src/GlobalPayments.Api/Terminals/SummaryResponse.cs index 93a2b98e..2db2233b 100644 --- a/src/GlobalPayments.Api/Terminals/SummaryResponse.cs +++ b/src/GlobalPayments.Api/Terminals/SummaryResponse.cs @@ -10,7 +10,15 @@ public enum SummaryType { VoidPending, Declined, VoidDeclined, - OfflineApproved + OfflineApproved, + Provsional, + Discarded, + VoidProvisional, + VoidDiscarded, + Reversal, + EmvDeclined, + Attachment, + Unknown } public class SummaryResponse { diff --git a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs index 204ff61e..a112bc1c 100644 --- a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs +++ b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs @@ -8,6 +8,7 @@ using System.Drawing.Imaging; using GlobalPayments.Api.Terminals.UPA; using GlobalPayments.Api.Utils; +using GlobalPayments.Api.Entities.UPA; namespace GlobalPayments.Api.Terminals { public class TerminalUtilities { @@ -36,7 +37,7 @@ private static DeviceMessage BuildMessage(string messageId, string message) { // Begin Message buffer.Add((byte)ControlCodes.STX); - + // Add Message ID foreach (char c in messageId) buffer.Add((byte)c); @@ -95,13 +96,13 @@ public static DeviceMessage BuildRequest(string messageId, params object[] eleme public static DeviceMessage BuildUpaAdminRequest(int requestId, string ecrId, string txnType, string lineItemLeft = null, string lineItemRight = null, int? displayOption = null) { var doc = new JsonDoc(); doc.Set("message", UpaMessageType.Msg); - var data = doc.SubElement("data"); - data.Set("command", txnType); - data.Set("EcrId", ecrId); - data.Set("requestId", requestId); + var baseRequest = doc.SubElement("data"); + baseRequest.Set("command", txnType); + baseRequest.Set("EcrId", ecrId); + baseRequest.Set("requestId", requestId.ToString()); if ((!string.IsNullOrEmpty(lineItemLeft) || !string.IsNullOrEmpty(lineItemRight)) || displayOption.HasValue) { - var request = data.SubElement("data"); + var request = baseRequest.SubElement("data"); var requestParams = request.SubElement("params"); requestParams.Set("lineItemLeft", lineItemLeft); requestParams.Set("lineItemRight", lineItemRight); @@ -112,8 +113,9 @@ public static DeviceMessage BuildUpaAdminRequest(int requestId, string ecrId, st return BuildUpaRequest(doc.ToString()); } - + public static byte[] BuildRawUpaRequest(string jsonRequest) { + jsonRequest = jsonRequest.Replace("ecrId", "EcrId"); jsonRequest = jsonRequest.Replace("", "\r\n"); @@ -148,8 +150,9 @@ public static DeviceMessage BuildUpaRequest(T doc) where T : IRawRequestBu return new DeviceMessage(doc, buffer); } public static DeviceMessage BuildUpaRequest(string jsonRequest) { + var json = JsonDoc.Parse(jsonRequest); byte[] buffer = BuildRawUpaRequest(jsonRequest); - return new DeviceMessage(buffer); + return new DeviceMessage(json, buffer); } public static byte CalculateLRC(byte[] buffer) { @@ -163,7 +166,6 @@ public static byte CalculateLRC(byte[] buffer) { lrc = (byte)(lrc ^ buffer[i]); return lrc; } - public static byte[] BuildSignatureImage(string pathData, int width = 150) { Func toPoint = (coord) => { var xy = coord.Split(','); @@ -184,7 +186,7 @@ public static byte[] BuildSignatureImage(string pathData, int width = 150) { var index = 0; var coordinate = coordinates[index++]; do { - if (coordinate == "0,65535") + if(coordinate == "0,65535") coordinate = coordinates[index++]; var start = toPoint(coordinate); diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaMicInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaMicInterface.cs index e36130c3..8cb0d92e 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaMicInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaMicInterface.cs @@ -12,6 +12,8 @@ internal class UpaMicInterface : IDeviceCommInterface { public event MessageSentEventHandler OnMessageSent; + public event MessageReceivedEventHandler OnMessageReceived; + public UpaMicInterface(ITerminalConfiguration config) { _config = config; _gatewayConfig = _config.GatewayConfig as GpApiConfig; @@ -26,7 +28,7 @@ public void Disconnect() { /* NOM NOM */ } public byte[] Send(IDeviceMessage message) { Connect(); - string requestId = message.GetRequestField("data").GetValue("requestId"); + string requestId = message.GetRequestField("data")?.GetValue("requestId"); var request = new JsonDoc(); request.Set("merchant_id", _gatewayConfig.MerchantId); diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index 22ea5005..1bbbce62 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -14,14 +14,17 @@ 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) { _settings = settings; + _tokenSource = new CancellationTokenSource(); } public void Connect() @@ -31,13 +34,12 @@ public void Connect() public void Disconnect() { - + _tokenSource.Cancel(); } public byte[] Send(IDeviceMessage deviceMessage) { - var tokenSource = new CancellationTokenSource(); - var token = tokenSource.Token; + var token = _tokenSource.Token; var tcs = new TaskCompletionSource(); @@ -47,8 +49,6 @@ public byte[] Send(IDeviceMessage deviceMessage) var buffer = deviceMessage.GetSendBuffer(); - var readyReceived = false; - var client = new TcpClientAsync { IpAddress = IPAddress.Parse(_settings.IpAddress), @@ -77,7 +77,7 @@ public byte[] Send(IDeviceMessage deviceMessage) if (rvalue != null) { var msgValue = GetResponseMessageType(rvalue); - + OnMessageSent?.Invoke($"Server Response: {msgValue}"); switch (msgValue) { case UpaMessageType.Ack: @@ -88,11 +88,9 @@ public byte[] Send(IDeviceMessage deviceMessage) if (serverIsBusy) { await c.Send(new ArraySegment(buffer, 0, buffer.Length), token); - OnMessageSent?.Invoke(deviceMessage.ToString()); + OnMessageSent?.Invoke("Resending Request..."); serverIsBusy = false; } - - readyReceived = true; break; case UpaMessageType.Busy: serverIsBusy = true; @@ -101,11 +99,7 @@ public byte[] Send(IDeviceMessage deviceMessage) break; case UpaMessageType.Msg: responseMessage = TrimResponse(rvalue); - if (IsNonReadyResponse(responseMessage)) - { - readyReceived = true; // since reboot doesn't return READY - } - + OnMessageSent?.Invoke($"Sending {UpaMessageType.Ack}..."); await SendAckMessageToDevice(c); break; default: @@ -115,6 +109,7 @@ public byte[] Send(IDeviceMessage deviceMessage) if (responseMessage != null) { + OnMessageReceived?.Invoke(Encoding.UTF8.GetString(responseMessage)); c.Disconnect(); } }, @@ -173,7 +168,7 @@ private string GetResponseMessageType(byte[] response) } } - private static async Task SendAckMessageToDevice(TcpClientAsync c) + private static Task SendAckMessageToDevice(TcpClientAsync c) { var doc = new JsonDoc(); doc.Set("message", UpaMessageType.Ack); @@ -181,7 +176,7 @@ private static async Task SendAckMessageToDevice(TcpClientAsync c) var message = TerminalUtilities.BuildUpaRequest(doc.ToString()); var ackBuffer = message.GetSendBuffer(); - await c.Send(new ArraySegment(ackBuffer, 0, ackBuffer.Length)); + return c.Send(new ArraySegment(ackBuffer, 0, ackBuffer.Length)); } private static bool IsNonReadyResponse(byte[] responseMessage) 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 3abb7855..825176ce 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/BatchReportResponse.cs @@ -25,8 +25,9 @@ public BatchReportResponse(JsonDoc root) { Status = cmdResult.GetValue("result"); if (string.IsNullOrEmpty(Status)) { - DeviceResponseCode = cmdResult.GetValue("errorCode"); - DeviceResponseText = cmdResult.GetValue("errorMessage"); + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; } else { // If the Status is not "Success", there is either nothing to process, or something else went wrong. @@ -83,8 +84,9 @@ public BatchReportResponse(JsonDoc root) { } } else { // the only other option is "Failed" - DeviceResponseCode = cmdResult.GetValue("errorCode"); - DeviceResponseText = cmdResult.GetValue("errorMessage"); + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; } } } 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/SafReportResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/SafReportResponse.cs index 0ce8f3fb..a773cbcc 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/SafReportResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/SafReportResponse.cs @@ -68,6 +68,8 @@ public SafReportResponse(JsonDoc root) { TransactionType = record.GetValue("transactionType"), TerminalRefNumber = record.GetValue("transId"), // The sample XML says tranNo? ReferenceNumber = record.GetValue("referenceNumber"), + SafReferenceNumber = record.GetValue("safReferenceNumber"), + TranNo = record.GetValue("tranNo"), GratuityAmount = record.GetValue("tipAmount"), TaxAmount = record.GetValue("taxAmount"), Amount = record.GetValue("baseAmount"), @@ -127,7 +129,7 @@ private SummaryType MapSummaryType(string safType) { return SummaryType.Declined; } } - SafReport ReportResult; + public SafReport ReportResult; public string Status { get; set; } public string Command { get; set; } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index 67ece021..2cc1f5fb 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs @@ -4,13 +4,14 @@ namespace GlobalPayments.Api.Terminals.UPA { - - public class TransactionResponse : ITerminalResponse { + public class TransactionResponse: ITerminalResponse { #region Properties public decimal? AvailableBalance { get; set; } public string TransactionId { get; set; } public string TerminalRefNumber { get; set; } public string Token { get; set; } + public string TokenResponseCode { get; set; } + public string TokenResponseMessage { get; set; } public string CardBrandTransId { get; set; } public string SignatureStatus { get; set; } public byte[] SignatureData { get; set; } @@ -49,6 +50,7 @@ public class TransactionResponse : ITerminalResponse { public string ResponseText { get; set; } public string ApprovalCode { get; set; } public decimal? TipAmount { get; set; } + public decimal? BaseAmount { get; set; } public decimal? CashBackAmount { get; set; } public string ReferenceNumber { get; set; } public string CardHolderName { get; set; } @@ -61,6 +63,7 @@ public class TransactionResponse : ITerminalResponse { public TransactionResponse(JsonDoc root) { + if (!isGpApiResponse(root)) { var response = root.Get("data"); if (response == null) { return; @@ -77,7 +80,18 @@ public TransactionResponse(JsonDoc root) { } HydrateHostData(responseData); HydratePaymentData(responseData); + HydrateTransactionData(responseData); HydrateEmvData(responseData); + HydrateDccData(responseData); + HydrateHeaderData(responseData); + } + else { + RequestId = root.GetValue("id"); + TransactionId = RequestId; + DeviceResponseText = root.GetValue("status"); + ResponseText = root.Get("action").GetValue("result_code"); + DeviceResponseCode = ResponseText; + } } private bool isGpApiResponse(JsonDoc root) { @@ -108,27 +122,10 @@ protected void HydrateHostData(JsonDoc data) { ResponseText = host.GetValue("responseText"); ApprovalCode = host.GetValue("approvalCode"); ReferenceNumber = host.GetValue("referenceNumber"); - - AvsResponseCode = host.GetValue("AvsResultCode"); - - if (string.IsNullOrEmpty(AvsResponseCode)) - AvsResponseCode = "0"; - - CvvResponseCode = host.GetValue("CvvResultCode"); - - if (string.IsNullOrEmpty(CvvResponseCode)) - CvvResponseCode = "0"; - - AvsResponseText = host.GetValue("AvsResultText"); - - if (string.IsNullOrEmpty(AvsResponseText)) - AvsResponseText = "AVS Not Requested."; - - CvvResponseText = host.GetValue("CvvResultText"); - - if (string.IsNullOrEmpty(CvvResponseText)) - CvvResponseText = "CVV Not Requested."; - + AvsResponseCode = host.GetValue("avsResultCode"); + CvvResponseCode = host.GetValue("cvvResultCode"); + AvsResponseText = host.GetValue("avsResultText"); + CvvResponseText = host.GetValue("cvvResultText"); // AdditionalTipAmount = host.GetValue("additionalTipAmount"); // BaseAmount = host.GetValue("baseAmount"); TipAmount = host.GetValue("tipAmount"); @@ -138,7 +135,9 @@ protected void HydrateHostData(JsonDoc data) { TransactionAmount = host.GetValue("totalAmount"); MerchantFee = host.GetValue("surcharge"); Token = host.GetValue("tokenValue"); - if(host.GetValue("cardBrandTransId") != null) + TokenResponseCode = host.GetValue("tokenRspCode"); + TokenResponseMessage = host.GetValue("tokenRspMsg"); + if (host.GetValue("cardBrandTransId") != null) CardBrandTransId = host.GetValue("cardBrandTransId"); // TxnDescriptor = host.GetValue("txnDescriptor"); // RecurringDataCode = host.GetValue("recurringDataCode"); @@ -181,6 +180,76 @@ protected void HydratePaymentData(JsonDoc data) { ExpirationDate = payment.GetValue("expiryDate"); } + protected void HydrateHeaderData(JsonDoc headerData) { + if (headerData == null) { + return; + } + var acquisitionType = headerData.GetValue("acquisitionType"); + var LuhnCheckPassed = headerData.GetValue("LuhnCheckPassed"); + var dataEncryptionType = headerData.GetValue("dataEncryptionType"); + var EmvTags = headerData.GetValue("EmvTags"); + var expDate = headerData.GetValue("expDate"); + var Cvv = headerData.GetValue("Cvv"); + var ScannedData = headerData.GetValue("ScannedData"); + HydratePanData(headerData); + HydrateTrackData(headerData); + HydratePinDUKPTData(headerData); + Hydrate3DesDukptData(headerData); + } + + protected void HydratePanData(JsonDoc data) { + var panData = data.Get("Pan"); + if (panData == null) { + return; + } + var clearPAN = panData.GetValue("clearPAN"); + var maskedPAN = panData.GetValue("maskedPAN"); + var encryptedPAN = panData.GetValue("encryptedPAN"); + } + + protected void HydrateTrackData(JsonDoc data) { + var trackData = data.Get("trackData"); + if (trackData == null) { + return; + } + var clearTrack2 = trackData.GetValue("clearTrack2"); + var maskedTrack2 = trackData.GetValue("maskedTrack2"); + var clearTrack1 = trackData.GetValue("clearTrack1"); + var maskedTrack1 = trackData.GetValue("maskedTrack1"); + var clearTrack3 = trackData.GetValue("clearTrack3"); + var maskedTrack3 = trackData.GetValue("maskedTrack3"); + } + + protected void HydratePinDUKPTData(JsonDoc data) { + var pinDUKPT = data.Get("PinDUKPT"); + if (pinDUKPT == null) { + return; + } + var pinBlock = pinDUKPT.GetValue("PinBlock"); + var Ksn = pinDUKPT.GetValue("Ksn"); + } + + protected void Hydrate3DesDukptData(JsonDoc data) { + var desDukpt = data.Get("3DesDukpt"); + if (desDukpt == null) { + return; + } + var encryptedBlob = desDukpt.GetValue("encryptedBlob"); + var Ksn = desDukpt.GetValue("Ksn"); + } + + protected void HydrateTransactionData(JsonDoc data) + { + var transaction = data.Get("transaction"); + if (transaction == null) + { + return; + } + TipAmount = transaction.GetValue("tipAmount"); + TransactionAmount = transaction.GetValue("totalAmount"); + BaseAmount = transaction.GetValue("baseAmount"); + } + protected void HydrateEmvData(JsonDoc data) { var emv = data.Get("emv"); if (emv == null) { @@ -228,6 +297,16 @@ protected void HydrateEmvData(JsonDoc data) { // TacOnline = emv.TacOnline; } + protected void HydrateDccData(JsonDoc data) { + var dcc = data.Get("dcc"); + if (dcc == null) + return; + ExchangeRate = dcc.GetValue("exchangeRate"); + MarkUp = dcc.GetValue("markUp"); + TransactionCurrency = dcc.GetValue("transactionCurrency"); + DccTransactionAmount = dcc.GetValue("transactionAmount"); + } + protected string NormalizeResponseCode(string responseCode, string partialApproval) { if (partialApproval == "1") { return "10"; @@ -250,12 +329,16 @@ private string ConvertHEX(string hexString) { return retValue; } - - public static TransactionResponse ParseResponse(string rawResponse) { + public static TransactionResponse ParseResponse(string rawResponse) + { JsonDoc response = JsonDoc.Parse(rawResponse); // TODO: We might have to scope the document down depending on what response we actually get from the message endpoint return new TransactionResponse(response); } - + public decimal? ExchangeRate { get; set; } + public decimal? MarkUp { get; set; } + public string TransactionCurrency { get; set; } + public string DccTransactionAmount { get; set; } } } + diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs index 43468276..33186310 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs @@ -23,8 +23,9 @@ public UpaEODResponse(JsonDoc root) { RequestId = firstDataNode.GetValue("requestId"); // Log error info if it's there - DeviceResponseCode = cmdResult.GetValue("errorCode"); - DeviceResponseText = cmdResult.GetValue("errorMessage"); + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; // Unlike in other response types, this data should always be here, even if the Status is "Failed" var secondDataNode = firstDataNode.Get("data"); diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaGiftCardResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaGiftCardResponse.cs new file mode 100644 index 00000000..70b2a600 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaGiftCardResponse.cs @@ -0,0 +1,110 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.UPA.Responses +{ + internal class UpaGiftCardResponse: DeviceResponse + { + const string INVALID_RESPONSE_FORMAT = "The response received is not in the proper format."; + + public string AcquisitionType { get; set; } + public string LuhnCheckPassed { get; set; } + public string DataEncryptionType { get; set; } + public Pan Pan { get; set; } + public TrackData TrackData { get; set; } + public string EmvTags { get; set; } + public string ExpDate { get; set; } + public int Cvv { get; set; } + public string ScannedData { get; set; } + public object PinDUKPT { get; set; } + public ThreeDesDukpt ThreeDesDukpt { get; set; } + + + public UpaGiftCardResponse(JsonDoc root) + { + string test = root.ToString(); + var firstDataNode = root.Get("data"); + if (firstDataNode == null) + { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + + var cmdResult = firstDataNode.Get("cmdResult"); + if (cmdResult == null) + { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + + Status = cmdResult.GetValue("result"); + if (string.IsNullOrEmpty(Status)) + { + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + } + else + { + // If the Status is not "Success", there is either nothing to process, or something else went wrong. + // Skip the processing of the rest of the message, as we'll likely hit null reference exceptions + if (Status == "Success") + { + var secondDataNode = firstDataNode.Get("data"); + if (secondDataNode == null) + { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + AcquisitionType = secondDataNode.GetValue("acquisitionType"); + var threedesDukpt = secondDataNode.Get("3DesDukpt"); + ThreeDesDukpt = new ThreeDesDukpt(); + ThreeDesDukpt.EncryptedBlob = threedesDukpt.GetValue("encryptedBlob"); + ThreeDesDukpt.Ksn = threedesDukpt.GetValue("Ksn"); + DataEncryptionType = secondDataNode.GetValue("dataEncryptionType"); + EmvTags = secondDataNode.GetValue("EmvTags"); + DeviceResponseCode = "00"; + DeviceResponseText = "Success"; + } + else + { + // the only other option is "Failed" + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + } + } + + } + } + + public class PinDUKPT + { + public string Pinblock { get; set; } + public string Ksn { get; set; } + } + + public class ThreeDesDukpt + { + public string EncryptedBlob { get; set; } + public string Ksn { get; set; } + } + + public class Pan + { + public string ClearPAN { get; set; } + public string MaskedPAN { get; set; } + public string EncryptedPAN { get; set; } + } + + public class TrackData + { + public string ClearTrack2 { get; set; } + public string MaskedTrack2 { get; set; } + public string ClearTrack1 { get; set; } + public string MaskedTrack1 { get; set; } + public string ClearTrack3 { get; set; } + public string MaskedTrack3 { get; set; } + } +} diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSAFResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSAFResponse.cs index 288e6b05..96288667 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSAFResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSAFResponse.cs @@ -69,6 +69,7 @@ public UpaSAFResponse(JsonDoc root) { TransactionType = record.GetValue("transactionType"), TerminalRefNumber = record.GetValue("transId"), // The sample XML says tranNo? ReferenceNumber = record.GetValue("referenceNumber"), + SafReferenceNumber = record.GetValue("safReferenceNumber"), GratuityAmount = record.GetValue("tipAmount"), TaxAmount = record.GetValue("taxAmount"), Amount = record.GetValue("baseAmount"), diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSignatureResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSignatureResponse.cs new file mode 100644 index 00000000..2d1ca498 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaSignatureResponse.cs @@ -0,0 +1,50 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System; + +namespace GlobalPayments.Api.Terminals.UPA.Responses { + public class UpaSignatureResponse : DeviceResponse, ISignatureResponse { + + public byte[] SignatureData { get; set; } + const string INVALID_RESPONSE_FORMAT = "The response received is not in the proper format."; + + public UpaSignatureResponse(JsonDoc root) { + var firstDataNode = root.Get("data"); + if (firstDataNode == null) { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + + var cmdResult = firstDataNode.Get("cmdResult"); + if (cmdResult == null) { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + + Status = cmdResult.GetValue("result"); + if (string.IsNullOrEmpty(Status)) { + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + } + else { + // If the Status is not "Success", there is either nothing to process, or something else went wrong. + // Skip the processing of the rest of the message, as we'll likely hit null reference exceptions + if (Status == "Success") { + var secondDataNode = firstDataNode.Get("data"); + if (secondDataNode == null) { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + var signatureData = secondDataNode.GetValue("signatureData"); + SignatureData = Convert.FromBase64String(signatureData); + } + else { + // the only other option is "Failed" + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + } + } + + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs index e166a84b..cbbd638a 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaController.cs @@ -6,13 +6,10 @@ using System; using System.Text; using System.Text.RegularExpressions; -using log4net; namespace GlobalPayments.Api.Terminals.UPA { public class UpaController : DeviceController { - private readonly ILog _logger = LogManager.GetLogger(typeof(UpaController)); - internal override IDeviceInterface ConfigureInterface() { if (_interface == null) { _interface = new UpaInterface(this); @@ -51,9 +48,6 @@ internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { string jsonObject = Encoding.UTF8.GetString(response); - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - var jsonParse = JsonDoc.Parse(jsonObject); switch (builder.ReportType) { @@ -67,27 +61,20 @@ internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { } } - private bool IsTokenRequestApplicable(bool isTipAdjust, TransactionType transactionType) { - if (isTipAdjust) { - return true; - } - else { - switch (transactionType) { - case TransactionType.Tokenize: - case TransactionType.Refund: - return true; - default: - return false; - } - } + private bool IsTokenRequestApplicable(TransactionType transactionType) { + switch (transactionType) { + case TransactionType.Tokenize: + case TransactionType.Refund: + return true; + default: + return false; + } } internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { var pmt = builder.PaymentMethodType; var transType = builder.TransactionType; var transModifier = builder.TransactionModifier; - bool isTipAdjust = IsTipAdjust(transType, builder.Gratuity); - bool isCardHSAFSA = IsCardHSAFSA(builder.AutoSubstantiation); if (pmt != PaymentMethodType.Credit && pmt != PaymentMethodType.Debit && pmt != PaymentMethodType.EBT) { throw new UnsupportedTransactionException("The supplied payment method type is not supported"); @@ -103,7 +90,7 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { var baseRequest = doc.SubElement("data"); baseRequest.Set("command", MapTransactionType(transType, transModifier, builder.RequestMultiUseToken, builder.Gratuity)); - baseRequest.Set("EcrId", builder.EcrId); + baseRequest.Set("EcrId", builder.EcrId.ToString()); baseRequest.Set("requestId", requestId.ToString()); if (transType != TransactionType.Balance) { @@ -111,10 +98,8 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { var txnParams = txnData.SubElement("params"); txnParams.Set("clerkId", builder.ClerkId); - if (transType == TransactionType.Tokenize || transType == TransactionType.Verify) { - txnParams.Set("cardIsHSAFSA", isCardHSAFSA ? "1" : "0"); - } - if (!IsTokenRequestApplicable(isTipAdjust, transType)) { + + if (!IsTokenRequestApplicable(transType)) { txnParams.Set("tokenRequest", builder.RequestMultiUseToken ? "1" : "0"); } if (builder.PaymentMethod is CreditCardData) { @@ -129,17 +114,19 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { } txnParams.Set("lineItemLeft", builder.LineItemLeft); txnParams.Set("lineItemRight", builder.LineItemRight); - + if (transType == TransactionType.Auth) + txnParams.Set("invoiceNbr", builder.InvoiceNumber); if (builder.ShippingDate != DateTime.MinValue && builder.InvoiceNumber != null) { txnParams.Set("directMktInvoiceNbr", builder.InvoiceNumber); txnParams.Set("directMktShipMonth", builder.ShippingDate.Month.ToString("00")); txnParams.Set("directMktShipDay", builder.ShippingDate.Day.ToString("00")); } - if (transType != TransactionType.Verify && transType != TransactionType.Refund && !isTipAdjust && transType != TransactionType.Tokenize) { + if (transType != TransactionType.Verify && transType != TransactionType.Refund && transType != TransactionType.Tokenize) { var transaction = txnData.SubElement("transaction"); if (transType == TransactionType.Auth) { transaction.Set("amount", ToCurrencyString(builder.Amount)); + transaction.Set("preAuthAmount", ToCurrencyString(builder.PreAuthAmount)); } else { transaction.Set("baseAmount", ToCurrencyString(builder.Amount)); @@ -152,13 +139,13 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { } transaction.Set("referenceNumber", builder.TerminalRefNumber); - transaction.Set("cardIsHSAFSA", isCardHSAFSA ? "1" : "0"); transaction.Set("prescriptionAmount", ToCurrencyString(builder.PrescriptionAmount)); transaction.Set("clinicAmount", ToCurrencyString(builder.ClinicAmount)); transaction.Set("dentalAmount", ToCurrencyString(builder.DentalAmount)); transaction.Set("visionOpticalAmount", ToCurrencyString(builder.VisionOpticalAmount)); + transaction.Set("cardAcquisition", EnumConverter.GetMapping(Target.UPA, builder.CardAcquisition)); } if (transType == TransactionType.Refund) { @@ -166,14 +153,7 @@ internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { transaction.Set("totalAmount", ToCurrencyString(builder.Amount)); transaction.Set("invoiceNbr", builder.InvoiceNumber); transaction.Set("referenceNumber", builder.TerminalRefNumber); - } - - if (isTipAdjust) { - var transaction = txnData.SubElement("transaction"); - transaction.Set("tranNo", builder.TerminalRefNumber); - transaction.Set("tipAmount", ToCurrencyString(builder.Gratuity)); - transaction.Set("invoiceNbr", builder.InvoiceNumber); - } + } } return TerminalUtilities.BuildUpaRequest(doc); @@ -208,6 +188,8 @@ internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { var transType = builder.TransactionType; var transModifier = builder.TransactionModifier; int requestId = builder.ReferenceNumber; + bool isTipAdjust = IsTipAdjust(transType, builder.Gratuity); + if (requestId == default(int) && RequestIdProvider != null) { requestId = RequestIdProvider.GetRequestId(); } @@ -218,19 +200,29 @@ internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { var baseRequest = doc.SubElement("data"); // Possibly update the requestToken parameter in the future if necessary baseRequest.Set("command", MapTransactionType(transType, transModifier, false, builder.Gratuity)); - baseRequest.Set("EcrId", builder.EcrId); + baseRequest.Set("EcrId", builder.EcrId.ToString()); baseRequest.Set("requestId", requestId.ToString()); var txnData = baseRequest.SubElement("data"); var transaction = txnData.SubElement("transaction"); - transaction.Set("referenceNumber", builder.TransactionId ?? StringUtils.PadLeft(builder.TerminalRefNumber, 4, '0')); - transaction.Set("amount", ToCurrencyString(builder.Amount)); - transaction.Set("taxAmount", ToCurrencyString(builder.TaxAmount)); - transaction.Set("tipAmount", ToCurrencyString(builder.Gratuity)); - transaction.Set("taxIndicator", builder.TaxExempt); - transaction.Set("invoiceNbr", builder.InvoiceNumber); - transaction.Set("processCPC", builder.ProcessCPC); + if (isTipAdjust) + { + transaction.Set("tranNo", builder.TerminalRefNumber); + transaction.Set("tipAmount", ToCurrencyString(builder.Gratuity)); + transaction.Set("invoiceNbr", builder.InvoiceNumber); + } + else + { + transaction.Set("referenceNumber", builder.TransactionId ?? StringUtils.PadLeft(builder.TerminalRefNumber, 4, '0')); + transaction.Set("amount", ToCurrencyString(builder.Amount)); + transaction.Set("taxAmount", ToCurrencyString(builder.TaxAmount)); + transaction.Set("tipAmount", ToCurrencyString(builder.Gratuity)); + transaction.Set("taxIndicator", builder.TaxExempt); + transaction.Set("invoiceNbr", builder.InvoiceNumber); + transaction.Set("processCPC", builder.ProcessCPC); + } + return TerminalUtilities.BuildUpaRequest(doc.ToString()); } @@ -266,9 +258,6 @@ internal TransactionResponse DoTransaction(IDeviceMessage request) { var jsonObject = Encoding.UTF8.GetString(response); - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - var jsonParse = JsonDoc.Parse(jsonObject); return new TransactionResponse(jsonParse); @@ -330,20 +319,7 @@ private string MapReportType(TerminalReportType type) { bool IsTipAdjust(TransactionType transType, decimal? gratuity) { return (transType == TransactionType.Edit && (gratuity != null || gratuity > 0m)); - } - - private bool IsCardHSAFSA(AutoSubstantiation autoSubstantiation) { - if (autoSubstantiation != null) { - return (autoSubstantiation.PrescriptionSubTotal > 0m || - autoSubstantiation.ClinicSubTotal > 0m || - autoSubstantiation.DentalSubTotal > 0m || - autoSubstantiation.VisionSubTotal > 0m || - autoSubstantiation.CopaySubTotal > 0m || - autoSubstantiation.TotalHealthcareAmount > 0m); - } - else - return false; - } + } protected string ToCurrencyString(decimal? dec) { if (!dec.HasValue) { diff --git a/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs index 5776b22e..3c40adb5 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaInterface.cs @@ -3,17 +3,15 @@ using GlobalPayments.Api.Terminals.Builders; using GlobalPayments.Api.Utils; using System.Text; -using log4net; +using Newtonsoft.Json; namespace GlobalPayments.Api.Terminals.UPA { public class UpaInterface : DeviceInterface, IDeviceInterface { - private readonly ILog _logger = LogManager.GetLogger(typeof(UpaInterface)); - internal UpaInterface(UpaController controller) : base(controller) { } - public override TerminalAuthBuilder TipAdjust(decimal? amount) { - return new TerminalAuthBuilder(TransactionType.Edit, PaymentMethodType.Credit) + public override TerminalManageBuilder TipAdjust(decimal? amount) { + return new TerminalManageBuilder(TransactionType.Edit, PaymentMethodType.Credit) .WithGratuity(amount); } @@ -34,11 +32,7 @@ public override TerminalManageBuilder DeletePreAuth() public override IEODResponse EndOfDay() { var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.EodProcessing)); - string jsonObject = Encoding.UTF8.GetString(response); - - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - + var jsonObject = Encoding.UTF8.GetString(response); var jsonParse = JsonDoc.Parse(jsonObject); return new UpaEODResponse(jsonParse); } @@ -46,42 +40,41 @@ public override IEODResponse EndOfDay() { public override IDeviceResponse Reboot() { var requestId = _controller.GetRequestId(); var response = _controller.Send(TerminalUtilities.BuildUpaAdminRequest(requestId, EcrId, UpaTransType.Reboot)); - string jsonObject = Encoding.UTF8.GetString(response); - - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - - 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); - - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - + var jsonObject = Encoding.UTF8.GetString(response); var jsonParse = JsonDoc.Parse(jsonObject); return new TransactionResponse(jsonParse); } 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); + var jsonObject = Encoding.UTF8.GetString(response); - if(_logger.IsDebugEnabled) - _logger.Debug($"Raw Response: {jsonObject}"); - - JsonDoc doc = JsonDoc.Parse(jsonObject); + var doc = JsonDoc.Parse(jsonObject); return new UpaSAFResponse(doc); } @@ -100,6 +93,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/UpaTransType.cs b/src/GlobalPayments.Api/Terminals/UPA/UpaTransType.cs index 23e7a20f..d3753810 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/UpaTransType.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/UpaTransType.cs @@ -1,7 +1,5 @@ -namespace GlobalPayments.Api.Terminals.UPA -{ - internal class UpaTransType - { +namespace GlobalPayments.Api.Terminals.UPA { + internal class UpaTransType { public const string SALE_REDEEM = "Sale"; public const string Void = "Void"; public const string Refund = "Refund"; @@ -20,5 +18,10 @@ internal class UpaTransType public const string AuthCompletion = "AuthCompletion"; public const string DeletePreAuth = "DeletePreAuth"; public const string GetOpenTabDetails = "GetOpenTabDetails"; + public const string StartCardTransaction = "StartCardTransaction"; + public const string DeleteSAF = "DeleteSAF"; + public const string RegisterPOS = "RegisterPOS"; + public const string GetSignature = "GetSignature"; + } } diff --git a/src/GlobalPayments.Api/Utils/EnumUtils.cs b/src/GlobalPayments.Api/Utils/EnumUtils.cs index 15bb8c04..92ffdbd5 100644 --- a/src/GlobalPayments.Api/Utils/EnumUtils.cs +++ b/src/GlobalPayments.Api/Utils/EnumUtils.cs @@ -63,6 +63,27 @@ public static T FromDescription(object value) { return default(T); } + /// + /// Get an enum when the value represented in the enum class is a char through a string + /// + /// + /// + /// + public static T FromCharToObject(string value) { + if (!string.IsNullOrEmpty(value)) { + return (T)Enum.ToObject(typeof(T), Char.Parse(value)); + } + return default(T); + } + + + public static T GetEnumFromValue(string value) { + if (!string.IsNullOrEmpty(value)) { + return (T)Enum.Parse(typeof(T), value); + } + return default(T); + } + public static string GetMapping(Target target, object value) { if (value is Enum) { var mappings = value.GetType().GetRuntimeField(value.ToString()).GetCustomAttributes(); diff --git a/src/GlobalPayments.Api/Utils/Extensions.cs b/src/GlobalPayments.Api/Utils/Extensions.cs index 5979761b..1b64facd 100644 --- a/src/GlobalPayments.Api/Utils/Extensions.cs +++ b/src/GlobalPayments.Api/Utils/Extensions.cs @@ -32,10 +32,11 @@ public static string RemoveInitialZero(this string amount) { } return amount; } - - public static string ToCurrencyString(this decimal? dec) { + + public static string ToCurrencyString(this decimal? dec, bool withoutThousandsSign = false) { if (dec != null) { - return Regex.Replace(string.Format("{0:c}", dec), "[^0-9.,]", ""); + var patternString = !(withoutThousandsSign) ? "[^0-9.,]" : "[^0-9.]"; + return Regex.Replace(string.Format("{0:c}", dec), patternString, ""); } return null; } diff --git a/src/GlobalPayments.Api/Utils/GenerationUtils.cs b/src/GlobalPayments.Api/Utils/GenerationUtils.cs index 3f5ba91a..7b357757 100644 --- a/src/GlobalPayments.Api/Utils/GenerationUtils.cs +++ b/src/GlobalPayments.Api/Utils/GenerationUtils.cs @@ -125,12 +125,25 @@ public static string GenerateHash(string secret, params string[] fields) { return GenerateHash(toHash, secret); } - public static string GenerateHash(string secret, ShaHashType shaType = ShaHashType.SHA1, params string[] fields) - { + public static string GenerateHash(string secret, ShaHashType shaType = ShaHashType.SHA1, params string[] fields) { var toHash = string.Join(".", fields); return GenerateHash(toHash, secret, shaType); } + public static string HMACSHA256Hash(string data, string secretKey) { + var secret = Encoding.UTF8.GetBytes(secretKey); + // Initialize the keyed hash object. + using (HMACSHA256 hmac = new HMACSHA256(secret)) { + var dataConverted = Encoding.UTF8.GetBytes(data); + + // Compute the hash of the input file. + byte[] hashValue = hmac.ComputeHash(dataConverted); + // Reset inStream to the beginning of the file. + + return BitConverter.ToString(hashValue).Replace("-", "").ToLower(); + } + } + /// /// Generate the current datetimestamp in the string formaat (YYYYMMDDHHSS) required in a /// request to Realex. diff --git a/src/GlobalPayments.Api/Utils/JsonUtils.cs b/src/GlobalPayments.Api/Utils/JsonUtils.cs index 3cac36fe..3a1d8716 100644 --- a/src/GlobalPayments.Api/Utils/JsonUtils.cs +++ b/src/GlobalPayments.Api/Utils/JsonUtils.cs @@ -172,6 +172,16 @@ public static JsonDoc Parse(string json, IRequestEncoder encoder = null) { return null; } + public static bool IsJson(string json) { + try { + var parsed = JsonConvert.DeserializeObject(json); + return true; + } + catch (Exception) { + return false; + } + } + public static T ParseSingleValue(string json, string name, IRequestEncoder encoder = null) { var doc = Parse(json, encoder); return doc.GetValue(name); From 144cda0fdaaabf4352a52f252150ff8059e26179 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Fri, 12 Jan 2024 12:17:38 -0700 Subject: [PATCH 17/19] Cleaned up logging --- .../Terminals/UPA/Interfaces/UpaTcpInterface.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index 1bbbce62..e671cd76 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -134,11 +134,11 @@ EventHandler ClientOnMessage() { if (a.Exception == null) { - _logger.Debug($"Client: {a.Message}"); + _logger.Debug($"Tcp Client: {a.Message}"); } else { - _logger.Error($"Client: {a.Message}", a.Exception); + _logger.Error($"Tcp Client: {a.Message}", a.Exception); } }; } From 8312cbeb4e083dd202bdeec809572752ad8febd3 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Wed, 10 Apr 2024 11:17:44 -0700 Subject: [PATCH 18/19] Better error handling --- .../UPA/Interfaces/UpaTcpInterface.cs | 11 ++- .../UPA/Responses/TransactionResponse.cs | 8 +- .../Terminals/UPA/Responses/UpaEODResponse.cs | 76 +++++++++++-------- .../Terminals/UPA/UpaMessageType.cs | 1 + 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index e671cd76..3c8a8b8b 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using GlobalPayments.Api.Terminals.PAX; using log4net; namespace GlobalPayments.Api.Terminals.UPA @@ -41,6 +42,8 @@ public byte[] Send(IDeviceMessage deviceMessage) { var token = _tokenSource.Token; + var requestId = deviceMessage.GetRequestBuilder().GetValue("data")?.GetValue("requestId"); + var tcs = new TaskCompletionSource(); var serverIsBusy = false; @@ -102,6 +105,12 @@ public byte[] Send(IDeviceMessage deviceMessage) 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."); } @@ -164,7 +173,7 @@ private string GetResponseMessageType(byte[] response) catch (Exception ex) { _logger.Error($"{ex.Message} : Response - {jsonObject}"); - throw; + return UpaMessageType.Error; } } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs index 2cc1f5fb..cfb376fc 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/TransactionResponse.cs @@ -86,11 +86,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"); } } diff --git a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs index 33186310..8d74ec29 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Responses/UpaEODResponse.cs @@ -7,43 +7,53 @@ public class UpaEODResponse : IEODResponse { const string INVALID_RESPONSE_FORMAT = "The response received is not in the proper format."; public UpaEODResponse(JsonDoc root) { - var firstDataNode = root.Get("data"); - if (firstDataNode == null) { - throw new MessageException(INVALID_RESPONSE_FORMAT); + if (!isGpApiResponse(root)) { + var firstDataNode = root.Get("data"); + if (firstDataNode == null) { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + + var cmdResult = firstDataNode.Get("cmdResult"); + if (cmdResult == null) { + 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 + var errorCode = cmdResult.GetValue("errorCode"); + var errorMsg = cmdResult.GetValue("errorMessage"); + DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; + + // Unlike in other response types, this data should always be here, even if the Status is "Failed" + var secondDataNode = firstDataNode.Get("data"); + if (secondDataNode == null) { + throw new MessageException(INVALID_RESPONSE_FORMAT); + } + Multiplemessage = secondDataNode.GetValue("multipleMessage"); + + var host = secondDataNode.Get("host"); + + if (host != null) { + RespDateTime = host.GetValue("respDateTime"); + BatchId = host.GetValue("batchId"); + GatewayResponseCode = host.GetValue("gatewayResponseCode"); + GatewayResponseMessage = host.GetValue("gatewayResponseMessage"); + } } - - var cmdResult = firstDataNode.Get("cmdResult"); - if (cmdResult == null) { - throw new MessageException(INVALID_RESPONSE_FORMAT); - } - - Status = cmdResult.GetValue("result"); - - EcrId = firstDataNode.GetValue("EcrId"); - RequestId = firstDataNode.GetValue("requestId"); - - // Log error info if it's there - var errorCode = cmdResult.GetValue("errorCode"); - var errorMsg = cmdResult.GetValue("errorMessage"); - DeviceResponseText = $"Error: {errorCode} - {errorMsg}"; - - // Unlike in other response types, this data should always be here, even if the Status is "Failed" - var secondDataNode = firstDataNode.Get("data"); - if (secondDataNode == null) { - throw new MessageException(INVALID_RESPONSE_FORMAT); + else { + RequestId = root.GetValue("id"); + DeviceResponseText = root.GetValue("status"); + DeviceResponseCode = root.Get("action").GetValue("result_code"); ; } - Multiplemessage = secondDataNode.GetValue("multipleMessage"); - - var host = secondDataNode.Get("host"); + } - if (host != null) { - RespDateTime = host.GetValue("respDateTime"); - BatchId = host.GetValue("batchId"); - GatewayResponseCode = host.GetValue("gatewayResponseCode"); - GatewayResponseMessage = host.GetValue("gatewayResponseMessage"); - } + private bool isGpApiResponse(JsonDoc root) { + return !root.Has("data"); } + public int RequestId { get; set; } public string Multiplemessage { get; set; } public IDeviceResponse AttachmentResponse { get; set; } @@ -66,7 +76,7 @@ public UpaEODResponse(JsonDoc root) { public string RespDateTime { get; set; } public int BatchId { get; set; } public string EcrId { get; set; } - public int RequestId { get; set; } + public int GatewayResponseCode { get; set; } public string GatewayResponseMessage { get; set; } 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"; } } From 53c76c7e04836f42124b315d2cb17bfb86f2f054 Mon Sep 17 00:00:00 2001 From: "Rose, Michael" Date: Thu, 11 Apr 2024 11:30:21 -0700 Subject: [PATCH 19/19] Post Merge Fixes --- src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj | 1 + src/GlobalPayments.Api/Terminals/DeviceInterface.cs | 10 ++++++++-- .../Terminals/UPA/Interfaces/UpaTcpInterface.cs | 3 --- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj index 9175121b..44475492 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.4.6.csproj @@ -226,6 +226,7 @@ + diff --git a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs index c6097136..b2930057 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs @@ -113,8 +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/UPA/Interfaces/UpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs index c6322e5a..d238d7dc 100644 --- a/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/UPA/Interfaces/UpaTcpInterface.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using GlobalPayments.Api.Terminals.PAX; using log4net; namespace GlobalPayments.Api.Terminals.UPA @@ -21,8 +20,6 @@ internal class UpaTcpInterface : IDeviceCommInterface public event MessageSentEventHandler OnMessageSent; public event MessageReceivedEventHandler OnMessageReceived; - public event MessageReceivedEventHandler OnMessageReceived; - public UpaTcpInterface(ITerminalConfiguration settings) { _settings = settings;