diff --git a/src/GlobalPayments.Api/Entities/Enums.cs b/src/GlobalPayments.Api/Entities/Enums.cs index 77ae4d74..ae18da1b 100644 --- a/src/GlobalPayments.Api/Entities/Enums.cs +++ b/src/GlobalPayments.Api/Entities/Enums.cs @@ -55,7 +55,32 @@ public enum DeviceType { /// /// Indicates a genius terminal /// - GENIUS + GENIUS, + + /// + /// Indicates an Ingenico Desk/5000 terminal underlying in Epos software package. + /// + Ingenico_EPOS_Desk5000, + + /// + /// Indicates an Ingenico Lane/3000 terminal underlying in Epos software package. + /// + Ingenico_EPOS_Lane3000, + + /// + /// Indicates an Ingenico Move/3500 terminal underlying in Epos software package. + /// + Ingenico_EPOS_Move3500 + } + + /// + /// Indicates a device mode. + /// + public enum DeviceMode { + /// + /// For devices that supports Pay@Table functionalities. + /// + PAY_AT_TABLE } /// diff --git a/src/GlobalPayments.Api/GlobalPayments.Api.csproj b/src/GlobalPayments.Api/GlobalPayments.Api.csproj index 468dde4a..deaa26ea 100644 --- a/src/GlobalPayments.Api/GlobalPayments.Api.csproj +++ b/src/GlobalPayments.Api/GlobalPayments.Api.csproj @@ -2,7 +2,7 @@ 3.0.0 - netstandard1.3 + netstandard2.0 GlobalPayments.Api GlobalPayments.Api 1.6.1 @@ -18,7 +18,9 @@ - + + + @@ -29,4 +31,8 @@ + + + + diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs index 2113d5bb..2200cb9b 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceCommInterface.cs @@ -9,5 +9,9 @@ public interface IDeviceCommInterface { byte[] Send(IDeviceMessage message); event MessageSentEventHandler OnMessageSent; + + event BroadcastMessageEventHandler OnBroadcastMessage; + + event PayAtTableRequestEventHandler OnPayAtTableRequest; } } diff --git a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs index 17ec5cf4..8f566fb7 100644 --- a/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Abstractions/IDeviceInterface.cs @@ -2,14 +2,23 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Ingenico; using GlobalPayments.Api.Terminals.Messaging; namespace GlobalPayments.Api.Terminals { public interface IDeviceInterface : IDisposable { event MessageSentEventHandler OnMessageSent; + event BroadcastMessageEventHandler OnBroadcastMessage; + event PayAtTableRequestEventHandler OnPayAtTableRequest; #region Admin Calls - void Cancel(); + + /// + /// A method to Cancel a live transaction. + /// + /// Amount to be passed for cancel request. + /// TerminalManageBuilder + IDeviceResponse Cancel(); IDeviceResponse CloseLane(); IDeviceResponse DisableHostResponseBeep(); ISignatureResponse GetSignatureFile(); @@ -24,10 +33,44 @@ public interface IDeviceInterface : IDisposable { ISAFResponse SendStoreAndForward(); IDeviceResponse SetStoreAndForwardMode(bool enabled); IDeviceResponse StartCard(PaymentMethodType paymentMethodType); + + /// + /// The terminal immediately initiates a duplicate of the last completed transaction + /// + /// IDeviceResponse + IDeviceResponse Duplicate(); + + /// + /// Command used to gain feedback as to the status of the terminal. + /// + /// + IDeviceResponse GetTerminalStatus(); + + /// + /// Command used to request for CALL TMS in the terminal. + /// + /// IDeviceResponse + IDeviceResponse GetTerminalConfiguration(); + + IDeviceResponse TestConnection(); #endregion #region reporting TerminalReportBuilder LocalDetailReport(); + /// + /// Used to request the XML data for the last completed report that is stored in the terminal’s memory + /// + /// Receipt Type + /// + TerminalReportBuilder GetLastReceipt(ReceiptType type = ReceiptType.TICKET); + + /// + /// Instruct the terminal to initiate report and stores it in terminal's memory. + /// GetLastReceipt can be used to extract XML data after. + /// + /// Report Type + /// + TerminalReportBuilder GetReport(Ingenico.ReportType type); #endregion #region Batch Calls @@ -65,14 +108,59 @@ public interface IDeviceInterface : IDisposable { #region Generic Calls TerminalAuthBuilder AddValue(decimal? amount = null); + + /// + /// Instructs the terminal to transact a pre-authorization transaction. + /// + /// Amount + /// TerminalAuthBuilder Authorize(decimal? amount = null); TerminalAuthBuilder Balance(); TerminalManageBuilder Capture(decimal? amount = null); + + /// + /// Instruct the terminal to refund the last completed transaction. + /// + /// Refund Amount + /// TerminalAuthBuilder Refund(decimal? amount = null); + + /// + /// Instruct the terminal to process sale transaction. + /// + /// Sale Amount + /// TerminalAuthBuilder Sale(decimal? amount = null); + + /// + /// Verify the account of the card holder. + /// + /// TerminalAuthBuilder Verify(); TerminalManageBuilder Void(); TerminalAuthBuilder Withdrawal(decimal? amount = null); #endregion + + #region Terminal Management + + /// + /// The terminal immediately performs a reversal of the last completed transaction if no Transaction Id is set. + /// + /// Amount to be passed for cancel request. + /// TerminalManageBuilder + TerminalManageBuilder Reverse(decimal? amount = null); + + #endregion + + #region Pay@Table Feature + + /// + /// Response to terminal after Pay@Table request has been made. + /// + /// + /// + TerminalAuthBuilder PayAtTableResponse(); + + #endregion } } diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs index 99058151..70c6f8da 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalAuthBuilder.cs @@ -2,6 +2,7 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.PaymentMethods; using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; namespace GlobalPayments.Api.Terminals.Builders { public class TerminalAuthBuilder : TerminalBuilder { @@ -10,8 +11,9 @@ public class TerminalAuthBuilder : TerminalBuilder { internal decimal? Amount { get; set; } internal string AuthCode { get { - if (PaymentMethod is TransactionReference) + if (PaymentMethod is TransactionReference) { return (PaymentMethod as TransactionReference).AuthCode; + } return null; } } @@ -30,11 +32,19 @@ internal string AuthCode { internal string TaxExemptId { get; set; } internal string TransactionId { get { - if (PaymentMethod is TransactionReference) + if (PaymentMethod is TransactionReference) { return (PaymentMethod as TransactionReference).TransactionId; + } return null; } } + internal string CurrencyCode { get; set; } + internal PaymentMode PaymentMode { get; set; } + internal string TableNumber { get; set; } + internal TaxFreeType? TaxFreeType { get; set; } + internal PATPaymentMode? AdditionalMessage { get; set; } + internal PATResponseType? PayAtTableResponse { get; private set; } + public string FilePath { get; private set; } public TerminalAuthBuilder WithAddress(Address address) { Address = address; @@ -48,9 +58,16 @@ public TerminalAuthBuilder WithAmount(decimal? amount) { Amount = amount; return this; } + + /// + /// Sets the authorization code for the transaction. + /// + /// Authorization Code + /// public TerminalAuthBuilder WithAuthCode(string value) { - if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) + if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) { PaymentMethod = new TransactionReference(); + } (PaymentMethod as TransactionReference).AuthCode = value; return this; } @@ -64,6 +81,12 @@ public TerminalAuthBuilder WithAutoSubstantiation(AutoSubstantiation value) { AutoSubstantiation = value; return this; } + + /// + /// Sets the cash back for the transaction. + /// + /// + /// public TerminalAuthBuilder WithCashBack(decimal? amount) { CashBackAmount = amount; return this; @@ -126,9 +149,71 @@ public TerminalAuthBuilder WithTransactionId(string value) { return this; } + /// + /// Sets the currency code for the transaction. + /// + /// Currency Code + /// + public TerminalAuthBuilder WithCurrencyCode(string value) { + CurrencyCode = value; + return this; + } + + /// + /// Sets the payment mode for the transaction. + /// + /// Payment Mode + /// + public TerminalAuthBuilder WithPaymentMode(PaymentMode value) { + PaymentMode = value; + return this; + } + + public TerminalAuthBuilder WithPaymentMode(PATPaymentMode additionalMessage) { + AdditionalMessage = additionalMessage; + return this; + } + + /// + /// Sets the table number for the transaction. + /// + /// Table Number + /// + public TerminalAuthBuilder WithTableNumber(string value) { + TableNumber = value; + return this; + } + + /// + /// Method used for requesting a Tax Free Refund Payment type transaction. + /// + /// + /// Payment Type of refund. Either Cash or Credit + /// + /// + public TerminalAuthBuilder WithTaxFree(TaxFreeType taxFreeType) { + TaxFreeType = taxFreeType; + return this; + } + + public TerminalAuthBuilder WithPayAtTableResponseType(PATResponseType response) { + PayAtTableResponse = response; + return this; + } + + public TerminalAuthBuilder WithXMLPath(string filePath) { + FilePath = filePath; + return this; + } + internal TerminalAuthBuilder(TransactionType type, PaymentMethodType paymentType) : base(type, paymentType) { } + /// + /// Executes the transaction. + /// + /// + /// public override ITerminalResponse Execute(string configName = "default") { base.Execute(configName); diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs index 8fd725c4..06e2ab68 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalManageBuilder.cs @@ -1,6 +1,7 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.PaymentMethods; using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; namespace GlobalPayments.Api.Terminals.Builders { public class TerminalManageBuilder : TerminalBuilder { @@ -10,12 +11,32 @@ public class TerminalManageBuilder : TerminalBuilder { internal decimal? Gratuity { get; set; } internal string TransactionId { get { - if (PaymentMethod is TransactionReference) + if (PaymentMethod is TransactionReference) { return (PaymentMethod as TransactionReference).TransactionId; + } + return null; + } + } + internal string CurrencyCode { get; set; } + internal PaymentMode PaymentMode { get; set; } + internal string AuthCode { + get { + if (PaymentMethod is TransactionReference) + return (PaymentMethod as TransactionReference).AuthCode; return null; } } + /// + /// Sets the currency code for the transaction. + /// + /// Currency Code + /// + public TerminalManageBuilder WithCurrencyCode(string value) { + CurrencyCode = value; + return this; + } + public TerminalManageBuilder WithAmount(decimal? amount) { Amount = amount; return this; @@ -32,10 +53,25 @@ public TerminalManageBuilder WithGratuity(decimal? amount) { Gratuity = amount; return this; } + + /// + /// Sets the authorization code for the transaction. + /// + /// Authorization Code + /// + public TerminalManageBuilder WithAuthCode(string value) { + if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) { + PaymentMethod = new TransactionReference(); + } + (PaymentMethod as TransactionReference).AuthCode = value; + return this; + } + public TerminalManageBuilder WithTransactionId(string value) { if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) PaymentMethod = new TransactionReference(); (PaymentMethod as TransactionReference).TransactionId = value; + return this; } @@ -57,7 +93,7 @@ public override byte[] Serialize(string configName = "default") { } protected override void SetupValidations() { - Validations.For(TransactionType.Capture).Check(() => TransactionId).IsNotNull(); + Validations.For(TransactionType.Capture).When(() => AuthCode).IsNull().Check(() => TransactionId).IsNotNull(); Validations.For(TransactionType.Void).When(() => ClientTransactionId).IsNull().Check(() => TransactionId).IsNotNull(); Validations.For(PaymentMethodType.Gift).Check(() => Currency).IsNotNull(); } diff --git a/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs b/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs index 56156875..454f4979 100644 --- a/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs +++ b/src/GlobalPayments.Api/Terminals/Builders/TerminalReportBuilder.cs @@ -1,4 +1,5 @@ using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; using GlobalPayments.Api.Terminals.PAX; using System; using System.Linq; @@ -7,6 +8,8 @@ namespace GlobalPayments.Api.Terminals.Builders { public class TerminalReportBuilder { internal TerminalReportType ReportType { get; set; } + internal ReceiptType ReceiptType { get; set; } + internal ReportType? Type { get; set; } private TerminalSearchBuilder _searchBuilder; internal TerminalSearchBuilder SearchBuilder { @@ -22,6 +25,14 @@ public TerminalReportBuilder(TerminalReportType reportType) { ReportType = reportType; } + public TerminalReportBuilder(ReceiptType receiptType) { + ReceiptType = receiptType; + } + + public TerminalReportBuilder(ReportType reportType) { + Type = reportType; + } + public TerminalSearchBuilder Where(PaxSearchCriteria criteria, T value) { return SearchBuilder.And(criteria, value); } diff --git a/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs b/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs index f200899b..ee9f0d65 100644 --- a/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs +++ b/src/GlobalPayments.Api/Terminals/ConnectionConfig.cs @@ -3,31 +3,26 @@ using GlobalPayments.Api.Terminals.HPA; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Genius; +using System.IO.Ports; +using GlobalPayments.Api.Terminals.Ingenico; namespace GlobalPayments.Api.Terminals { public enum ConnectionModes { SERIAL, TCP_IP, SSL_TCP, - HTTP + HTTP, + TCP_IP_SERVER } public enum BaudRate { + r9600 = 9600, r38400 = 38400, r57600 = 57600, r19200 = 19200, r115200 = 115200 } - public enum Parity { - None = 0, - Odd, - Even, - } - public enum StopBits { - One = 1, - Two - } public enum DataBits { Seven = 7, Eight = 8 @@ -47,12 +42,16 @@ public interface ITerminalConfiguration { Parity Parity { get; set; } StopBits StopBits { get; set; } DataBits DataBits { get; set; } + Handshake Handshake { get; set; } // Timeout int Timeout { get; set; } // Associated Gateway GatewayConfig GatewayConfig { get; set; } + + // Pay@Table + DeviceMode? DeviceMode { get; set; } } public class ConnectionConfig : Configuration, ITerminalConfiguration { @@ -62,10 +61,12 @@ public class ConnectionConfig : Configuration, ITerminalConfiguration { public Parity Parity { get; set; } public StopBits StopBits { get; set; } public DataBits DataBits { get; set; } + public Handshake Handshake { get; set; } public string IpAddress { get; set; } public string Port { get; set; } public IRequestIdProvider RequestIdProvider { get; set; } public GatewayConfig GatewayConfig { get; set; } + public DeviceMode? DeviceMode { get; set; } public ConnectionConfig() { Timeout = -1; @@ -84,8 +85,13 @@ internal override void ConfigureContainer(ConfiguredServices services) { services.DeviceController = new HpaController(this); break; //case DeviceType.GENIUS: - //services.DeviceController = new GeniusController(this); - //break; + //services.DeviceController = new GeniusController(this); + //break; + case DeviceType.Ingenico_EPOS_Desk5000: + case DeviceType.Ingenico_EPOS_Lane3000: + case DeviceType.Ingenico_EPOS_Move3500: + services.DeviceController = new IngenicoController(this); + break; default: break; } @@ -97,7 +103,7 @@ internal override void Validate() { if (ConnectionMode == ConnectionModes.TCP_IP || ConnectionMode == ConnectionModes.HTTP) { if (string.IsNullOrEmpty(IpAddress)) throw new ApiException("IpAddress is required for TCP or HTTP communication modes."); - if(string.IsNullOrEmpty(Port)) + if (string.IsNullOrEmpty(Port)) throw new ApiException("Port is required for TCP or HTTP communication modes."); } } diff --git a/src/GlobalPayments.Api/Terminals/DeviceController.cs b/src/GlobalPayments.Api/Terminals/DeviceController.cs index c494ca55..8d6e6028 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceController.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceController.cs @@ -33,6 +33,8 @@ public IRequestIdProvider RequestIdProvider { } public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; internal DeviceController(ITerminalConfiguration settings) { _settings = settings; @@ -40,6 +42,14 @@ internal DeviceController(ITerminalConfiguration settings) { _connector.OnMessageSent += (message) => { OnMessageSent?.Invoke(message); }; + + _connector.OnBroadcastMessage += (code, message) => { + OnBroadcastMessage?.Invoke(code, message); + }; + + _connector.OnPayAtTableRequest += (request) => { + OnPayAtTableRequest?.Invoke(request); + }; } public byte[] Send(IDeviceMessage message) { diff --git a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs index 859d6020..ecc77952 100644 --- a/src/GlobalPayments.Api/Terminals/DeviceInterface.cs +++ b/src/GlobalPayments.Api/Terminals/DeviceInterface.cs @@ -2,6 +2,7 @@ using GlobalPayments.Api.Entities; using GlobalPayments.Api.Terminals.Abstractions; using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Ingenico; using GlobalPayments.Api.Terminals.Messaging; namespace GlobalPayments.Api.Terminals { @@ -10,17 +11,29 @@ public abstract class DeviceInterface : IDeviceInterface where T : DeviceCont protected IRequestIdProvider _requestIdProvider; public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; internal DeviceInterface(T controller) { _controller = controller; _controller.OnMessageSent += (message) => { OnMessageSent?.Invoke(message); }; + + _controller.OnBroadcastMessage += (code, message) => { + OnBroadcastMessage?.Invoke(code, message); + }; + + _controller.OnPayAtTableRequest += (request) => { + OnPayAtTableRequest?.Invoke(request); + }; + + _requestIdProvider = _controller.RequestIdProvider; } #region Admin Methods - public virtual void Cancel() { + public virtual IDeviceResponse Cancel() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } @@ -79,6 +92,22 @@ public virtual IDeviceResponse SetStoreAndForwardMode(bool enabled) { public virtual IDeviceResponse StartCard(PaymentMethodType paymentMethodType) { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } + + public virtual IDeviceResponse Duplicate() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + + public virtual IDeviceResponse GetTerminalStatus() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + + public virtual IDeviceResponse GetTerminalConfiguration() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } + + public virtual IDeviceResponse TestConnection() { + throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); + } #endregion #region Batching @@ -95,6 +124,15 @@ public virtual IEODResponse EndOfDay() { public virtual TerminalReportBuilder LocalDetailReport() { throw new UnsupportedTransactionException("This function is not supported by the currently configured device."); } + public virtual TerminalReportBuilder GetLastReceipt(ReceiptType type = ReceiptType.TICKET) { + return new TerminalReportBuilder(type); + } + + public virtual TerminalReportBuilder GetReport(Ingenico.ReportType type) { + //return new TerminalReportBuilder(TerminalReportType.LocalDetailReport).WithReportType(type); + return new TerminalReportBuilder(type); + } + #endregion #region Transactions @@ -121,8 +159,10 @@ public virtual TerminalAuthBuilder Sale(decimal? amount = null) { return new TerminalAuthBuilder(TransactionType.Sale, PaymentMethodType.Credit) .WithAmount(amount); } + public virtual TerminalAuthBuilder Verify() { - return new TerminalAuthBuilder(TransactionType.Verify, PaymentMethodType.Credit); + return new TerminalAuthBuilder(TransactionType.Verify, PaymentMethodType.Credit) + .WithAmount(6.18m); } public virtual TerminalManageBuilder Void() { return new TerminalManageBuilder(TransactionType.Void, PaymentMethodType.Credit); @@ -138,5 +178,25 @@ public void Dispose() { _controller.Dispose(); } #endregion + + #region For clarification + + #region Transaction Management + public virtual TerminalManageBuilder Reverse(decimal? amount = null) { + return new TerminalManageBuilder(TransactionType.Reversal, PaymentMethodType.Credit) + .WithAmount(amount); + } + + #endregion + + #region Pay@Table Methods + + public virtual TerminalAuthBuilder PayAtTableResponse() { + return new TerminalAuthBuilder(TransactionType.Void, PaymentMethodType.Other); + } + + #endregion + + #endregion } } diff --git a/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs index 52515891..eaa44b98 100644 --- a/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/Genius/Interfaces/GeniusHttpInterface.cs @@ -17,6 +17,8 @@ internal class GeniusHttpInterface : IDeviceCommInterface { private GeniusConfig _gatewayConfig; public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; public GeniusHttpInterface(ITerminalConfiguration settings) { _settings = settings; diff --git a/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs b/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs index 71be05bd..9f4c4226 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/HpaInterface.cs @@ -14,9 +14,10 @@ internal HpaInterface(HpaController controller) : base(controller) { } #region Admin Messages - public override void Cancel() { + public override IDeviceResponse Cancel() { // TODO: Cancel for HPA? Reset(); + return null; } public override IDeviceResponse CloseLane() { diff --git a/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs b/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs index 8ebdb03b..af4bae6a 100644 --- a/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/HPA/Interfaces/HpaTcpInterface.cs @@ -16,6 +16,8 @@ internal class HpaTcpInterface : IDeviceCommInterface { List message_queue; public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; public HpaTcpInterface(ITerminalConfiguration settings) { this._settings = settings; diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoController.cs b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoController.cs new file mode 100644 index 00000000..e76bad42 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoController.cs @@ -0,0 +1,315 @@ +using GlobalPayments.Api.Builders; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Terminals.Ingenico.Responses; +using GlobalPayments.Api.Utils; +using System; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class IngenicoController : DeviceController { + IDeviceInterface _device; + + public IngenicoController(ITerminalConfiguration settings) : base(settings) { + } + + internal override IDeviceInterface ConfigureInterface() { + if (_device == null) { + _device = new IngenicoInterface(this); + } + return _device; + } + + internal override IDeviceCommInterface ConfigureConnector() { + switch (_settings.ConnectionMode) { + case ConnectionModes.SERIAL: + return new IngenicoSerialInterface(_settings); + case ConnectionModes.TCP_IP_SERVER: + return new IngenicoTcpInterface(_settings); + default: + throw new NotImplementedException(); + } + } + + #region overrides + internal override ITerminalResponse ManageTransaction(TerminalManageBuilder builder) { + IDeviceMessage request = BuildManageTransaction(builder); + + if (builder.TransactionType == TransactionType.Reversal) { + return DoReverseRequest(request); + } + else { + return DoRequest(request); + } + } + + internal override ITerminalReport ProcessReport(TerminalReportBuilder builder) { + IDeviceMessage request; + if (builder.Type != null) { + request = BuildReportTransaction(builder); + return ReportRequest(request); + } + else { + request = TerminalUtilities.BuildRequest(INGENICO_REQ_CMD.RECEIPT.FormatWith(builder.ReceiptType), settings: _settings.ConnectionMode); + return XmlRequest(request); + } + } + + internal override ITerminalResponse ProcessTransaction(TerminalAuthBuilder builder) { + IDeviceMessage request = BuildProcessTransaction(builder); + return DoRequest(request); + } + + internal override byte[] SerializeRequest(TerminalAuthBuilder builder) { + return BuildProcessTransaction(builder).GetSendBuffer(); + } + + internal override byte[] SerializeRequest(TerminalManageBuilder builder) { + return BuildManageTransaction(builder).GetSendBuffer(); + } + + internal override byte[] SerializeRequest(TerminalReportBuilder builder) { + throw new NotImplementedException(); + } + #endregion + + #region Methods + internal IDeviceMessage BuildManageTransaction(TerminalManageBuilder builder) { + int refNumber = builder.ReferenceNumber; + decimal? amount = ValidateAmount(builder.Amount); + int returnRep = 1; + int paymentMode = ValidatePaymentMode(builder.PaymentMode); + int? paymentType = (int?)((IngenicoInterface)_device).paymentMethod ?? 0; + string currencyCode = (string.IsNullOrEmpty(builder.CurrencyCode) ? "826" : builder.CurrencyCode); + string privData = "EXT0100000"; + int immediateAns = 0; + int forceOnline = 0; + string extendedData = "0000000000"; + + // Validation for Authcode + if (!string.IsNullOrEmpty(builder.AuthCode)) { + extendedData = INGENICO_REQ_CMD.AUTHCODE.FormatWith(builder.AuthCode); + } + // Validation for Reversal with Transaction Id value in Extended data + else if (builder.TransactionId != null && builder.TransactionType == TransactionType.Reversal) { + extendedData = INGENICO_REQ_CMD.REVERSE_WITH_ID.FormatWith(builder.TransactionId); + } + else if (builder.TransactionType == TransactionType.Reversal) { + extendedData = INGENICO_REQ_CMD.REVERSE; + } + + // Concat all data to create a request string. + var sb = new StringBuilder(); + + sb.Append(builder.ReferenceNumber.ToString("00")); + sb.Append(amount?.ToString("00000000")); + sb.Append(returnRep); + sb.Append(paymentMode); + sb.Append(paymentType); + sb.Append(currencyCode); + sb.Append(privData); + sb.Append("A01" + immediateAns); + sb.Append("B01" + forceOnline); + sb.Append(extendedData); + + return TerminalUtilities.BuildRequest(sb.ToString(), settings: _settings.ConnectionMode); + } + + internal IDeviceMessage BuildProcessTransaction(TerminalAuthBuilder builder) { + string message = string.Empty; + + // For Pay@Table functionalities + if (_settings.DeviceMode != null && _settings.DeviceMode == DeviceMode.PAY_AT_TABLE) { + if (builder.PayAtTableResponse != null && builder.AdditionalMessage != null) { + + StringBuilder payAtTableResp = new StringBuilder(); + payAtTableResp.Append(PAYATTABLE_RESP.PAT_EPOS_NUMBER); + payAtTableResp.Append(PAYATTABLE_RESP.PAT_STATUS); + payAtTableResp.Append((int?)builder.AdditionalMessage ?? 0); + payAtTableResp.Append(ValidateAmount(builder.Amount)?.ToString("00000000")); + payAtTableResp.Append(ValidateCurrency(builder.CurrencyCode) ); + payAtTableResp.Append(builder.PayAtTableResponse.ToString()); + + message = payAtTableResp.ToString(); + } + else if (!string.IsNullOrEmpty(builder.FilePath)) { + message = TerminalUtilities.GetTextContent(builder.FilePath); + } + else { + throw new BuilderException("PayAtTable Response type and Additional message can not be null."); + } + return TerminalUtilities.BuildRequest(message, settings: _settings.ConnectionMode); + } + + + int referenceNumber = builder.ReferenceNumber; + decimal? amount = builder.Amount; + int returnRep = 1; + int paymentMode = 0; + int paymentType = (int)((IngenicoInterface)_device).paymentMethod; + string currencyCode = "826"; + string privateData = "EXT0100000"; + int immediateAnswer = 0; + int forceOnline = 0; + string extendedData = "0000000000"; + + decimal? cashbackAmount = builder.CashBackAmount; + string authCode = builder.AuthCode; + string tableId = builder.TableNumber; + + // Validations + if (referenceNumber == default(int) && RequestIdProvider != null) { + referenceNumber = RequestIdProvider.GetRequestId(); + } + amount = ValidateAmount(amount); + + // Tax free Refund handling + if (paymentType == (int)PaymentType.Refund && builder.TaxFreeType == TaxFreeType.CASH) { + paymentType = (int)PaymentType.TaxFreeCashRefund; + } + else if (paymentType == (int)PaymentType.Refund && builder.TaxFreeType == TaxFreeType.CREDIT) { + paymentType = (int)PaymentType.TaxFreeCreditRefund; + + } + + paymentMode = ValidatePaymentMode(builder.PaymentMode); + currencyCode = ValidateCurrency((string.IsNullOrEmpty(builder.CurrencyCode) ? currencyCode : builder.CurrencyCode)); + + if (!string.IsNullOrEmpty(tableId)) { + ValidateTableId(tableId); + extendedData = INGENICO_REQ_CMD.TABLE_WITH_ID.FormatWith(tableId); + } + else if (!string.IsNullOrEmpty(authCode)) { + extendedData = INGENICO_REQ_CMD.AUTHCODE.FormatWith(authCode); + } + else if (cashbackAmount != null) { + ValidateCashbackAmount(cashbackAmount); + cashbackAmount *= 100; + extendedData = INGENICO_REQ_CMD.CASHBACK.FormatWith(Convert.ToInt64(Math.Round(cashbackAmount.Value, MidpointRounding.AwayFromZero))); + } + + // Concat all data to create a request string. + var sb = new StringBuilder(); + + sb.Append(referenceNumber.ToString("00").Substring(0, 2)); + sb.Append(amount?.ToString("00000000")); + sb.Append(returnRep); + sb.Append(paymentMode); + sb.Append(paymentType); + sb.Append(currencyCode); + sb.Append(privateData); + sb.Append("A01" + immediateAnswer); + sb.Append("B01" + forceOnline); + sb.Append(extendedData); + + message = sb.ToString(); + + return TerminalUtilities.BuildRequest(message, settings: _settings.ConnectionMode); + } + + internal IDeviceMessage BuildReportTransaction(TerminalReportBuilder builder) { + if (!IsObjectNullOrEmpty(builder.Type)) { + string message = INGENICO_REQ_CMD.REPORT.FormatWith(builder.Type); + return TerminalUtilities.BuildRequest(message, settings: _settings.ConnectionMode); + } + else { + throw new BuilderException("Type of report is missing in request."); + } + } + + internal IngenicoTerminalReportResponse XmlRequest(IDeviceMessage request) { + byte[] send = Send(request); + return new IngenicoTerminalReportResponse(send); + } + + internal ReportResponse ReportRequest(IDeviceMessage request) { + byte[] send = Send(request); + return new ReportResponse(send); + } + + #region Validations + private bool IsObjectNullOrEmpty(object value) { + bool response = false; + if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { + response = true; + } + else { + response = false; + } + + return response; + } + + private void ValidateTableId(string value) { + if (value.Length > 8) { + throw new BuilderException("The maximum length for table number is 8."); + } + } + + private void ValidateCashbackAmount(decimal? value) { + if (value >= 1000000m) { + throw new BuilderException("Cashback Amount exceed."); + } + if (value < 0m) { + throw new BuilderException("Cashback Amount must not be in less than zero."); + } + } + + private int ValidatePaymentMode(PaymentMode? paymentMode) { + if (IsObjectNullOrEmpty(paymentMode)) { + paymentMode = PaymentMode.APPLICATION; + } + + return (int)paymentMode; + } + + private string ValidateCurrency(string currencyCode) { + if (!string.IsNullOrWhiteSpace(currencyCode)) { + currencyCode = currencyCode.PadLeft(3, '0'); + } + else { + currencyCode = "826"; + } + + return currencyCode.Substring(0, 3); + } + + private decimal? ValidateAmount(decimal? amount) { + if (amount != null && amount > 0 && amount < 1000000m) { + amount *= 100; + } + else if (amount == null) { + throw new BuilderException("Amount can not be null."); + } + else if (amount >= 1000000m) { + throw new BuilderException("Amount exceed."); + } + else { + throw new BuilderException("Invalid input amount."); + } + return amount; + } + + #endregion + + #endregion + + #region Request + internal IngenicoTerminalResponse DoRequest(IDeviceMessage request) { + byte[] response = Send(request); + return new IngenicoTerminalResponse(response); + } + + private CancelResponse DoCancelRequest(IDeviceMessage request) { + byte[] response = Send(request); + return new CancelResponse(response); + } + + private IngenicoTerminalResponse DoReverseRequest(IDeviceMessage request) { + byte[] response = Send(request); + return new IngenicoTerminalResponse(response); + } + #endregion + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoEnums.cs b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoEnums.cs new file mode 100644 index 00000000..0336c692 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoEnums.cs @@ -0,0 +1,262 @@ + +using GlobalPayments.Api.Utils; +using System; + +namespace GlobalPayments.Api.Terminals.Ingenico { + internal class INGENICO_REQ_CMD { + // Request Transactions + public const string AUTHCODE = "AUTHCODE={0}"; + public const string CASHBACK = "CASHB={0};"; + + // Request Commands + /** + * REQUEST_MESSAGE is hard-coded in order to fulfill the request message frame 3 + * and values in here are ignored in the terminal. + */ + public const string REQUEST_MESSAGE = "0100000001100826EXT0100000A010B010"; + public const string CANCEL = "CMD=CANCEL"; + public const string DUPLICATE = "CMD=DUPLIC"; + public const string REVERSE = "CMD=REVERSE"; + public const string REVERSE_WITH_ID = "CMD=REV{0}"; + public const string TABLE_WITH_ID = "CMD=ID{0}"; + + // Terminal Management Commands + public const string STATE = "CMD=STATE"; + public const string PID = "CMD=PID"; + public const string LOGON = "CMD=LOGON"; + public const string RESET = "CMD=RESET"; + public const string CALLTMS = "CMD=CALLTMS"; + + // Request Report + public const string REPORT = "0100000001100826EXT0100000A010B010CMD={0}"; + public const string RECEIPT = "0100000001100826EXT0100000A010B010CMD={0}"; + } + + internal class INGENICO_GLOBALS { + public const string BROADCAST = "BROADCAST CODE"; + public const string CANCEL = "CMD=CANCEL"; + public const string TID_CODE = "TID CODE"; + public const string KEEP_ALIVE_RESPONSE = " OK"; + public static bool KeepAlive = true; + public const int IP_PORT = 18101; + public const int RAW_RESPONSE_LENGTH = 80; + public const string MGMT_SCOPE = "root\\CIMV2"; + public const string MGMT_QUERY = "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\""; + public const int MSG_FRAME_TWO_LEN = 80; + public const string XML_TAG = "", "LF" }; + public readonly static string INVALID = "\u0005\u0004"; + public readonly static string ENDXML = ""; + } + + internal static class PAYATTABLE_RESP { + public readonly static string PAT_EPOS_NUMBER = "00"; + public readonly static string PAT_STATUS = "0"; + } + + public enum ReceiptType { + TICKET, + SPLITR, + TAXFREE, + REPORT + } + + public enum ReportType { + EOD, + BANKING, + XBAL, + ZBAL + } + + public enum TransactionStatus { + SUCCESS = 0, + REFERRAL = 2, + CANCELLED_BY_USER = 6, + FAILED = 7, + RECEIVED = 9 + } + + public enum ReverseStatus { + REVERSAL_SUCCESS = 0, + REVERSAL_FAILED = 7, + NOTHING_TO_REVERSE = 9 + } + + public enum CancelStatus { + CANCEL_DONE = 9, + CANCEL_FAILED = 7 + } + + public enum DynamicCurrencyStatus { + CONVERSION_APPLIED = 1, + REJECTED = 0 + } + + public enum TransactionSubTypes { + [Description("S")] + SPLIT_SALE_TXN, + + [Description("D")] + DCC_TXN, + + [Description("R")] + REFERRAL_RESULT + } + + public enum TerminalStatus { + NOT_READY = 0, + READY = 1 + } + + public enum SalesMode { + STANDARD_SALE_MODE = 0, + VENDING_MODE = 1 + } + + public enum PaymentMethod { + Keyed = 1, + Swiped = 2, + Chip = 3, + Conctactless = 4 + } + + public enum PaymentType { + Sale = 0, + Refund = 1, + CompletionMode = 2, + PreAuthMode = 3, + TaxFreeCreditRefund = 4, + TaxFreeCashRefund = 5, + AccountVerification = 6, + ReferralConfirmation = 9 + } + + public enum PaymentMode { + APPLICATION = 0, + MAILORDER = 1 + } + + public enum TaxFreeType { + CREDIT = 0, + CASH = 1 + } + + // Codes in Response field for TLV format + public enum RepFieldCode { + AuthCode = 67, // C + CashbackAmount = 90, // Z + GratuityAmount = 89, // Y + FinalTransactionAmount = 77, // M + AvailableAmount = 65, // A + DccCurrency = 85, // U + DccConvertedAmount = 79, // O + PaymentMethod = 80, // P + TransactionSubType = 84, // T + SplitSalePaidAmount = 83, // S + DccOperationStatus = 68, // D + } + + public enum StateResponseCode { + Status = 83, // S + AppVersionNumber = 86, // V + HandsetNumber = 72, // H + TerminalId = 84, // T + } + + public enum TLVFormat { + /// + /// Format for transaction request. + /// + Standard, + + /// + /// Format for State command request. + /// + State + } + + public enum ParseFormat { + /// + /// For Transaction response parsing format + /// + Transaction = 0, + + /// + /// For State Command response parsing format + /// + State = 1, + + /// + /// For PID Command response parsing format + /// + PID = 2, + + /// + /// For Pay@Table functionalities in terminal + /// + PayAtTableRequest + } + + /// + /// Type of request message from terminal during Pay@Table mode. + /// + public enum PATRequestType { + /// + /// Indicates a Table Lock + /// + TableLock = 1, + + /// + /// Indicates a Table Unlock + /// + TableUnlock = 2, + + /// + /// Indicates a Receipt for table + /// + TableReceipt = 3, + + /// + /// Indicates a List of Table. + /// + TableList = 4, + + /// + /// Indicates a Transaction Outcome request + /// + TransactionOutcome, + + XMLData + } + + /// + /// Confirmation options + /// + public enum PATResponseType { + /// + /// Positive confirmation + /// + CONF_OK, + + /// + /// Negative confirmation + /// + CONF_NOK + } + + /// + /// Indicates if the EPOS want to uses the additional Message + /// + public enum PATPaymentMode { + NO_ADDITIONAL = 0, + + USE_ADDITIONAL = 1 + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoInterface.cs b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoInterface.cs new file mode 100644 index 00000000..00964f3c --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/IngenicoInterface.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Text; +using GlobalPayments.Api.Builders; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.PaymentMethods; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Builders; +using GlobalPayments.Api.Utils; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class IngenicoInterface : DeviceInterface, IDeviceInterface { + internal PaymentType? paymentMethod = null; + internal IngenicoInterface(IngenicoController controller) : base(controller) { + } + + #region Terminal Management/Admin Methods + public override IDeviceResponse GetTerminalStatus() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.STATE); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new StateResponse(response); + } + + public override IInitializeResponse Initialize() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.PID); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new POSIdentifierResponse(response); + } + + public override IDeviceResponse GetTerminalConfiguration() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.CALLTMS); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new IngenicoTerminalResponse(response); + } + + public override IDeviceResponse TestConnection() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.LOGON); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new IngenicoTerminalResponse(response); + } + + public override IDeviceResponse Reboot() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.RESET); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new IngenicoTerminalResponse(response); + } + + #endregion + + #region Payment Transaction Management + + public override TerminalAuthBuilder Sale(decimal? amount = null) { + paymentMethod = PaymentType.Sale; + return base.Sale(amount); + } + + public override TerminalAuthBuilder Refund(decimal? amount = null) { + paymentMethod = PaymentType.Refund; + return base.Refund(amount); + } + + public override TerminalManageBuilder Capture(decimal? amount = null) { + paymentMethod = PaymentType.CompletionMode; + return base.Capture(amount); + } + + /// + /// Authorize method is Equivalent of Pre-Authorisation from Ingenico + /// + /// + /// + public override TerminalAuthBuilder Authorize(decimal? amount = null) { + paymentMethod = PaymentType.PreAuthMode; + return base.Authorize(amount); + } + + public override TerminalAuthBuilder Verify() { + paymentMethod = PaymentType.AccountVerification; + return base.Verify(); + } + #endregion + + #region XML & Report Management + public override TerminalReportBuilder GetReport(ReportType type) { + return base.GetReport(type); + } + + public override TerminalReportBuilder GetLastReceipt(ReceiptType type = ReceiptType.TICKET) { + return base.GetLastReceipt(type); + } + #endregion + + #region Transaction Management + + public override IDeviceResponse Cancel() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.CANCEL); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new IngenicoTerminalResponse(response); + } + + public override TerminalManageBuilder Reverse(decimal? amount = null) { + if (amount != null) { + return base.Reverse(amount); + } + else throw new BuilderException("Amount can't be null."); + } + + ///// + ///// Duplicate falls under lost transaction recovery and we have mechanisms for this which we'll need to look into further + ///// + public override IDeviceResponse Duplicate() { + StringBuilder sb = new StringBuilder(); + sb.Append(INGENICO_REQ_CMD.REQUEST_MESSAGE); + sb.Append(INGENICO_REQ_CMD.DUPLICATE); + + byte[] response = _controller.Send(TerminalUtilities.BuildRequest(sb.ToString(), settings: _controller.ConnectionMode.Value)); + + return new IngenicoTerminalResponse(response); + } + + #endregion + + public override TerminalAuthBuilder PayAtTableResponse() { + return base.PayAtTableResponse(); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoSerialInterface.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoSerialInterface.cs new file mode 100644 index 00000000..c8eb8188 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoSerialInterface.cs @@ -0,0 +1,245 @@ +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.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Management; + +namespace GlobalPayments.Api.Terminals.Ingenico { + internal class IngenicoSerialInterface : IDeviceCommInterface { + private SerialPort _serialPort; + private ITerminalConfiguration _settings; + + private bool _transComplete; + private bool _isResult; + private bool _isAcknowledge; + private bool _isBroadcast; + private bool _isXML; + + private List _messageResponse; + private string _bufferReceived = string.Empty; + private StringBuilder _report = new StringBuilder(); + + public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; + + public IngenicoSerialInterface(ITerminalConfiguration settings) { + this._settings = settings; + Connect(); + } + + public void Connect() { + if (_serialPort == null) { + ManagementObjectSearcher searcher = new ManagementObjectSearcher(INGENICO_GLOBALS.MGMT_SCOPE, INGENICO_GLOBALS.MGMT_QUERY); + + string manufacturer = string.Empty; + foreach (ManagementObject mgmtObject in searcher.Get()) { + manufacturer = mgmtObject["Manufacturer"].ToString().ToLower(); + if (manufacturer.Equals("ingenico")) { + string caption = mgmtObject["Caption"].ToString(); + string portName = "COM{0}".FormatWith(_settings.Port); + if (caption.Equals(portName)) { + _serialPort = new SerialPort() { + PortName = portName, + BaudRate = (int)_settings.BaudRate, + DataBits = (int)_settings.DataBits, + StopBits = _settings.StopBits, + Parity = _settings.Parity, + Handshake = _settings.Handshake, + RtsEnable = true, + DtrEnable = true, + ReadTimeout = _settings.Timeout + }; + } + } + } + + if (_serialPort == null) { + throw new ConfigurationException("Can't connect to the terminal. Port not found."); + } + + if (!_serialPort.IsOpen) { + _serialPort.DataReceived += new SerialDataReceivedEventHandler(Serial_DataReceived); + _serialPort.Open(); + } + } else { + throw new ConfigurationException("Serial port is already open."); + } + } + + private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e) { + SerialPort serial = (SerialPort)sender; + do { + Thread.Sleep(0100); + _bufferReceived = serial.ReadExisting(); + + if (!string.IsNullOrEmpty(_bufferReceived)) { + _serialPort.ReadTimeout = _settings.Timeout; + + if (_bufferReceived.Equals(INGENICO_RESP.ACKNOWLEDGE)) { + _isAcknowledge = true; + break; + } else if (_bufferReceived.Equals(INGENICO_RESP.ENQUIRY)) { + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.ACK), 0, 1); + break; + } else if (_bufferReceived.Contains(INGENICO_GLOBALS.BROADCAST)) { + _isBroadcast = true; + break; + } else if (INGENICO_RESP.XML.Any(_bufferReceived.Contains)) { + _isXML = true; + break; + } else if (!_bufferReceived.Contains(INGENICO_RESP.INVALID) + && !_bufferReceived.Contains(INGENICO_GLOBALS.BROADCAST) + && !INGENICO_RESP.XML.Any(_bufferReceived.Contains)) { + _isResult = true; + break; + } + } + } while (true); + } + + public void Disconnect() { + _serialPort.Close(); + _serialPort?.Dispose(); + _serialPort = null; + } + + private bool ValidateResponseLRC(string calculate, string actual) { + bool response = false; + + byte[] calculateLRC = TerminalUtilities.CalculateLRC(calculate); + byte[] actualLRC = TerminalUtilities.CalculateLRC(actual); + + if (BitConverter.ToString(actualLRC) == BitConverter.ToString(calculateLRC)) { + response = true; + } + + return response; + } + + private async Task WriteMessage(IDeviceMessage message) { + return await Task.Run(() => { + try { + int enquiryCount = 0; + _messageResponse = new List(); + + if (_serialPort == null) { + return false; + } + + do { + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.ENQ), 0, 1); + if (!_isAcknowledge) { + Thread.Sleep(1000); + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.EOT), 0, 1); + enquiryCount++; + + if (enquiryCount.Equals(3)) { + throw new MessageException("Terminal did not respond in Enquiry for three (3) times. Send aborted."); + } + } else { + do { + byte[] msg = message.GetSendBuffer(); + foreach (byte b in msg) { + byte[] _b = new byte[] { b }; + _serialPort.Write(_b, 0, 1); + } + + if (_isAcknowledge) { + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.EOT), 0, 1); + _isAcknowledge = false; + break; + } + } while (true); + + do { + Thread.Sleep(100); + if (_isBroadcast) { + byte[] bMsg = Encoding.ASCII.GetBytes(_bufferReceived); + BroadcastMessage broadcastMsg = new BroadcastMessage(bMsg); + OnBroadcastMessage?.Invoke(broadcastMsg.Code, broadcastMsg.Message); + _isBroadcast = false; + } + + if (_isXML) { + do { + _report.Append(_bufferReceived); + if (_report.ToString().Contains(INGENICO_RESP.ENDXML)) { + string xmlData = _report.ToString().Substring(1, _report.ToString().Length - 3); + if (MessageReceived(xmlData)) { + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.ACK), 0, 1); + _isXML = false; + _transComplete = true; + } + } + Thread.Sleep(0500); + } while (!_transComplete); + } + + if (_isResult) { + string check = Encoding.UTF8.GetString(message.GetSendBuffer()); + if (_bufferReceived.Contains(check.Substring(0, 2))) { + do { + string rData = _bufferReceived.Substring(1, _bufferReceived.Length - 3); + _bufferReceived = _bufferReceived.Substring(1, _bufferReceived.Length - 3); + bool validateLRC = ValidateResponseLRC(rData, _bufferReceived); + if (validateLRC) { + if (MessageReceived(rData)) { + _serialPort.Write(BitConverter.GetBytes((char)ControlCodes.ACK), 0, 1); + _isResult = false; + _transComplete = true; + } + } + } while (!_transComplete); + } + } + } while (!_transComplete); + + return _transComplete; + } + } while (true); + } catch (MessageException e) { + throw new MessageException(e.Message); + } + }); + } + + public byte[] Send(IDeviceMessage message) { + try { + if (_serialPort != null) { + string bufferSend = Encoding.ASCII.GetString(message.GetSendBuffer()); + OnMessageSent?.Invoke(bufferSend.Substring(1, bufferSend.Length - 3)); + Task task = WriteMessage(message); + if (!task.Wait(_settings.Timeout)) { + throw new MessageException("Terminal did not response within timeout."); + } + + return _messageResponse.ToArray(); + } else { + throw new MessageException("Terminal not connected."); + } + } finally { + _transComplete = false; + } + } + + private bool MessageReceived(string messageData) { + if (_messageResponse == null) { + return false; + } + + foreach (char b in messageData) { + _messageResponse.Add((byte)b); + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoTcpInterface.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoTcpInterface.cs new file mode 100644 index 00000000..ec75835a --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/IngenicoTcpInterface.cs @@ -0,0 +1,337 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using GlobalPayments.Api.Utils; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Messaging; +using System.Collections.Generic; +using GlobalPayments.Api.Entities; +using System.Threading; +using System.Threading.Tasks; +using GlobalPayments.Api.Terminals.Ingenico.Requests; + +namespace GlobalPayments.Api.Terminals.Ingenico { + internal class IngenicoTcpInterface : IDeviceCommInterface { + private volatile TcpClient _client; + private NetworkStream _stream; + private ITerminalConfiguration _settings; + private TcpListenerEx _listener; + private Socket _server; + private List _ipAddresses = new List(); + private BroadcastMessage _broadcastMessage; + private byte[] _termResponse; + private Thread _dataReceiving; + private bool _isKeepAlive; + private bool _isKeepAliveRunning; + private Exception _receivingException; + private bool _isResponseNeeded; + private volatile bool _readData; + private volatile bool _disposable; + + public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; + + public IngenicoTcpInterface(ITerminalConfiguration settings) { + _settings = settings; + _client = new TcpClient(); ; + _ipAddresses = new List(); + + InitializeServer(); + + // Start listening to port. + Connect(); + + // Accepting client connected to port. + AcceptingClient(); + } + + public void Connect() { + try { + if (!_listener.Active) { + _listener.Start(); + } + else { + throw new ConfigurationException("Server already started."); + } + } + catch (Exception ex) { + throw new Exception(ex.Message); + } + } + + public void Disconnect() { + try { + if (_listener.Active) { + + _readData = false; + _stream.ReadTimeout = 1; + + // Closing and disposing current clients + while (true) { + if (_disposable) { + _client.Close(); + _client.Dispose(); + break; + } + } + // Stopping server listening + _listener.Stop(); + + _ipAddresses.Clear(); + } + } + catch (Exception ex) { + throw new Exception(ex.Message); + } + + } + + public byte[] Send(IDeviceMessage message) { + + byte[] buffer = message.GetSendBuffer(); + _termResponse = null; + _isResponseNeeded = true; + + try { + // Validate if server is starting + if (!_listener.Active) { + throw new ConfigurationException("Server is not running."); + } + + // Validate keep alive for setting of timeout during Transaction + _stream.ReadTimeout = _settings.Timeout; + + if (_ipAddresses.Count > 0) { + _stream.WriteAsync(buffer, 0, buffer.Length).Wait(); + // Should be move to Finally block before deployment + OnMessageSent?.Invoke(Encoding.UTF8.GetString(RemoveHeader(buffer))); + + if (_settings.DeviceMode == DeviceMode.PAY_AT_TABLE) { + return null; + } + + while (_termResponse == null) { + Thread.Sleep(100); + if (_receivingException != null) { + Exception ex = _receivingException; + _receivingException = null; + throw ex; + } + + if (_termResponse != null) { + // Remove timeout for stream read + if (!_isKeepAlive) { + _stream.ReadTimeout = -1; + } + + _isResponseNeeded = false; + _receivingException = null; + + return _termResponse; + } + } + return null; + } + else { + throw new ConfigurationException("No terminal connected to server."); + } + } + catch (Exception ex) { + throw new Exception(ex.Message); + } + } + + #region Interface private Methods + private void InitializeServer() { + if (_listener == null) { + int _port = INGENICO_GLOBALS.IP_PORT; // Default port. + if (!string.IsNullOrWhiteSpace(_settings.Port)) { + if (!int.TryParse(_settings.Port, out _port)) { + throw new ConfigurationException("Invalid port number."); + } + } + + _listener = new TcpListenerEx(IPAddress.Any, _port); + + // Set timeout for client to send data. + _server = _listener.Server; + + // Initialize keep Alive value to false. + _isKeepAlive = false; + _isKeepAliveRunning = false; + + _readData = true; + _disposable = false; + } + else { + throw new ConfigurationException("Server already initialize."); + } + } + + private void AcceptingClient() { + + _client = _listener.AcceptTcpClient(); + _stream = _client.GetStream(); + _stream.ReadTimeout = _settings.Timeout; + + + _ipAddresses.Add(((IPEndPoint)_client.Client.RemoteEndPoint).Address); + // Start thread for handling keep alive request. + if (_dataReceiving == null || _dataReceiving.ThreadState != ThreadState.Running) { + _dataReceiving = new Thread(new ThreadStart(AnalyzeReceivedData)); + _dataReceiving.Start(); + } + } + + private bool isBroadcast(byte[] terminalResponse) { + return Encoding.UTF8.GetString(terminalResponse).Contains(INGENICO_GLOBALS.BROADCAST); + } + + private bool isKeepAlive(byte[] buffer) { + return Encoding.UTF8.GetString(buffer).Contains(INGENICO_GLOBALS.TID_CODE); + } + + private byte[] RemoveHeader(byte[] buffer) { + return buffer.SubArray(2, buffer.Length - 2); + } + + private byte[] KeepAliveResponse(byte[] buffer) { + if (buffer.Length > 0) { + var tIdIndex = Encoding.ASCII.GetString(buffer, 0, buffer.Length).IndexOf(INGENICO_GLOBALS.TID_CODE); + var tId = Encoding.ASCII.GetString(buffer, tIdIndex + 10, 8); + + var respData = INGENICO_GLOBALS.KEEP_ALIVE_RESPONSE.FormatWith(tId); + respData = TerminalUtilities.CalculateHeader(Encoding.ASCII.GetBytes(respData)) + respData; + return Encoding.ASCII.GetBytes(respData); + } + else { + return null; + } + } + + private async void AnalyzeReceivedData() { + try { + var headerBuffer = new byte[2]; + while (_readData) { + + // For Pay@Table functionalities handling. + if (_settings.DeviceMode == DeviceMode.PAY_AT_TABLE) { + + _stream.Read(headerBuffer, 0, headerBuffer.Length); + if (!_readData) { + throw new Exception(); + } + int dataLength = await Task.Run(() => TerminalUtilities.HeaderLength(headerBuffer)); + if (dataLength > 0) { + byte[] dataBuffer = new byte[dataLength]; + + var incomplete = true; + int offset = 0; + int tempLength = dataLength; + + do { + + // Read data + int bytesReceived = _stream.Read(dataBuffer, offset, tempLength); + if (!_readData) { + throw new Exception(); + } + if (bytesReceived != tempLength) { + offset += bytesReceived; + tempLength -= bytesReceived; + } + else { + incomplete = false; + } + } while (incomplete); + + var readBuffer = new byte[dataLength]; + Array.Copy(dataBuffer, readBuffer, dataLength); + PayAtTableHandler(readBuffer); + } + + } + // For standard device functionalities handling + else { + _stream.Read(headerBuffer, 0, headerBuffer.Length); + if (!_readData) { + throw new Exception(); + } + int dataLength = await Task.Run(() => TerminalUtilities.HeaderLength(headerBuffer)); + if (dataLength > 0) { + byte[] dataBuffer = new byte[dataLength]; + + var incomplete = true; + int offset = 0; + int tempLength = dataLength; + + do { + + // Read data + int bytesReceived = _stream.Read(dataBuffer, offset, tempLength); + if (!_readData) { + throw new Exception(); + } + if (bytesReceived != tempLength) { + offset += bytesReceived; + tempLength -= bytesReceived; + } + else { + incomplete = false; + } + } while (incomplete); + + var readBuffer = new byte[dataLength]; + Array.Copy(dataBuffer, readBuffer, dataLength); + + if (isBroadcast(readBuffer)) { + _broadcastMessage = new BroadcastMessage(readBuffer); + OnBroadcastMessage?.Invoke(_broadcastMessage.Code, _broadcastMessage.Message); + } + else if (isKeepAlive(readBuffer) && INGENICO_GLOBALS.KeepAlive) { + + _isKeepAlive = true; + + if (_isKeepAlive && !_isKeepAliveRunning) { + _stream.ReadTimeout = _settings.Timeout; + _isKeepAliveRunning = true; + } + + var keepAliveRep = KeepAliveResponse(readBuffer); + _stream.WriteAsync(keepAliveRep, 0, keepAliveRep.Length).Wait(); + } + else { // Receiving request response data. + _termResponse = readBuffer; + } + } + else { + _receivingException = new ApiException("No data received."); + } + + } + } + } + catch (Exception ex) { + if (_isResponseNeeded || _isKeepAlive) { + _receivingException = ex; + _stream.ReadTimeout = -1; + _isKeepAlive = false; + } + + if (_readData) { + AnalyzeReceivedData(); + } + else { + _disposable = true; + } + } + } + + private void PayAtTableHandler(byte[] buffer) { + OnPayAtTableRequest?.Invoke(new PATRequest(buffer)); + } + #endregion + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/TcpListenerEx.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/TcpListenerEx.cs new file mode 100644 index 00000000..c15fcfad --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Interfaces/TcpListenerEx.cs @@ -0,0 +1,12 @@ +using System.Net; +using System.Net.Sockets; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class TcpListenerEx : TcpListener { + public TcpListenerEx(IPAddress localaddr, int port) : base(localaddr, port) { + + } + + public new bool Active { get { return base.Active; } } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Requests/PATRequest.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Requests/PATRequest.cs new file mode 100644 index 00000000..75d92ded --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Requests/PATRequest.cs @@ -0,0 +1,56 @@ +using GlobalPayments.Api.Utils; +using System; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico.Requests { + public class PATRequest { + private byte[] _buffer; + + public PATRequestType RequestType { get; set; } + public string WaiterId { get; set; } + public string TableId { get; set; } + public string TID { get; set; } + public string TerminalCurrency { get; set; } + public string XMLData { get; set; } + public TransactionOutcomeRequest TransactionOutcome { get; set; } + + // Assign passed value of buffer into private variable. + public PATRequest(byte[] buffer) { + _buffer = buffer; + ParseData(); + } + + private void ParseData() { + string strBuffer = Encoding.UTF8.GetString(_buffer); + + // Validating if data is XML Type + if (strBuffer.Contains(INGENICO_GLOBALS.XML_TAG)) { + RequestType = PATRequestType.XMLData; + XMLData = Encoding.UTF8.GetString(_buffer); + } + else { + // Otherwise check if what type of message frame the request is. + // Length of Ingenico Message Frame 2 + if (_buffer.Length == INGENICO_GLOBALS.MSG_FRAME_TWO_LEN) { + RequestType = PATRequestType.TransactionOutcome; + TransactionOutcome = new TransactionOutcomeRequest(_buffer); + } + else { + RequestType = (PATRequestType)Convert + .ToInt32(strBuffer.Substring(11, 1)); + + var tlvData = new TypeLengthValue(_buffer); + + WaiterId = tlvData.GetValue((byte)'O', typeof(string))?.ToString(); + TableId = tlvData.GetValue((byte)'L', typeof(string))?.ToString(); + TID = tlvData.GetValue((byte)'T', typeof(string))?.ToString(); + TerminalCurrency = tlvData.GetValue((byte)'C', typeof(string))?.ToString(); + } + } + } + + public override string ToString() { + return Encoding.UTF8.GetString(_buffer); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Requests/TransactionOutcomeRequest.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Requests/TransactionOutcomeRequest.cs new file mode 100644 index 00000000..108cb8b6 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Requests/TransactionOutcomeRequest.cs @@ -0,0 +1,21 @@ +using GlobalPayments.Api.Utils; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico.Requests { + public class TransactionOutcomeRequest { + private byte[] _buffer; + + public DataResponse RepField { get; set; } + public TransactionStatus Status { get; set; } + + public TransactionOutcomeRequest(byte[] buffer) { + _buffer = buffer; + ParseData(); + } + + private void ParseData() { + Status = (TransactionStatus)Encoding.UTF8.GetString(_buffer.SubArray(2, 1)).ToInt32(); + RepField = new DataResponse(_buffer.SubArray(11, 55)); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/BroadcastMessage.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/BroadcastMessage.cs new file mode 100644 index 00000000..77a6e50a --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/BroadcastMessage.cs @@ -0,0 +1,60 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Ingenico; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class BroadcastMessage { + private byte[] _buffer; + private string _code; + private string _message; + public string Code { + get { return _code; } + } + public string Message { + get { return _message; } + } + + private Dictionary _broadcastData = new Dictionary { + {"A0", "CONNECTING" }, + {"A1", "CONNECTION MADE" }, + {"A2", "APPROVED" }, + {"A3", "DECLINED" }, + {"A4", "INSERT CARD" }, + {"A5", "CARD ERROR" }, + {"A6", "PROCESSING ERROR" }, + {"A7", "REMOVE CARD" }, + {"A8", "TRY AGAIN" }, + {"A9", "PRESENT CARD" }, + {"AA", "RE-PRESENT CARD" }, + {"AB", "CARD NOT SUPPORTED" }, + {"AC", "PRESENT ONLY ONE CARD" }, + {"AD", "PLEASE WAIT" }, + {"AE", "BAD SWIPE" }, + {"AF", "CARD EXPIRED" }, + {"B0", "DECLINED BY CARD" }, + {"B1", "PIN ENTRY" }, + {"B2", "CASHBACK AMOUNT ENTRY" }, + {"B3", "PAPER OUT" }, + }; + + public BroadcastMessage(byte[] buffer) { + _buffer = buffer; + ParseBroadcast(_buffer); + } + + private void ParseBroadcast(byte[] broadBuffer) { + if (broadBuffer.Length > 0) { + var strBroadcast = ASCIIEncoding.UTF8.GetString(broadBuffer); + int findIndex = strBroadcast.IndexOf(INGENICO_GLOBALS.BROADCAST); + int findLen = 14 + 2; // additional 2 is for extra char '="' + _code = strBroadcast.Substring(findIndex + findLen, 2); + _message = _broadcastData[_code]; + } + else { + throw new MessageException("No broadcast message."); + } + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/CancelResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/CancelResponse.cs new file mode 100644 index 00000000..e17badf0 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/CancelResponse.cs @@ -0,0 +1,18 @@ +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class CancelResponse : IngenicoTerminalResponse, IDeviceResponse{ + public CancelResponse(byte[] buffer) : base(buffer) { + ParseResponse(buffer); + } + + public override void ParseResponse(byte[] response) { + base.ParseResponse(response); + Status = ((CancelStatus)Encoding.UTF8.GetString(response.SubArray(2, 1)).ToInt32()).ToString(); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DataResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DataResponse.cs new file mode 100644 index 00000000..31685656 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DataResponse.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Text; +using GlobalPayments.Api.Terminals.Ingenico; +using GlobalPayments.Api.Utils; +using System.Linq; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class DataResponse { + + private string _authCode; + private string _finalAmount; + private PaymentMethod? _paymentMethod; + private string _cashbackAmount; + private string _gratuityAmount; + private string _availableAmount; + private string _dccCode; + private string _dccAmount; + private TransactionSubTypes? _txnSubType; + private string _splitSaleAmount; + private DynamicCurrencyStatus? _dccStatus; + + private byte[] _buffer; + + public DataResponse(byte[] buffer) { + _buffer = buffer; + ParseData(); + } + + #region Property Fields + + public string AuthorizationCode { + get { return _authCode ?? ""; } + private set { } + } + + public decimal? FinalAmount { + get { return _finalAmount.ToAmount(); } + set { _finalAmount = value.ToString(); } + } + + public PaymentMethod? PaymentMethod { + get { return _paymentMethod; } + set { _paymentMethod = value; } + } + + public decimal? CashbackAmount { + get { return _cashbackAmount.ToAmount(); } + set { _cashbackAmount = value.ToString(); } + } + + public decimal? GratuityAmount { + get { return _gratuityAmount.ToAmount(); } + set { _gratuityAmount = value.ToString(); } + } + + public decimal? AvailableAmount { + get { return _availableAmount.ToAmount(); } + set { _availableAmount = value.ToString(); } + } + public string DccCode { + get { return _dccCode; } + set { _dccCode = value; } + } + + public decimal? DccAmount { + get { return _dccAmount.ToAmount(); } + set { _dccAmount = value.ToString(); } + } + + public TransactionSubTypes? TransactionSubType { + get { return _txnSubType; } + set { _txnSubType = value; } + } + + public decimal? SplitSaleAmount { + get { return _splitSaleAmount.ToAmount(); } + set { _splitSaleAmount = value.ToString(); } + } + + public DynamicCurrencyStatus? DccStatus { + get { return _dccStatus; } + set { _dccStatus = value; } + } + + #endregion + + private void ParseData() { + + var tlv = new TypeLengthValue(_buffer); + + Type stringType = typeof(string); + + _authCode = (string)tlv.GetValue((byte)RepFieldCode.AuthCode, stringType); + _cashbackAmount = (string)tlv.GetValue((byte)RepFieldCode.CashbackAmount, stringType); + _gratuityAmount = (string)tlv.GetValue((byte)RepFieldCode.GratuityAmount, stringType); + _finalAmount = (string)tlv.GetValue((byte)RepFieldCode.FinalTransactionAmount, stringType); + _availableAmount = (string)tlv.GetValue((byte)RepFieldCode.AvailableAmount, stringType); + _dccCode = (string)tlv.GetValue((byte)RepFieldCode.DccCurrency, stringType); + _dccAmount = (string)tlv.GetValue((byte)RepFieldCode.DccConvertedAmount, stringType); + _txnSubType = EnumConverter.FromDescription(tlv.GetValue((byte)RepFieldCode.TransactionSubType, stringType)); + _dccStatus = (DynamicCurrencyStatus?)tlv.GetValue((byte)RepFieldCode.DccOperationStatus, typeof(DynamicCurrencyStatus?)); + _splitSaleAmount = (string)tlv.GetValue((byte)RepFieldCode.SplitSalePaidAmount, stringType); + _paymentMethod = (PaymentMethod?)tlv.GetValue((byte)RepFieldCode.PaymentMethod, typeof(PaymentMethod?)); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DeviceResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DeviceResponse.cs new file mode 100644 index 00000000..72c8b9a5 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/DeviceResponse.cs @@ -0,0 +1,124 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Extensions; +using System.IO; +using System.Text; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Utils; +using System; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public abstract class IngenicoBaseResponse : DeviceResponse { + protected byte[] _buffer; + protected ParseFormat _parseFormat; + internal DataResponse _respField; + + #region Added Properties Specific for Ingenico + public string DccCurrency { get; set; } + public DynamicCurrencyStatus? DccStatus { get; set; } + public decimal? DccAmount { get; set; } + public TransactionSubTypes? TransactionSubType { get; set; } + public decimal? SplitSaleAmount { get; set; } + public PaymentMode PaymentMode { get; set; } + public string CurrencyCode { get; set; } + public string PrivateData { get; set; } + public decimal? FinalTransactionAmount { get; set; } + + internal string Amount { get; set; } + + #endregion + + internal IngenicoBaseResponse(byte[] buffer, ParseFormat format = ParseFormat.Transaction) { + _buffer = buffer; + _parseFormat = format; + ParseResponse(_buffer); + } + + + public virtual void ParseResponse(byte[] response) { + if (response != null) { + + ReferenceNumber = Encoding.UTF8.GetString(response.SubArray(0, 2)); + Status = ((TransactionStatus)Encoding.UTF8.GetString(response.SubArray(2, 1)).ToInt32()).ToString(); + Amount = Encoding.UTF8.GetString(response.SubArray(3, 8)); + PaymentMode = (PaymentMode)Encoding.UTF8.GetString(response.SubArray(11, 1)).ToInt32(); + CurrencyCode = Encoding.UTF8.GetString(response.SubArray(67, 3)); + PrivateData = Encoding.UTF8.GetString(response.SubArray(70, response.Length - 70)); + + // This is for parsing of Response field for Transaction request + if (_parseFormat == ParseFormat.Transaction) { + _respField = new DataResponse(response.SubArray(12, 55)); + + DccAmount = _respField.DccAmount; + DccCurrency = _respField.DccCode; + DccStatus = _respField.DccStatus; + } + } + } + + public override string ToString() { + DeviceResponseText = Encoding.UTF8.GetString(_buffer, 0, _buffer.Length); + return DeviceResponseText; + } + + } + + public class IngenicoTerminalResponse : IngenicoBaseResponse, ITerminalResponse { + + internal IngenicoTerminalResponse(byte[] buffer, ParseFormat format = ParseFormat.Transaction) : base(buffer, format) { } + + #region Properties + public decimal? TransactionAmount { get { return Amount.ToAmount(); } set { } } + public decimal? BalanceAmount { get { return _respField.AvailableAmount; } set { } } + public string AuthorizationCode { get { return _respField.AuthorizationCode ?? ""; } set { } } + public decimal? TipAmount { get { return _respField.GratuityAmount; } set { } } + public decimal? CashBackAmount { get { return _respField.CashbackAmount; } set { } } + public string PaymentType { get { return _respField.PaymentMethod.ToString(); } set { } } + public string TerminalRefNumber { get { return ReferenceNumber; } set { } } + + public string ResponseCode { get; set; } + public string TransactionId { 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 ApprovalCode { get; set; } + public decimal? AmountDue { get; set; } + public string CardHolderName { get; set; } + public string CardBIN { get; set; } + public bool CardPresent { get; set; } + public string ExpirationDate { 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 ApplicationCryptogramType ApplicationCryptogramType { get; set; } + public string ApplicationCryptogram { get; set; } + public string CardHolderVerificationMethod { get; set; } + public string TerminalVerificationResults { get; set; } + public string ApplicationPreferredName { get; set; } + public string ApplicationLabel { get; set; } + public string ApplicationId { get; set; } + public decimal? MerchantFee { get; set; } + public string ResponseText { get; set; } + #endregion + + } + + public class IngenicoTerminalReportResponse : IngenicoBaseResponse, ITerminalReport { + + internal IngenicoTerminalReportResponse(byte[] buffer) : base(buffer) { + _buffer = buffer; + Status = _buffer.Length > 0 ? "SUCCESS" : "FAILED"; + } + + public override string ToString() { + return Encoding.ASCII.GetString(_buffer); + } + } +} \ No newline at end of file diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/POSIdentifierResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/POSIdentifierResponse.cs new file mode 100644 index 00000000..a2231fc2 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/POSIdentifierResponse.cs @@ -0,0 +1,18 @@ +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class POSIdentifierResponse : IngenicoTerminalResponse, IInitializeResponse { + public string SerialNumber { get; set; } + + public POSIdentifierResponse(byte[] buffer) + : base(buffer, ParseFormat.PID) { + } + + public override void ParseResponse(byte[] response) { + base.ParseResponse(response); + SerialNumber = Encoding.UTF8.GetString(response.SubArray(12, 55)).Trim(); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReportResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReportResponse.cs new file mode 100644 index 00000000..cd4f021d --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReportResponse.cs @@ -0,0 +1,11 @@ +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico.Responses { + public class ReportResponse : IngenicoTerminalResponse, ITerminalReport { + internal ReportResponse(byte[] buffer) : base(buffer) { + ParseResponse(buffer); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReverseResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReverseResponse.cs new file mode 100644 index 00000000..dfe7524f --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/ReverseResponse.cs @@ -0,0 +1,18 @@ +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class ReverseResponse : IngenicoTerminalResponse, IDeviceResponse{ + public ReverseResponse(byte[] buffer) : base(buffer) { + ParseResponse(buffer); + } + + public override void ParseResponse(byte[] response) { + base.ParseResponse(response); + Status = ((ReverseStatus)Encoding.UTF8.GetString(response.SubArray(2, 1)).ToInt32()).ToString(); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/INGENICO/Responses/StateResponse.cs b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/StateResponse.cs new file mode 100644 index 00000000..3323df4c --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/INGENICO/Responses/StateResponse.cs @@ -0,0 +1,59 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; +using GlobalPayments.Api.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace GlobalPayments.Api.Terminals.Ingenico { + public class StateResponse : IngenicoTerminalResponse, IDeviceResponse { + + private TerminalStatus _terminalStatus; + private SalesMode _salesMode; + private string _terminalCapabilities; + private string _additionalTerminalCapabilities; + private string _appVersionNumber; + private string _handsetNumber; + private string _terminalId; + + public StateResponse(byte[] buffer) + : base(buffer, ParseFormat.State) { + } + + public TerminalStatus TerminalStatus { get { return _terminalStatus; } set { } } + public SalesMode SalesMode { get { return _salesMode; } set { } } + public string TerminalCapabilities { get { return _terminalCapabilities; } set { } } + public string AdditionalTerminalCapabilities { get { return _additionalTerminalCapabilities; } set { } } + public string AppVersionNumber { get { return _appVersionNumber; } set { } } + public string HandsetNumber { get { return _handsetNumber; } set { } } + public string TerminalId { get { return _terminalId; } set { } } + + public override void ParseResponse(byte[] response) { + if (response == null) { + throw new ApiException("Response data is null"); + } + + if (response.Length < INGENICO_GLOBALS.RAW_RESPONSE_LENGTH) { + byte[] newResponse = new byte[INGENICO_GLOBALS.RAW_RESPONSE_LENGTH]; + response.CopyTo(newResponse, 0); + + response = newResponse; + } + + base.ParseResponse(response); + + var tlv = new TypeLengthValue(response.SubArray(12, 55)); + tlv.TLVFormat = TLVFormat.State; + + string terminalStatusData = (string)tlv.GetValue((byte)StateResponseCode.Status, typeof(string)); + _terminalStatus = (TerminalStatus)Convert.ToByte(terminalStatusData.Substring(0, 1)); + _salesMode = (SalesMode)Convert.ToByte(terminalStatusData.Substring(1, 1)); + _terminalCapabilities = terminalStatusData.Substring(2, 6); + _additionalTerminalCapabilities = terminalStatusData.Substring(8, 10); + _appVersionNumber = (string)tlv.GetValue((byte)StateResponseCode.AppVersionNumber, typeof(string)); + _handsetNumber = (string)tlv.GetValue((byte)StateResponseCode.HandsetNumber, typeof(string)); + _terminalId = (string)tlv.GetValue((byte)StateResponseCode.TerminalId, typeof(string)); + } + } +} diff --git a/src/GlobalPayments.Api/Terminals/Messaging/BroadcastMessageEventHandler.cs b/src/GlobalPayments.Api/Terminals/Messaging/BroadcastMessageEventHandler.cs index d7cdab72..14fb0ba6 100644 --- a/src/GlobalPayments.Api/Terminals/Messaging/BroadcastMessageEventHandler.cs +++ b/src/GlobalPayments.Api/Terminals/Messaging/BroadcastMessageEventHandler.cs @@ -1,3 +1,3 @@ namespace GlobalPayments.Api.Terminals.Messaging { - public delegate void BroadcastMessageEventHandler(); + public delegate void BroadcastMessageEventHandler(string code, string message); } diff --git a/src/GlobalPayments.Api/Terminals/Messaging/PayAtTableRequestEventHandler.cs b/src/GlobalPayments.Api/Terminals/Messaging/PayAtTableRequestEventHandler.cs new file mode 100644 index 00000000..5cb60564 --- /dev/null +++ b/src/GlobalPayments.Api/Terminals/Messaging/PayAtTableRequestEventHandler.cs @@ -0,0 +1,5 @@ +using GlobalPayments.Api.Terminals.Ingenico.Requests; + +namespace GlobalPayments.Api.Terminals.Messaging { + public delegate void PayAtTableRequestEventHandler(PATRequest request); +} diff --git a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs index 5f8229ea..20de9ad1 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxHttpInterface.cs @@ -13,6 +13,8 @@ internal class PaxHttpInterface : IDeviceCommInterface { WebRequest _client; public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; public PaxHttpInterface(ITerminalConfiguration settings) { _settings = settings; diff --git a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs index 2f440631..13335ae8 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/Interfaces/PaxTcpInterface.cs @@ -14,6 +14,8 @@ internal class PaxTcpInterface : IDeviceCommInterface { int _connectionCount = 0; public event MessageSentEventHandler OnMessageSent; + public event BroadcastMessageEventHandler OnBroadcastMessage; + public event PayAtTableRequestEventHandler OnPayAtTableRequest; public PaxTcpInterface(ITerminalConfiguration settings) { _settings = settings; diff --git a/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs b/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs index 29b52734..fd37cd56 100644 --- a/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs +++ b/src/GlobalPayments.Api/Terminals/PAX/PaxInterface.cs @@ -24,18 +24,20 @@ public override ISignatureResponse GetSignatureFile() { return new SignatureResponse(response, _controller.DeviceType.Value); } - public override void Cancel() { + public override IDeviceResponse Cancel() { if (_controller.ConnectionMode == ConnectionModes.HTTP) { throw new MessageException("The cancel command is not available in HTTP mode"); } try { _controller.Send(TerminalUtilities.BuildRequest(PAX_MSG_ID.A14_CANCEL)); + return null; } catch (MessageException exc) { if (!exc.Message.Equals("Terminal returned EOT for the current message.")) { throw; } + return null; } } diff --git a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs index c2647ec6..fdd666a9 100644 --- a/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs +++ b/src/GlobalPayments.Api/Terminals/TerminalUtilities.cs @@ -7,6 +7,7 @@ using System.IO; using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using GlobalPayments.Api.Entities; namespace GlobalPayments.Api.Terminals { public class TerminalUtilities { @@ -35,7 +36,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); @@ -61,6 +62,33 @@ private static DeviceMessage BuildMessage(string messageId, string message) { return new DeviceMessage(buffer.ToArray()); } + public static DeviceMessage BuildRequest(string message, ConnectionModes settings) { + var buffer = new List(); + byte[] lrc; + + switch (settings) { + case ConnectionModes.SERIAL: + buffer.Add((byte)ControlCodes.STX); + foreach (char c in message) + buffer.Add((byte)c); + buffer.Add((byte)ControlCodes.ETX); + lrc = CalculateLRC(message); + buffer.Add(lrc[0]); + break; + case ConnectionModes.TCP_IP_SERVER: + var _msg = CalculateHeader(Encoding.UTF8.GetBytes(message)) + message; + + foreach (char c in _msg) + buffer.Add((byte)c); + + break; + default: + throw new BuilderException("Failed to build request message. Unknown Connection mode."); + } + + return new DeviceMessage(buffer.ToArray()); + } + public static DeviceMessage BuildRequest(string message, MessageFormat format) { var buffer = new List(); @@ -142,5 +170,49 @@ public static byte[] BuildSignatureImage(string pathData, int width = 150) { return ms.ToArray(); } } + + public static string CalculateHeader(byte[] buffer) { + //The Header contains the data length in hexadecimal format on two digits + var hex = buffer.Length.ToString("X4"); + hex = hex.PadLeft(4, '0'); + + // Get total value per two char. + var fDigit = hex[0].ToString() + hex[1]; + var sDigit = hex[2].ToString() + hex[3]; + + return string.Format("{0}{1}", Convert.ToChar(Convert.ToUInt32(fDigit, 16)), + Convert.ToChar(Convert.ToUInt32(sDigit, 16))); + } + + public static int HeaderLength(byte[] buffer) { + // Conversion from decimal to hex value + var fHex = Convert.ToInt64(buffer[0]).ToString("X2"); + var sHex = Convert.ToInt64(buffer[1]).ToString("X2"); + + // Concat two hex value + var _hex = fHex + sHex; + + // Get decimal value of concatenated hex + return int.Parse(_hex, System.Globalization.NumberStyles.HexNumber); + } + + public static byte[] CalculateLRC(string requestMessage) { + byte[] bytes = Encoding.ASCII.GetBytes((requestMessage + (char)ControlCodes.ETX)); + byte lrc = 0; + for (int i = 0; i < bytes.Length; i++) { + lrc ^= bytes[i]; + } + bytes = new byte[] { lrc }; + return bytes; + } + + public static string GetTextContent(string filePath) { + try { + return File.ReadAllText(filePath); + } + catch (Exception ex) { + throw ex; + } + } } } diff --git a/src/GlobalPayments.Api/Utils/Extensions.cs b/src/GlobalPayments.Api/Utils/Extensions.cs index be53c37b..eca72018 100644 --- a/src/GlobalPayments.Api/Utils/Extensions.cs +++ b/src/GlobalPayments.Api/Utils/Extensions.cs @@ -206,5 +206,10 @@ public static string TrimEnd(this string str, string trimString) { } return rvalue; } + public static T[] SubArray(this T[] data, int index, int length) { + T[] result = new T[length]; + Array.Copy(data, index, result, 0, length); + return result; + } } } diff --git a/src/GlobalPayments.Api/Utils/TypeLengthValue.cs b/src/GlobalPayments.Api/Utils/TypeLengthValue.cs new file mode 100644 index 00000000..1a3b76f1 --- /dev/null +++ b/src/GlobalPayments.Api/Utils/TypeLengthValue.cs @@ -0,0 +1,78 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Terminals.Ingenico; +using System; +using System.Linq; +using System.Text; + +namespace GlobalPayments.Api.Utils { + internal class TypeLengthValue { + + private byte[] _data = new byte[0]; + private TLVFormat _format = TLVFormat.Standard; + + public TypeLengthValue() { + + } + + public TypeLengthValue(byte[] data) { + _data = data; + } + + // Add TLV Format since Ingenico has different format of length when it comes to TLV Standard. + public TLVFormat TLVFormat { + get { return _format; } + set { _format = value; } + } + + + public object GetValue(byte type, Type returnType, TLVFormat? format = null) { + if (_data.Length == 0) { + throw new Exception("No data to parse."); + } + + int typeIndexLocation = Array.FindIndex(_data, e => e == type); + if (typeIndexLocation >= 0) { + // Get the length based on Documentation (TLV). + byte[] lengthBuffer = { _data[typeIndexLocation + 1], _data[typeIndexLocation + 2] }; + int length = 0; + + if ((format != null && format == TLVFormat.Standard) || _format == TLVFormat.Standard) { + length = Convert.ToInt32(Encoding.UTF8.GetString(lengthBuffer, 0, lengthBuffer.Length), 16); + } + else if ((format != null && format == TLVFormat.State) || _format == TLVFormat.State) { + length = Convert.ToInt32(Encoding.UTF8.GetString(lengthBuffer, 0, lengthBuffer.Length)); + } + else { + throw new ApiException("Unsupported TLV format."); + } + + // Get the value of type according to length limit. + byte[] value = _data.SubArray(typeIndexLocation + 3, length); + + int endLength = typeIndexLocation + length + 3; + + // Remove field that have been parsed and successfully get the value. + _data = _data.SubArray(0, typeIndexLocation).Concat(_data.SubArray(endLength, _data.Length - endLength)).ToArray(); + string strValue = Encoding.ASCII.GetString(value, 0, value.Length); + + + if (returnType == typeof(decimal?)) { + return decimal.Parse(strValue); + } + else if (returnType == typeof(string)) { + return strValue; + } + else if (returnType == typeof(DynamicCurrencyStatus?)) { + return (DynamicCurrencyStatus)int.Parse(strValue); + } + else if (returnType == typeof(PaymentMethod?)) { + return (PaymentMethod)int.Parse(strValue); + } + else { + throw new Exception("Data type not supported in parsing of TLV data."); + } + } + return null; + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/IngenicoTransactionTests.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/IngenicoTransactionTests.cs new file mode 100644 index 00000000..5601183c --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/IngenicoTransactionTests.cs @@ -0,0 +1,167 @@ +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Ingenico; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class IngenicoTransactionTests { + IDeviceInterface _device; + + public IngenicoTransactionTests() { + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = Entities.DeviceType.Ingenico_EPOS_Desk5000, + ConnectionMode = ConnectionModes.TCP_IP_SERVER, + Port = "18101", + Timeout = 30 * 1000 + }); + Assert.IsNotNull(_device); + } + + [TestMethod] + public void Test() { + SaleTest(); + RefundTest(); + PreAuthTest(); + CompletionTest(); + } + + public void SaleTest() { + _device.OnMessageSent += (message) => { + Assert.IsNotNull(message); + }; + + var resp = _device.Sale(6.18m) + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .WithCashBack(3m) + .Execute(); + + Assert.IsNotNull(resp); + + Thread.Sleep(5000); + } + + public void RefundTest() { + _device.OnMessageSent += (message) => { + Assert.IsNotNull(message); + }; + + var resp = _device.Refund(6.18m) + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .WithCashBack(3m) + .Execute(); + + Assert.IsNotNull(resp); + + Thread.Sleep(5000); + } + + public void PreAuthTest() { + _device.OnMessageSent += (message) => { + Assert.IsNotNull(message); + }; + + var resp = _device.Authorize(6.18m) + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .WithCashBack(3m) + .Execute(); + + Assert.IsNotNull(resp); + + Thread.Sleep(5000); + } + + [TestMethod] + public void CompletionTest() { + _device.OnMessageSent += (message) => { + Assert.IsNotNull(message); + }; + + var resp = _device.Capture(6.18m) + .WithAuthCode("025433") + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(resp); + + Thread.Sleep(5000); + } + + [TestMethod] + public void AsyncCancelTest() { + + Thread thSale = new Thread(new ThreadStart(() => { + var resp = _device.Sale(6.18m) + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .WithTaxFree(TaxFreeType.CASH) + .WithCashBack(3m) + .Execute(); + + + Assert.IsNotNull(resp, "Sale Assert"); + })); + + + Thread thCancel = new Thread(new ThreadStart(() => { + var resp = _device.Cancel(); + + Assert.IsNotNull(resp, "Cancel assert"); + })); + + thSale.Start(); + Thread.Sleep(2000); + thCancel.Start(); + } + + [TestMethod] + public void CancelTest() { + var resp = _device.Cancel(); + + Assert.IsNotNull(resp); + } + + [TestMethod] + public void TaxFreeCreditCardRefundTest() { + try { + var respone = _device.Refund(5m) + .WithTaxFree(TaxFreeType.CREDIT) + .Execute(); + + Assert.IsNotNull(respone); + } + catch (ApiException e) { + Assert.Fail(e.Message); + //throw e; + } + catch (Exception e) { + Assert.Fail(e.Message); + //throw e; + } + } + + [TestMethod] + public void TaxFreeCashRefundTest() { + try { + var respone = _device.Refund(5m) + .WithReferenceNumber(1) + .WithTaxFree(TaxFreeType.CASH) + .Execute(); + + Assert.IsNotNull(respone); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/PayAtTableRequestTests.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/PayAtTableRequestTests.cs new file mode 100644 index 00000000..d0a347d8 --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/PayAtTableRequestTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Ingenico; +using GlobalPayments.Api.Terminals.Ingenico.Requests; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class PayAtTableRequestTests { + + private IDeviceInterface _device; + + public PayAtTableRequestTests() { + + + + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = DeviceType.Ingenico_EPOS_Desk5000, + ConnectionMode = ConnectionModes.TCP_IP_SERVER, + Port = "18101", + Timeout = 60 * 1000, + DeviceMode = DeviceMode.PAY_AT_TABLE + }); + + + Assert.IsNotNull(_device); + + _device.OnPayAtTableRequest += _device_OnPayAtTableRequest; + + } + + private void _device_OnPayAtTableRequest(PATRequest request) { + // Success ConfirmaitonOK + + + Thread.Sleep(5 * 1000); + + + + if (request.RequestType == PATRequestType.TableReceipt) { + _device.PayAtTableResponse() + .WithXMLPath("C:\\tmp\\receipt.txt") + .Execute(); + } + else { + _device.PayAtTableResponse() + .WithPayAtTableResponseType(PATResponseType.CONF_OK) + .WithAmount(25M) + .WithPaymentMode(PATPaymentMode.USE_ADDITIONAL) + .Execute(); + } + } + + [TestMethod] + public void TableLock() { + Thread.Sleep(3000 * 1000); + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SaleTransactionManagement.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SaleTransactionManagement.cs new file mode 100644 index 00000000..61e2b85a --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SaleTransactionManagement.cs @@ -0,0 +1,138 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class PaymentTransactionManagement { + IDeviceInterface _device; + + public PaymentTransactionManagement() { + + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = DeviceType.Ingenico_EPOS_Lane3000, + ConnectionMode = ConnectionModes.TCP_IP_SERVER, + Port = "18101", + Timeout = 30000, + RequestIdProvider = new RandomIdProvider() + }); + + Assert.IsNotNull(_device); + } + + [TestMethod] + public void SaleTest1() { + var response = _device.Sale(12m) + .WithReferenceNumber(15) + .WithCashBack(10m) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(response); + } + + + [TestMethod] + public void SaleTest() { + _device.OnMessageSent += (message) => { + Assert.IsNotNull(message); + }; + + ITerminalResponse response = _device.Sale(5.12m) + .WithPaymentMode(PaymentMode.MAILORDER) + .WithCashBack(7.569m) + .WithReferenceNumber(1) + .Execute(); + + var authCode = response.AuthorizationCode; + + Assert.IsNotNull(response.AuthorizationCode); + } + + [TestMethod] + public void RefundTest() { + var response = _device.Refund(1m) + .WithPaymentMethodType(PaymentMethodType.Credit) + .WithReferenceNumber(1) + .Execute(); + + Assert.IsNotNull(response.AuthorizationCode); + } + + + [TestMethod] + public void SaleNegativeTest() { + var response = _device.Sale(5.12m) + .WithCashBack(7.52m) + .WithReferenceNumber(1) + .Execute(); + + Assert.IsNull(response.AuthorizationCode); + } + + [TestMethod] + public void SaleTWithOnMessageSentest() { + _device.OnBroadcastMessage += (code, message) => { + Assert.IsNull(code); + Assert.IsNull(message); + }; + + + var response = _device.Refund(5.12m) + //.WithCashBack(7.5m) + .WithReferenceNumber(1) + .Execute(); + + Assert.IsNotNull(response.AuthorizationCode); + } + + [TestMethod] + public void CancelTest() { + try { + _device.Cancel(); + } + catch (ApiException ex) { + Assert.Fail(ex.Message); + } + } + + [TestMethod] + public void AsyncCancelTest() { + var Task1 = Task.Factory.StartNew(() => { + var response = _device.Sale(5.12m) + .WithCashBack(7.5m) + .WithReferenceNumber(1) + .Execute(); + + Assert.IsNotNull(response); + }); + var Task2 = Task.Factory.StartNew(() => { + try { + Thread.Sleep(5000); + _device.Cancel(); + } + catch (Exception ex) { + Assert.Fail(ex.Message); + } + }); + + Task.WaitAll(Task1, Task2); + } + + + [TestMethod] + public void GetLastReceiptTest() { + var response = _device + .GetLastReceipt() + .Execute(); + + Assert.IsNotNull(response.ToString()); + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SerialPaymentTransactions.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SerialPaymentTransactions.cs new file mode 100644 index 00000000..f36b9b33 --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/SerialPaymentTransactions.cs @@ -0,0 +1,180 @@ +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Abstractions; +using GlobalPayments.Api.Terminals.Ingenico; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading; +using System.Threading.Tasks; +using ReportType = GlobalPayments.Api.Terminals.Ingenico.ReportType; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class SerialPaymentTransactions { + IDeviceInterface _device; + + public SerialPaymentTransactions() { + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = DeviceType.Ingenico_EPOS_Lane3000, + ConnectionMode = ConnectionModes.SERIAL, + Port = "5", + BaudRate = BaudRate.r9600, + DataBits = DataBits.Seven, + StopBits = System.IO.Ports.StopBits.One, + Parity = System.IO.Ports.Parity.Even, + Handshake = System.IO.Ports.Handshake.None, + Timeout = 65000 + }); + + Assert.IsNotNull(_device); + } + + [TestMethod] + public void CaptureTest() { + var response = _device.Capture(6.18m) + .WithReferenceNumber(1) + .WithCurrencyCode("826") + .WithTransactionId("011223") + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void SaleTest() { + var response = _device.Sale(6.18m) + .WithReferenceNumber(1) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .WithTableNumber("1") + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void Reverse() { + var response = _device.Reverse(amount: 6.18m) + .WithReferenceNumber(12) + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void SaleRefund() { + var response = _device.Refund(6.18m) + .WithReferenceNumber(1) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .WithTableNumber("1") + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void preAuth() { + var response = _device.Authorize(20.00m) + .WithReferenceNumber(1) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void SaleCancel() { + var task1 = Task.Factory.StartNew(() => { + var response = _device.Sale(523m) + .WithReferenceNumber(1) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(response); + }); + + var task2 = Task.Factory.StartNew(() => { + Thread.Sleep(10000); + _device.Cancel(); + }); + + Task.WaitAll(task1, task2); + } + + [TestMethod] + public void TaxFree() { + + var splitR = _device + .GetLastReceipt(ReceiptType.SPLITR) + .Execute(); + + string test = splitR.ToString(); + + if (splitR.ToString().Contains("")) + Assert.IsNotNull(splitR); + } + + [TestMethod] + public void Ticket() { + ITerminalReport res = _device + .GetLastReceipt(ReceiptType.TICKET) + .Execute(); + + string test = res.ToString(); + + if (res.ToString().Contains("")) + Assert.IsNotNull(res); + } + + [TestMethod] + public void EOD() { + + /** This example doesn't return XML/Report Data but it intiate End of Day + Report and the terminal will return EODOK if success. + */ + ITerminalReport res = _device + .GetReport(ReportType.EOD) + .Execute(); + + Assert.IsNotNull(res); + } + + [TestMethod] + public void Cashback() { + var response = _device.Sale(20.00m) + .WithReferenceNumber(01) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .WithCashBack(2.00m) + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void AccountVerification() { + + var response = _device.Verify() + .WithReferenceNumber(01) + .WithPaymentMode(PaymentMode.MAILORDER) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(response); + } + + [TestMethod] + public void test123() { + var response = _device.Verify() + .WithReferenceNumber(01) + .WithPaymentMode(PaymentMode.APPLICATION) + .WithCurrencyCode("826") + .Execute(); + + Assert.IsNotNull(response); + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TerminalManagementTest.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TerminalManagementTest.cs new file mode 100644 index 00000000..cc5c1270 --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TerminalManagementTest.cs @@ -0,0 +1,87 @@ +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Ingenico; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Text; +using System.Threading; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class TerminalManagementTest { + IDeviceInterface _device; + + public TerminalManagementTest() { + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = Entities.DeviceType.Ingenico_EPOS_Desk5000, + ConnectionMode = ConnectionModes.TCP_IP_SERVER, + Port = "18101", + Timeout = 5 * 1000 + }); + Assert.IsNotNull(_device); + } + + [TestMethod] + public void StateCommandTest() { + try { + var resp = _device.GetTerminalStatus(); + + Assert.IsNotNull(resp); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + + [TestMethod] + public void PIDCommandTest() { + try { + var resp = _device.Initialize(); + + Assert.IsNotNull(resp); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + + [TestMethod] + public void CALLTMSCommandTest() { + try { + var resp = _device.GetTerminalConfiguration(); + + Assert.IsNotNull(resp); + Assert.IsNotNull((resp as IngenicoTerminalResponse).PrivateData); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + + [TestMethod] + public void LOGONCommandTest() { + try { + var resp = _device.TestConnection(); + + Assert.IsNotNull(resp); + Assert.IsNotNull((resp as IngenicoTerminalResponse).PrivateData); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + + [TestMethod] + public void RESETCommandTest() { + try { + var resp = _device.Reboot(); + + Assert.IsNotNull(resp); + Assert.IsNotNull((resp as IngenicoTerminalResponse).PrivateData); + } + catch (Exception e) { + Assert.Fail(e.Message); + } + } + } +} diff --git a/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TransactionTest.cs b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TransactionTest.cs new file mode 100644 index 00000000..0483167a --- /dev/null +++ b/tests/GlobalPayments.Api.Tests/Terminals/Ingenico/TransactionTest.cs @@ -0,0 +1,86 @@ +using System.Threading; +using System.Threading.Tasks; +using GlobalPayments.Api.Entities; +using GlobalPayments.Api.Services; +using GlobalPayments.Api.Terminals; +using GlobalPayments.Api.Terminals.Ingenico; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GlobalPayments.Api.Tests.Terminals.Ingenico { + [TestClass] + public class TransactionTest { + IDeviceInterface _device; + + public TransactionTest() { + _device = DeviceService.Create(new ConnectionConfig() { + DeviceType = DeviceType.Ingenico_EPOS_Lane3000, + ConnectionMode = ConnectionModes.TCP_IP_SERVER, + Port = "18101", + Timeout = 60000, + RequestIdProvider = new RandomIdProvider() + }); + Assert.IsNotNull(_device); + } + + + [TestMethod] + public void AsyncCancelTest() { + var tsk1 = Task.Factory.StartNew(() => { + var respSale = _device.Sale(15.12m) + .WithCashBack(3m) + .WithReferenceNumber(02) + .Execute(); + + Assert.IsNotNull(respSale); + }); + + var tsk2 = Task.Factory.StartNew(() => { + Thread.Sleep(7000); + + var respCancel = _device.Cancel(); + + Assert.IsNotNull(respCancel); + Assert.AreEqual(respCancel.Status, "CANCEL_DONE"); + }); + + Thread.Sleep(10000); + _device.Dispose(); + + Task.WaitAll(tsk1, tsk2); + } + + [TestMethod] + public void ReverseTest() { + + Thread.Sleep(10000); + + var resSale = _device.Sale(125.12m) + .WithReferenceNumber(55) + .Execute(); + + Thread.Sleep(10000); + + if (resSale != null) { + var resp = _device.Reverse(amount: 6.18m) + .WithReferenceNumber(12) + .Execute(); + + var termId = resp.TerminalRefNumber; + + Assert.IsNotNull(termId); + Assert.AreEqual(resp.Status, "REVERSAL_SUCCESS"); + } + else Assert.IsNull(resSale); + } + + [TestMethod] + public void DuplicTest() { + + var duplicate = _device.Duplicate(); + + _device.Dispose(); + Assert.IsNotNull(duplicate); + Assert.AreEqual(((IngenicoTerminalResponse)duplicate).PrivateData, "DUPLICDONE"); + } + } +}