From bf639f257f3f95e9956cbc133dc46b92eb01a540 Mon Sep 17 00:00:00 2001 From: Samuel Coianiz Date: Mon, 7 May 2018 08:36:15 +1000 Subject: [PATCH] Release 1.4.0.0 (2018-05-07) * Added IDialogUIHandler for easier custom POS dialogs. * Updated TestPOS to handle changing Merchant Number * Added a check for SimpleDemoAsync sending Basket Data * EFTRequests now have a matching EFTResponse, constructors and booleans added to match --- .gitignore | 3 + IPInterface.SimpleDemoAsync/MainWindow.xaml | 5 +- .../MainWindow.xaml.cs | 51 +- .../IPInterface.TestPOS.csproj | 12 + .../Properties/AssemblyInfo.cs | 4 +- IPInterface.TestPOS/ViewModels/ClientData.cs | 332 +-- .../ViewModels/ClientViewModel.cs | 21 +- .../ViewModels/DialogUIHandler.cs | 147 + IPInterface.TestPOS/ViewModels/EftWrapper.cs | 1235 ++++---- .../ViewModels/ProxyViewModel.cs | 152 +- IPInterface.TestPOS/Views/MainWindow.xaml | 19 +- IPInterface.TestPOS/Views/ProxyDialog.xaml.cs | 5 +- IPInterface.TestPOS/Views/TestDialogUI.xaml | 149 + .../Views/TestDialogUI.xaml.cs | 27 + IPInterface/EFTClientIP.cs | 86 +- IPInterface/EFTClientIPAsync.cs | 1055 +++---- IPInterface/IDialogUIHandler.cs | 28 + IPInterface/IDialogUIHandlerAsync.cs | 29 + IPInterface/IEFTClientIP.cs | 9 +- IPInterface/IEFTClientIPAsync.cs | 25 + IPInterface/IPInterface.csproj | 19 +- IPInterface/Model/ChequeAuthRequest.cs | 6 +- IPInterface/Model/ControlPanel.cs | 17 +- IPInterface/Model/Display.cs | 2 +- IPInterface/Model/EFTBasketData.cs | 12 + IPInterface/Model/EFTClientList.cs | 8 +- IPInterface/Model/EFTCloudLogon.cs | 4 +- IPInterface/Model/EFTConfigMerchant.cs | 4 +- IPInterface/Model/EFTDuplicate.cs | 4 +- IPInterface/Model/EFTGetLast.cs | 4 +- IPInterface/Model/EFTLogon.cs | 6 +- IPInterface/Model/EFTPosAsPinpad.cs | 4 +- IPInterface/Model/EFTRequest.cs | 59 + IPInterface/Model/EFTSendKey.cs | 3 +- IPInterface/Model/EFTSettlement.cs | 4 +- IPInterface/Model/EFTTransaction.cs | 1138 ++++---- IPInterface/Model/GenericCommand.cs | 33 +- IPInterface/Model/QueryCard.cs | 16 +- IPInterface/Model/Receipt.cs | 13 +- IPInterface/Model/SetDialog.cs | 7 +- IPInterface/Model/Status.cs | 6 +- IPInterface/PCEFTPOS/Net/TcpSocketAsync.cs | 353 +-- IPInterface/PCEFTPOS/Net/TcpSocketSslAsync.cs | 513 ++-- IPInterface/Slave/EFTSlave.cs | 36 + IPInterface/{Model => Slave}/SlaveHelper.cs | 56 +- IPInterface/Util/Extensions.cs | 1 + IPInterface/Util/MessageParser.cs | 2534 +++++++++-------- README.md | 4 +- 48 files changed, 4586 insertions(+), 3674 deletions(-) create mode 100644 IPInterface.TestPOS/ViewModels/DialogUIHandler.cs create mode 100644 IPInterface.TestPOS/Views/TestDialogUI.xaml create mode 100644 IPInterface.TestPOS/Views/TestDialogUI.xaml.cs create mode 100644 IPInterface/IDialogUIHandler.cs create mode 100644 IPInterface/IDialogUIHandlerAsync.cs create mode 100644 IPInterface/Slave/EFTSlave.cs rename IPInterface/{Model => Slave}/SlaveHelper.cs (90%) diff --git a/.gitignore b/.gitignore index d78c339..448ffa7 100644 --- a/.gitignore +++ b/.gitignore @@ -287,3 +287,6 @@ __pycache__/ *.odx.cs *.xsd.cs *.bak +/IPInterface/IPInterface.nuspec +/IPInterface/pc-eftpos.cer +/IPInterface/PCEFTPOS.EFTClient.IPInterface.snk diff --git a/IPInterface.SimpleDemoAsync/MainWindow.xaml b/IPInterface.SimpleDemoAsync/MainWindow.xaml index 049a64a..645a260 100644 --- a/IPInterface.SimpleDemoAsync/MainWindow.xaml +++ b/IPInterface.SimpleDemoAsync/MainWindow.xaml @@ -65,7 +65,7 @@ - + @@ -143,7 +143,7 @@ - + @@ -188,6 +188,7 @@ + diff --git a/IPInterface.SimpleDemoAsync/MainWindow.xaml.cs b/IPInterface.SimpleDemoAsync/MainWindow.xaml.cs index eade5d9..096d645 100644 --- a/IPInterface.SimpleDemoAsync/MainWindow.xaml.cs +++ b/IPInterface.SimpleDemoAsync/MainWindow.xaml.cs @@ -260,28 +260,29 @@ async void BtnTender_Click(object sender, RoutedEventArgs e) } } }; - - if(!await _eft.WriteRequestAsync(new EFTBasketDataRequest() { Command = new EFTBasketDataCommandCreate() { Basket = basket } })) - { - ShowNotification("FAILED TO SEND TXN", "", "", NotificationType.Error, true); - } - else - { - try - { - await _eft.ReadResponseAsync(new CancellationTokenSource(new TimeSpan(0, 0, 10)).Token); - } - catch (TaskCanceledException) - { - // EFT-Client timeout waiting for response - ShowNotification("EFT-CLIENT TIMEOUT", null, null, NotificationType.Error, true); - } - catch (Exception) - { - // TODO: Handle failed EFTBasketDataRequest. Should still continue and attempt the transaction. - } - } - + if (chkBasketData.IsChecked == true) + { + if (!await _eft.WriteRequestAsync(new EFTBasketDataRequest() { Command = new EFTBasketDataCommandCreate() { Basket = basket } })) + { + ShowNotification("FAILED TO SEND TXN", "", "", NotificationType.Error, true); + } + else + { + try + { + await _eft.ReadResponseAsync(new CancellationTokenSource(new TimeSpan(0, 0, 10)).Token); + } + catch (TaskCanceledException) + { + // EFT-Client timeout waiting for response + ShowNotification("EFT-CLIENT TIMEOUT", null, null, NotificationType.Error, true); + } + catch (Exception) + { + // TODO: Handle failed EFTBasketDataRequest. Should still continue and attempt the transaction. + } + } + } // Transaction request var r = new EFTTransactionRequest(); @@ -297,7 +298,11 @@ async void BtnTender_Click(object sender, RoutedEventArgs e) // Set application. Used for gift card & 3rd party payment r.Application = TerminalApplication.EFTPOS; // Set basket PAD tag - r.PurchaseAnalysisData.SetTag("SKU", basketId); + if(chkBasketData.IsChecked == true) + { + r.PurchaseAnalysisData.SetTag("SKU", basketId); + } + if (!await _eft.WriteRequestAsync(r)) { diff --git a/IPInterface.TestPOS/IPInterface.TestPOS.csproj b/IPInterface.TestPOS/IPInterface.TestPOS.csproj index 9099dfd..d2743b7 100644 --- a/IPInterface.TestPOS/IPInterface.TestPOS.csproj +++ b/IPInterface.TestPOS/IPInterface.TestPOS.csproj @@ -75,14 +75,22 @@ + + + TestDialogUI.xaml + ProxyDialog.xaml + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -123,6 +131,10 @@ + + {dd628b2f-ff0f-42a5-b9bf-1815440b0cb9} + IPInterface.UIInterfaceDemo + {3de3ff35-5303-41f0-9f66-5b5ab2b6b7df} IPInterface diff --git a/IPInterface.TestPOS/Properties/AssemblyInfo.cs b/IPInterface.TestPOS/Properties/AssemblyInfo.cs index 3d014e7..646f74b 100644 --- a/IPInterface.TestPOS/Properties/AssemblyInfo.cs +++ b/IPInterface.TestPOS/Properties/AssemblyInfo.cs @@ -51,7 +51,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.1.0")] -[assembly: AssemblyFileVersion("1.3.1.0")] +[assembly: AssemblyVersion("1.3.2.0")] +[assembly: AssemblyFileVersion("1.3.2.0")] [assembly: Guid("4c988f82-f976-4663-9541-d0178f5aae47")] diff --git a/IPInterface.TestPOS/ViewModels/ClientData.cs b/IPInterface.TestPOS/ViewModels/ClientData.cs index c6d39dd..47f8e06 100644 --- a/IPInterface.TestPOS/ViewModels/ClientData.cs +++ b/IPInterface.TestPOS/ViewModels/ClientData.cs @@ -14,168 +14,174 @@ public enum ConnectedStatus { Connected, Disconnected }; public delegate void LogEvent(string message); public delegate void DisplayEvent(bool show); - public class ClientData : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - public event LogEvent OnLog; - public event DisplayEvent OnDisplay; - public event EventHandler OnDisplayChanged; - - public ClientData() - { - _dctCommands.Add("Enter Slave Mode", "*@101S1004300 "); - _dctCommands.Add("Display 'Swipe Card'", "*@103Z 0060216 D 0240000 SWIPE CARD D 0240100 "); - _dctCommands.Add("Swipe Card", "*@102J1000K100810000010"); - _dctCommands.Add("Exit Slave Mode", "*@101S0000"); - _dctCommands.Add("Complete Read Card Command", "*@107S1004300 Z 0060216 D 0240000 SWIPE CARD D 0240100 J1000K100810000010S0000"); - } - - #region Connect - private ConnectedStatus _connectedState = ConnectedStatus.Disconnected; - public ConnectedStatus ConnectedState - { - get - { - return _connectedState; - } - set - { - _connectedState = value; - NotifyPropertyChanged("ConnectedState"); - } - } - - #endregion - - #region Logon - public LogonType SelectedLogon { get; set; } = LogonType.Standard; - public ObservableCollection LogonList { get { return GetEnum(); } } - - private bool _logonTestEnabled = false; - public bool LogonTestEnabled - { - set - { - _logonTestEnabled = value; - NotifyPropertyChanged("LogonTestEnabled"); - } - get - { - return _logonTestEnabled; - } - } - - #endregion - - #region Receipt Options - - public bool CutReceipt { set; get; } - public ReceiptCutModeType CutReceiptMode - { - get - { - return (CutReceipt ? ReceiptCutModeType.Cut : ReceiptCutModeType.DontCut); - } - } - - public ReceiptPrintModeType PrintMode { get; set; } = ReceiptPrintModeType.PinpadPrinter; - public ObservableCollection PrintModeList { get { return GetEnum(); } } - #endregion - - #region Transaction - public bool IsETS { get; set; } = false; - public ObservableCollection TerminalList { get { return GetEnum(); } } - - private int _transactionCount = 0; - public string TransactionReference - { - get - { - return string.Format("{0:ddMMyyyyHHmm}{1:D3}", DateTime.Now, _transactionCount); - } - set - { - _transactionCount++; - NotifyPropertyChanged("TransactionReference"); - } - } - - public EFTTransactionRequest TransactionRequest { get; set; } = new EFTTransactionRequest(); - - public ObservableCollection TransactionList { get { return GetFilteredEnum(); } } - - public ObservableCollection CardSourceList { get { return GetEnum(); } } - string _selectedCardSource = string.Empty; - public string SelectedCardSource - { - get { return _selectedCardSource; } - set { _selectedCardSource = value; NotifyPropertyChanged("SelectedCardSource"); } - } - - public ObservableCollection AccountList { get { return GetEnum(); } } - - ExternalDataList _track2Items = new ExternalDataList(); - public ExternalDataList Track2Items - { - get - { - return _track2Items; - } - set - { - _track2Items = value; - NotifyPropertyChanged("Track2List"); - } - } - - string _selectedTrack2 = string.Empty; - public string SelectedTrack2 - { - get { return _selectedTrack2; } - set { _selectedTrack2 = value; NotifyPropertyChanged("SelectedTrack2"); } - } - - public ObservableCollection Track2List - { - get - { - var list = new ObservableCollection(); - _track2Items.ForEach(x => list.Add(x.ToString())); - return list; - } - } - - ExternalDataList _padItems = new ExternalDataList(); - public ExternalDataList PadItems - { - get { return _padItems; } - set { _padItems = value; NotifyPropertyChanged("PadList"); } - } - - - public string SelectedPad { get; set; } = string.Empty; - public ObservableCollection PadList - { - get - { - var list = new ObservableCollection(); - PadItems.ForEach(x => list.Add(x.ToString())); - return list; - } - } -#endregion - - #region ETS Transaction - public ObservableCollection ETSTransactionList { get { return GetFilteredEnum("ETS"); } } - #endregion - - #region Status - public StatusType SelectedStatus { get; set; } - public ObservableCollection StatusList { get { return GetEnum(); } } - #endregion - - #region Configure Merchant - public EFTConfigureMerchantRequest MerchantDetails { get; set; } = new EFTConfigureMerchantRequest(); + public class ClientData : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public event LogEvent OnLog; + public event DisplayEvent OnDisplay; + public event EventHandler OnDisplayChanged; + + public ClientData() + { + _dctCommands.Add("Enter Slave Mode", "*@101S1004300 "); + _dctCommands.Add("Display 'Swipe Card'", "*@103Z 0060216 D 0240000 SWIPE CARD D 0240100 "); + _dctCommands.Add("Swipe Card", "*@102J1000K100810000010"); + _dctCommands.Add("Exit Slave Mode", "*@101S0000"); + _dctCommands.Add("Complete Read Card Command", "*@107S1004300 Z 0060216 D 0240000 SWIPE CARD D 0240100 J1000K100810000010S0000"); + } + + #region Connect + private ConnectedStatus _connectedState = ConnectedStatus.Disconnected; + public ConnectedStatus ConnectedState + { + get + { + return _connectedState; + } + set + { + _connectedState = value; + NotifyPropertyChanged("ConnectedState"); + } + } + + #endregion + + #region Logon + public LogonType SelectedLogon { get; set; } = LogonType.Standard; + public ObservableCollection LogonList { get { return GetEnum(); } } + + private bool _logonTestEnabled = false; + public bool LogonTestEnabled + { + set + { + _logonTestEnabled = value; + NotifyPropertyChanged("LogonTestEnabled"); + } + get + { + return _logonTestEnabled; + } + } + + #endregion + + #region Receipt Options + + public bool CutReceipt { set; get; } + public ReceiptCutModeType CutReceiptMode + { + get + { + return (CutReceipt ? ReceiptCutModeType.Cut : ReceiptCutModeType.DontCut); + } + } + + public ReceiptPrintModeType PrintMode { get; set; } = ReceiptPrintModeType.PinpadPrinter; + public ObservableCollection PrintModeList { get { return GetEnum(); } } + #endregion + + #region Transaction + public bool IsETS { get; set; } = false; + public bool IsPrintTimeOut { get; set; } = false; + public bool IsPrePrintTimeOut { get; set; } = false; + public ObservableCollection TerminalList { get { return GetEnum(); } } + + private int _transactionCount = 0; + public string TransactionReference + { + get + { + return string.Format("{0:ddMMyyyyHHmm}{1:D3}", DateTime.Now, _transactionCount); + } + set + { + _transactionCount++; + NotifyPropertyChanged("TransactionReference"); + } + } + + public EFTTransactionRequest TransactionRequest { get; set; } = new EFTTransactionRequest(); + + public ObservableCollection TransactionList { get { return GetFilteredEnum(); } } + + public ObservableCollection CardSourceList { get { return GetEnum(); } } + string _selectedCardSource = string.Empty; + public string SelectedCardSource + { + get { return _selectedCardSource; } + set { _selectedCardSource = value; NotifyPropertyChanged("SelectedCardSource"); } + } + + public ObservableCollection AccountList { get { return GetEnum(); } } + + ExternalDataList _track2Items = new ExternalDataList(); + public ExternalDataList Track2Items + { + get + { + return _track2Items; + } + set + { + _track2Items = value; + NotifyPropertyChanged("Track2List"); + } + } + + string _selectedTrack2 = string.Empty; + public string SelectedTrack2 + { + get { return _selectedTrack2; } + set { _selectedTrack2 = value; NotifyPropertyChanged("SelectedTrack2"); } + } + + public ObservableCollection Track2List + { + get + { + var list = new ObservableCollection(); + _track2Items.ForEach(x => list.Add(x.ToString())); + return list; + } + } + + ExternalDataList _padItems = new ExternalDataList(); + public ExternalDataList PadItems + { + get { return _padItems; } + set { _padItems = value; NotifyPropertyChanged("PadList"); } + } + + + public string SelectedPad { get; set; } = string.Empty; + public ObservableCollection PadList + { + get + { + var list = new ObservableCollection(); + PadItems.ForEach(x => list.Add(x.ToString())); + return list; + } + } + #endregion + + #region ETS Transaction + public ObservableCollection ETSTransactionList { get { return GetFilteredEnum("ETS"); } } + #endregion + + #region Status + public StatusType SelectedStatus { get; set; } + public ObservableCollection StatusList { get { return GetEnum(); } } + #endregion + + #region ClientList + public EFTClientListRequest ClientListRequest{ get; set; } = new EFTClientListRequest(); + #endregion + + #region Configure Merchant + public EFTConfigureMerchantRequest MerchantDetails { get; set; } = new EFTConfigureMerchantRequest(); #endregion #region Settlement @@ -196,7 +202,7 @@ public ObservableCollection PadList #endregion #region Cheque Auth - public ChequeAuthRequest ChequeRequest { get; set; } = new ChequeAuthRequest(); + public EFTChequeAuthRequest ChequeRequest { get; set; } = new EFTChequeAuthRequest(); public ObservableCollection ChequeList { get { return GetEnum(); } } #endregion diff --git a/IPInterface.TestPOS/ViewModels/ClientViewModel.cs b/IPInterface.TestPOS/ViewModels/ClientViewModel.cs index 2015aef..ed3e633 100644 --- a/IPInterface.TestPOS/ViewModels/ClientViewModel.cs +++ b/IPInterface.TestPOS/ViewModels/ClientViewModel.cs @@ -414,10 +414,25 @@ public RelayCommand Status } } - #endregion + #endregion + + #region ClientList + + public RelayCommand ClientList + { + get + { + return new RelayCommand(async (o) => + { + await _eftw.DoClientList(new EFTClientListRequest()); + }); + } + } + + #endregion - #region Configure Merchant - public RelayCommand ConfigureMerchant + #region Configure Merchant + public RelayCommand ConfigureMerchant { get { diff --git a/IPInterface.TestPOS/ViewModels/DialogUIHandler.cs b/IPInterface.TestPOS/ViewModels/DialogUIHandler.cs new file mode 100644 index 0000000..ed8e725 --- /dev/null +++ b/IPInterface.TestPOS/ViewModels/DialogUIHandler.cs @@ -0,0 +1,147 @@ +using IPInterface.TestPOS.Views; +using PCEFTPOS.EFTClient.IPInterface; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace IPInterface.TestPOS.ViewModels +{ + public class DialogUIHandler : IDialogUIHandlerAsync, INotifyPropertyChanged + { + public IEFTClientIPAsync EFTClientIPAsync { get; set; } + private EFTDisplayResponse _displayResponse = new EFTDisplayResponse(); + public event PropertyChangedEventHandler PropertyChanged; + public TestDialogUI eftDialog = null; + public bool ProxyWindowClosing = false; + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public EFTDisplayResponse DisplayResponse + { + get + { + return _displayResponse; + } + set + { + _displayResponse = value; + OnPropertyChanged(nameof(DisplayResponse)); + } + } + + public Task HandleCloseDisplayAsync() + { + if(eftDialog != null) + { + eftDialog.Close(); + eftDialog.BtnOK.Click -= BtnOK_Click; + eftDialog.BtnCancel.Click -= BtnCancel_Click; + eftDialog = null; + } + return Task.FromResult(0); + } + + public Task HandleDisplayResponseAsync(EFTDisplayResponse eftDisplayResponse) + { + DisplayResponse = eftDisplayResponse; + if (eftDialog == null) + { + eftDialog = new TestDialogUI(); + eftDialog.DataContext = this; + eftDialog.BtnOK.Click += BtnOK_Click; + eftDialog.BtnCancel.Click += BtnCancel_Click; + eftDialog.txtResponseLine1.Text = eftDisplayResponse.DisplayText[0]; + eftDialog.txtResponseLine2.Text = eftDisplayResponse.DisplayText[1]; + eftDialog.Show(); + } + loadButtons(eftDisplayResponse); + if (eftDisplayResponse.InputType != InputType.None) + { + eftDialog.txtInput.Visibility = System.Windows.Visibility.Visible; + } + return Task.FromResult(0); + } + + private void BtnOK_Click(object sender, System.Windows.RoutedEventArgs e) + { + switch (eftDialog.BtnOK.Content) + { + case ("OK"): + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel, Data = (eftDialog.txtInput.Text != "") ? eftDialog.txtInput.Text : null }); + break; + case ("Authorise"): + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.Authorise, Data = (eftDialog.txtInput.Text != "") ? eftDialog.txtInput.Text : null }); + break; + case ("Yes"): + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.YesAccept, Data = (eftDialog.txtInput.Text != "") ? eftDialog.txtInput.Text : null }); + break; + default: + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel, Data = (eftDialog.txtInput.Text != "") ? eftDialog.txtInput.Text : null }); + break; + + } + } + + private void BtnCancel_Click(object sender, System.Windows.RoutedEventArgs e) + { + switch (eftDialog.BtnCancel.Content) + { + case ("Cancel"): + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel }); + break; + case ("Decline"): + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.NoDecline }); + break; + default: + EFTClientIPAsync?.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel }); + break; + + } + } + + private void loadButtons(EFTDisplayResponse eftDisplayResponse) + { + #region OKButtons + if (eftDisplayResponse.AcceptYesKeyFlag == true) + { + eftDialog.BtnOK.Content = "Yes"; + eftDialog.BtnOK.Visibility = System.Windows.Visibility.Visible; + } + else if (eftDisplayResponse.AuthoriseKeyFlag == true) + { + eftDialog.BtnOK.Content = "Authorise"; + eftDialog.BtnOK.Visibility = System.Windows.Visibility.Visible; + } + else if (eftDisplayResponse.OKKeyFlag == true) + { + eftDialog.BtnOK.Content = "OK"; + eftDialog.BtnOK.Visibility = System.Windows.Visibility.Visible; + } + else + { + eftDialog.BtnOK.Visibility = System.Windows.Visibility.Collapsed; + } + #endregion + + #region CancelButtons + if (eftDisplayResponse.CancelKeyFlag == true) + { + eftDialog.BtnCancel.Content = "Cancel"; + eftDialog.BtnCancel.Visibility = System.Windows.Visibility.Visible; + } + else if (eftDisplayResponse.DeclineNoKeyFlag == true) + { + eftDialog.BtnCancel.Content = "No"; + eftDialog.BtnCancel.Visibility = System.Windows.Visibility.Visible; + } + else + { + eftDialog.BtnCancel.Visibility = System.Windows.Visibility.Collapsed; + } + #endregion + } + } +} diff --git a/IPInterface.TestPOS/ViewModels/EftWrapper.cs b/IPInterface.TestPOS/ViewModels/EftWrapper.cs index 3e9daf2..d7660f0 100644 --- a/IPInterface.TestPOS/ViewModels/EftWrapper.cs +++ b/IPInterface.TestPOS/ViewModels/EftWrapper.cs @@ -7,598 +7,645 @@ namespace PCEFTPOS.EFTClient.IPInterface.TestPOS { - public class EftWrapper - { - IEFTClientIPAsync _eft = new EFTClientIPAsync(); - ClientData _data = null; - CancellationTokenSource _ct = null; - - /// - /// Set to true when a request is in progress and false when a request ends. Used to limit access to SendRequest - /// - bool _requestInProgress = false; - - //System.Timers.Timer _checkStatusTimer = null; - - public EftWrapper(ClientData data) - { - _data = data; - - //_checkStatusTimer = new System.Timers.Timer(15000 /* 15 seconds */); - //_checkStatusTimer.Elapsed += _checkStatusTimer_Elapsed; - - _eft.OnLog += Eft_OnLog; - } - - private string _eftLogs = string.Empty; - private void Eft_OnLog(object sender, LogEventArgs e) - { - if (_data.SendKeyEnabled) - { - _eftLogs = e.Message; - } - else - { - var logType = LogType.Info; - switch(e.LogLevel) - { - case LogLevel.Error: - case LogLevel.Fatal: - logType = LogType.Error; - break; - case LogLevel.Warn: - logType = LogType.Warning; - break; - default: - logType = LogType.Info; - break; - } - - var msg = (logType == LogType.Error && e.Exception != null) ? $"{e.Message} [{e.Exception.Message}]" : e.Message; - - _data.Log(msg, logType); - } - } - - #region Common - private Dictionary DictionaryFromType(object atype) - { - Dictionary d = new Dictionary(); - if (atype == null) - return d; - - Type t = atype.GetType(); - - try - { - foreach (PropertyInfo prp in t.GetProperties()) - { - var p = prp.GetValue(atype, new object[] { }); - if (p != null) - { - string value = p.ToString(); - - Type tt = p.GetType(); - if (p is TransactionType) - { - var txn = (TransactionType)p; - value = txn.ToTransactionString(); - } - else if (tt.IsClass && tt != typeof(string) && !tt.IsArray) - { - var e = DictionaryFromType(p); - if (e.Count > 0) - { - value = string.Empty; - foreach (var i in e) - { - value += $"{i.Key}: {i.Value};{Environment.NewLine}"; - } - } - } - else if (tt == typeof(string[])) - { - value = string.Empty; - string[] arr = p as string[]; - foreach (string s in arr) - { - value += s + Environment.NewLine; - } - _data.Log(value); - } - - d.Add(prp.Name, value); - } - } - } - catch - { - } - return d; - } - - private void ShowError(string hr, string message) - { - _data.LastTxnResult.Clear(); - var x = new Dictionary - { - { "Result", "Failed" }, - { "ResponseCode", hr }, - { "ResponseText", message } - }; - _data.LastTxnResult = x; - } - - private async Task SendRequest(EFTRequest request, bool autoApproveDialogs = false) - { - bool result = false; - - if(_requestInProgress) - { - _data.Log($"Unable to process {request.ToString()}. There is already a request in progress"); - return false; - } - - try - { - await _eft.WriteRequestAsync(request); - - _requestInProgress = true; - do - { - var timeoutToken = new CancellationTokenSource(new TimeSpan(0, 5, 0)).Token; // 5 minute timeout - var r = await _eft.ReadResponseAsync(timeoutToken); - - if (r == null) // stream is busy - { - _data.Log($"Unable to process {request.ToString()}. Stream is busy."); - } - else if (r is EFTDisplayResponse) - { - if (_data.Settings.DemoDialogOption != DemoDialogMode.Hide) - { - EFTDisplayResponse res = r as EFTDisplayResponse; - _data.DisplayDetails = res; - _data.DisplayDialog(true); - } - - if(autoApproveDialogs && (r as EFTDisplayResponse).OKKeyFlag) - { - await _eft.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel }); - } - } - else if (r is EFTReceiptResponse || r is EFTReprintReceiptResponse) - { - string[] receiptText = (r is EFTReceiptResponse) ? (r as EFTReceiptResponse).ReceiptText : (r as EFTReprintReceiptResponse).ReceiptText; - - StringBuilder receipt = new StringBuilder(); - foreach (var s in receiptText) - { - if (s.Length > 0) - { - receipt.AppendLine(s); - } - } - - if (!string.IsNullOrEmpty(receipt.ToString())) - { - _data.Receipt = receipt.ToString(); - } - - if (request is EFTReceiptRequest || request is EFTReprintReceiptRequest) - { - _requestInProgress = false; - } - } - else if (r is EFTQueryCardResponse) - { - _data.LastTxnResult = DictionaryFromType(r); - var x = (EFTQueryCardResponse)r; - _data.SelectedTrack2 = x.Track2; - _data.DisplayDialog(false); - _requestInProgress = false; - } - else - { - _data.LastTxnResult = DictionaryFromType(r); - string output = string.Empty; - _data.LastTxnResult.TryGetValue("Success", out output); - result = (output.Equals("True")); - _data.DisplayDialog(false); - _requestInProgress = false; - } - } - while (_requestInProgress); - - if (!_requestInProgress) - { - _data.Log($"Request: {request.ToString()} done!"); - } - } - catch(TaskCanceledException) - { - _data.ConnectedState = ConnectedStatus.Disconnected; - ShowError("EFT-Client Timeout", ""); - } - catch (ConnectionException cx) - { - _data.ConnectedState = ConnectedStatus.Disconnected; - ShowError(cx.HResult.ToString(), cx.Message); - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - - _requestInProgress = false; - return result; - } - - #endregion - - #region Connect - - public async Task Connect(string ip, int port, bool useSSL) - { - if (_data.ConnectedState == ConnectedStatus.Connected) - { - return true; - } - - try - { - if (await _eft.ConnectAsync(ip, port, useSSL)) - { - _data.ConnectedState = ConnectedStatus.Connected; - //_checkStatusTimer.Enabled = true; - return true; - } - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - return false; - } - - private async void _checkStatusTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - if (_requestInProgress == false) - { - _requestInProgress = true; - if (await _eft.CheckConnectStateAsync() == false) - { - _data.Log("CheckConnectState failed. Connection has been dropped."); - Disconnect(); - } - _requestInProgress = false; - } - } - - public void Disconnect() - { - try - { - //_checkStatusTimer.Enabled = false; - _data.ConnectedState = ConnectedStatus.Disconnected; - _eft.Disconnect(); - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - } - - #endregion - - #region Logon - public async Task Logon(LogonType logonType, ReceiptCutModeType cutMode, ReceiptPrintModeType printMode, bool autoApproveDialogs) - { - await SendRequest(new EFTLogonRequest() - { - LogonType = logonType, - ReceiptCutMode = cutMode, - ReceiptPrintMode = printMode - }, autoApproveDialogs); - } - - public async Task CloudLogon(string clientId, string password, string pairingCode) - { - await SendRequest(new EFTCloudLogonRequest() - { - ClientID = clientId, - Password = password, - PairingCode = pairingCode - }); - } - - public async Task StartLogonTest(LogonType type, ReceiptCutModeType cutMode, ReceiptPrintModeType printMod) - { - _ct = new CancellationTokenSource(); - await SpawnLogon(type, cutMode, printMod, _ct.Token); - } - - public void StopLogonTest() - { - _ct.Cancel(); - _ct.Dispose(); - } - - private async Task SpawnLogon(LogonType type, ReceiptCutModeType cutMode, ReceiptPrintModeType printMod, CancellationToken token) - { - while (!token.IsCancellationRequested) - { - await Task.Delay(10); - await Logon(type, cutMode, printMod, true); - } - } - - #endregion - - #region ControlPanel - public async Task DisplayControlPanel(ControlPanelType option, ReceiptCutModeType cutMode, ReceiptPrintModeType printMode) - { - try - { - await _eft.WriteRequestAsync(new ControlPanelRequest() - { - ControlPanelType = option, - ReceiptCutMode = cutMode, - ReceiptPrintMode = printMode, - ReturnType = ControlPanelReturnType.Immediately - }); - - - bool waiting = true; - do - { - var timeoutToken = new CancellationTokenSource(new TimeSpan(0, 5, 0)).Token; // 5 minute timeout - var r = await _eft.ReadResponseAsync(timeoutToken); - - if (r is EFTDisplayResponse) - { - if (_data.Settings.DemoDialogOption != DemoDialogMode.Hide) - { - EFTDisplayResponse res = r as EFTDisplayResponse; - _data.DisplayDetails = res; - _data.DisplayDialog(true); - } - } - else if (r is EFTReceiptResponse || r is EFTReprintReceiptResponse) - { - string[] receiptText = (r is EFTReceiptResponse) ? (r as EFTReceiptResponse).ReceiptText : (r as EFTReprintReceiptResponse).ReceiptText; - - StringBuilder receipt = new StringBuilder(); - foreach (var s in receiptText) - { - if (s.Length > 0) - { - receipt.AppendLine(s); - } - } - - if (!string.IsNullOrEmpty(receipt.ToString())) - { - _data.Receipt = receipt.ToString(); - } - } - else - { - _data.LastTxnResult = DictionaryFromType(r); - _data.DisplayDialog(false); - } - } - while (waiting); - - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - - } - #endregion - - #region Last Transaction - public async Task GetLastTransaction() - { - await SendRequest(new EFTGetLastTransactionRequest()); - } - - public async Task LastReceipt(ReceiptCutModeType cutMode, ReceiptPrintModeType printMode, ReprintType type) - { - await SendRequest(new EFTReprintReceiptRequest() - { - ReceiptCutMode = cutMode, - ReceiptPrintMode = printMode, - ReprintType = type - }); - } - #endregion - - #region Transaction - - - - public async Task DoTransaction(EFTTransactionRequest request) - { - bool result = await SendRequest(request); - if (result) - { - _data.TransactionReference = string.Empty; - } - } - #endregion - - #region Set Dialog - public async Task SetDialog(int x, int y, bool displayEvents, DialogPosition pos, string title, bool topMost, DialogType dialogType) - { - await SendRequest(new SetDialogRequest - { - DialogX = x, - DialogY = y, - DisableDisplayEvents = displayEvents, - DialogPosition = pos, - DialogTitle = title, - EnableTopmost = topMost, - DialogType = dialogType - }); - } - - public async Task SetDialog(SetDialogRequest request) - { - await SendRequest(request); - } - - #endregion - - #region Query Card - public async Task QueryCard(PadField pad, QueryCardType cardType) - { - await SendRequest(new QueryCardRequest - { - QueryCardType = cardType, - PurchaseAnalysisData = pad - }); - } - #endregion - - #region Status - public async Task GetStatus(StatusType statusType) - { - await SendRequest(new EFTStatusRequest - { - StatusType = statusType - }); - } - #endregion - - #region Config Merchant - public async Task ConfigureMerchant(int aiic, string merchantId, int nii, string terminalId, int timeout) - { - await SendRequest(new EFTConfigureMerchantRequest - { - AIIC = aiic, - Caid = merchantId, - NII = nii, - Catid = terminalId, - Timeout = timeout - }); - } - - public async Task ConfigureMerchant(EFTConfigureMerchantRequest request) - { - await SendRequest(request); - } - #endregion - - #region Settlement - public async Task DoSettlement(SettlementType settlement, ReceiptCutModeType cutMode, - PadField padInfo, ReceiptPrintModeType printMode, bool resetTotals) - { - await SendRequest(new EFTSettlementRequest - { - SettlementType = settlement, - ReceiptCutMode = cutMode, - PurchaseAnalysisData = padInfo, - ReceiptPrintMode = printMode, - ResetTotals = resetTotals - }); - } - #endregion - - #region Cheque Auth - public async Task DoVerifyCheque(ChequeAuthRequest request) - { - await SendRequest(request); - } - #endregion - - #region Slave Mode - public async Task DoSlaveMode(string cmd) - { - await SendRequest(new EFTSlaveRequest - { - Command = cmd - }); - } - #endregion - - #region SendKey - public async Task SendKey(EFTPOSKey option, string data = "") - { - try - { - await _eft.WriteRequestAsync(new EFTSendKeyRequest - { - Data = data, - Key = option - }); - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - } - - public async Task StartSendKeysTest(EFTPOSKey key) - { - _ct = new CancellationTokenSource(); - - var progress = new Progress((s) => - { - _data.Log(s); - }); - - _data.Progress = progress; - - await Task.Run(() => SpawnSendKeys(key, _ct.Token, progress), _ct.Token); - } - - public void StopSendKeysTest() - { - _ct.Cancel(); - _ct.Dispose(); - } - - private async Task SpawnSendKeys(EFTPOSKey key, CancellationToken token, IProgress p) - { - try - { - while (!token.IsCancellationRequested) - { - await Task.Delay(100); - await _eft.WriteRequestAsync(new EFTSendKeyRequest - { - Key = key - }); - p.Report(_eftLogs); - } - } - catch (Exception ex) - { - ShowError(ex.HResult.ToString(), ex.Message); - } - } - #endregion - - #region PIN - public async Task AuthPin() - { - await SendRequest(new EFTTransactionRequest - { - TxnType = TransactionType.AuthPIN - }); - } - - public async Task ChangePin() - { - await SendRequest(new EFTTransactionRequest - { - TxnType = TransactionType.EnhancedPIN - }); - } - #endregion - - } + public class EftWrapper + { + IEFTClientIPAsync _eft = new EFTClientIPAsync(); + ClientData _data = null; + CancellationTokenSource _ct = null; + + /// + /// Set to true when a request is in progress and false when a request ends. Used to limit access to SendRequest + /// + bool _requestInProgress = false; + + //System.Timers.Timer _checkStatusTimer = null; + + public EftWrapper(ClientData data) + { + _data = data; + + //_checkStatusTimer = new System.Timers.Timer(15000 /* 15 seconds */); + //_checkStatusTimer.Elapsed += _checkStatusTimer_Elapsed; + + _eft.OnLog += Eft_OnLog; + } + + private string _eftLogs = string.Empty; + private void Eft_OnLog(object sender, LogEventArgs e) + { + if (_data.SendKeyEnabled) + { + _eftLogs = e.Message; + } + else + { + var logType = LogType.Info; + switch (e.LogLevel) + { + case LogLevel.Error: + case LogLevel.Fatal: + logType = LogType.Error; + break; + case LogLevel.Warn: + logType = LogType.Warning; + break; + default: + logType = LogType.Info; + break; + } + + var msg = (logType == LogType.Error && e.Exception != null) ? $"{e.Message} [{e.Exception.Message}]" : e.Message; + + _data.Log(msg, logType); + } + } + + #region Common + private Dictionary DictionaryFromType(object atype) + { + Dictionary d = new Dictionary(); + if (atype == null) + return d; + + Type t = atype.GetType(); + + try + { + foreach (PropertyInfo prp in t.GetProperties()) + { + var p = prp.GetValue(atype, new object[] { }); + if (p != null) + { + string value = p.ToString(); + + Type tt = p.GetType(); + if (p is TransactionType) + { + var txn = (TransactionType)p; + value = txn.ToTransactionString(); + } + else if (tt.IsClass && tt != typeof(string) && !tt.IsArray) + { + var e = DictionaryFromType(p); + if (e.Count > 0) + { + value = string.Empty; + foreach (var i in e) + { + value += $"{i.Key}: {i.Value};{Environment.NewLine}"; + } + } + } + else if (tt == typeof(string[])) + { + value = string.Empty; + string[] arr = p as string[]; + foreach (string s in arr) + { + value += s + Environment.NewLine; + } + _data.Log(value); + } + + d.Add(prp.Name, value); + } + } + } + catch + { + } + return d; + } + + private void ShowError(string hr, string message) + { + _data.LastTxnResult.Clear(); + var x = new Dictionary + { + { "Result", "Failed" }, + { "ResponseCode", hr }, + { "ResponseText", message } + }; + _data.LastTxnResult = x; + } + + private async Task SendRequest(EFTRequest request, bool autoApproveDialogs = false) + { + bool result = false; + + if (_requestInProgress) + { + _data.Log($"Unable to process {request.ToString()}. There is already a request in progress"); + return false; + } + + try + { + await _eft.WriteRequestAsync(request); + + _requestInProgress = true; + do + { + var timeoutToken = new CancellationTokenSource(new TimeSpan(0, 5, 0)).Token; // 5 minute timeout + var r = await _eft.ReadResponseAsync(timeoutToken); + + if (r == null) // stream is busy + { + _data.Log($"Unable to process {request.ToString()}. Stream is busy."); + } + else if (r is EFTDisplayResponse) + { + if (_data.Settings.DemoDialogOption != DemoDialogMode.Hide) + { + EFTDisplayResponse res = r as EFTDisplayResponse; + _data.DisplayDetails = res; + _data.DisplayDialog(true); + } + + if (autoApproveDialogs && (r as EFTDisplayResponse).OKKeyFlag) + { + await _eft.WriteRequestAsync(new EFTSendKeyRequest() { Key = EFTPOSKey.OkCancel }); + } + } + else if (r is EFTReceiptResponse || r is EFTReprintReceiptResponse) + { + // Hacked in some preprint and print response timeouts. It ain't pretty and it may not work, but it's my code. + #region PrintResponse Timeout + if (_data.IsPrePrintTimeOut) + { + if ((r as EFTReceiptResponse).IsPrePrint) + { + Thread.Sleep(59000); + } + } + else if (_data.IsPrintTimeOut) + { + if (!(r as EFTReceiptResponse).IsPrePrint) + { + Thread.Sleep(59000); + } + } + #endregion + + string[] receiptText = (r is EFTReceiptResponse) ? (r as EFTReceiptResponse).ReceiptText : (r as EFTReprintReceiptResponse).ReceiptText; + + StringBuilder receipt = new StringBuilder(); + foreach (var s in receiptText) + { + if (s.Length > 0) + { + receipt.AppendLine(s); + } + } + + if (!string.IsNullOrEmpty(receipt.ToString())) + { + _data.Receipt = receipt.ToString(); + } + + if (request is EFTReceiptRequest || request is EFTReprintReceiptRequest) + { + _requestInProgress = false; + } + } + else if (r is EFTQueryCardResponse) + { + _data.LastTxnResult = DictionaryFromType(r); + var x = (EFTQueryCardResponse)r; + _data.SelectedTrack2 = x.Track2; + _data.DisplayDialog(false); + _requestInProgress = false; + } + else if (r is EFTClientListResponse) + { + int index = 1; + var x = (EFTClientListResponse)r; + foreach (var clnt in x.EFTClients) + { + _data.LastTxnResult.Add("Client " + index.ToString() + " " + nameof(clnt.Name), clnt.Name); + _data.LastTxnResult.Add("Client " + index.ToString() + " " + nameof(clnt.IPAddress), clnt.IPAddress); + _data.LastTxnResult.Add("Client " + index.ToString() + " " + nameof(clnt.Port), clnt.Port.ToString()); + _data.LastTxnResult.Add("Client " + index.ToString() + " " + nameof(clnt.State), clnt.State.ToString()); + index++; + } + //_data.LastTxnResult = DictionaryFromType(x.EFTClients); + _data.DisplayDialog(false); + _requestInProgress = false; + } + else + { + _data.LastTxnResult = DictionaryFromType(r); + string output = string.Empty; + _data.LastTxnResult.TryGetValue("Success", out output); + result = (output.Equals("True")); + _data.DisplayDialog(false); + _requestInProgress = false; + } + } + while (_requestInProgress); + + if (!_requestInProgress) + { + _data.Log($"Request: {request.ToString()} done!"); + } + } + catch (TaskCanceledException) + { + _data.ConnectedState = ConnectedStatus.Disconnected; + ShowError("EFT-Client Timeout", ""); + } + catch (ConnectionException cx) + { + _data.ConnectedState = ConnectedStatus.Disconnected; + ShowError(cx.HResult.ToString(), cx.Message); + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + + _requestInProgress = false; + return result; + } + + #endregion + + #region Connect + + public async Task Connect(string ip, int port, bool useSSL) + { + if (_data.ConnectedState == ConnectedStatus.Connected) + { + return true; + } + + try + { + if (await _eft.ConnectAsync(ip, port, useSSL)) + { + _data.ConnectedState = ConnectedStatus.Connected; + //_checkStatusTimer.Enabled = true; + return true; + } + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + return false; + } + + private async void _checkStatusTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (_requestInProgress == false) + { + _requestInProgress = true; + if (await _eft.CheckConnectStateAsync() == false) + { + _data.Log("CheckConnectState failed. Connection has been dropped."); + Disconnect(); + } + _requestInProgress = false; + } + } + + public void Disconnect() + { + try + { + //_checkStatusTimer.Enabled = false; + _data.ConnectedState = ConnectedStatus.Disconnected; + _eft.Disconnect(); + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + } + + #endregion + + #region Logon + public async Task Logon(LogonType logonType, ReceiptCutModeType cutMode, ReceiptPrintModeType printMode, bool autoApproveDialogs) + { + await SendRequest(new EFTLogonRequest() + { + LogonType = logonType, + ReceiptCutMode = cutMode, + ReceiptPrintMode = printMode + }, autoApproveDialogs); + } + + public async Task CloudLogon(string clientId, string password, string pairingCode) + { + await SendRequest(new EFTCloudLogonRequest() + { + ClientID = clientId, + Password = password, + PairingCode = pairingCode + }); + } + + public async Task StartLogonTest(LogonType type, ReceiptCutModeType cutMode, ReceiptPrintModeType printMod) + { + _ct = new CancellationTokenSource(); + await SpawnLogon(type, cutMode, printMod, _ct.Token); + } + + public void StopLogonTest() + { + _ct.Cancel(); + _ct.Dispose(); + } + + private async Task SpawnLogon(LogonType type, ReceiptCutModeType cutMode, ReceiptPrintModeType printMod, CancellationToken token) + { + while (!token.IsCancellationRequested) + { + await Task.Delay(10); + await Logon(type, cutMode, printMod, true); + } + } + + #endregion + + #region ControlPanel + public async Task DisplayControlPanel(ControlPanelType option, ReceiptCutModeType cutMode, ReceiptPrintModeType printMode) + { + try + { + await _eft.WriteRequestAsync(new ControlPanelRequest() + { + ControlPanelType = option, + ReceiptCutMode = cutMode, + ReceiptPrintMode = printMode, + ReturnType = ControlPanelReturnType.Immediately + }); + + + bool waiting = true; + do + { + var timeoutToken = new CancellationTokenSource(new TimeSpan(0, 5, 0)).Token; // 5 minute timeout + var r = await _eft.ReadResponseAsync(timeoutToken); + + if (r is EFTDisplayResponse) + { + if (_data.Settings.DemoDialogOption != DemoDialogMode.Hide) + { + EFTDisplayResponse res = r as EFTDisplayResponse; + _data.DisplayDetails = res; + _data.DisplayDialog(true); + } + } + else if (r is EFTReceiptResponse || r is EFTReprintReceiptResponse) + { + string[] receiptText = (r is EFTReceiptResponse) ? (r as EFTReceiptResponse).ReceiptText : (r as EFTReprintReceiptResponse).ReceiptText; + + StringBuilder receipt = new StringBuilder(); + foreach (var s in receiptText) + { + if (s.Length > 0) + { + receipt.AppendLine(s); + } + } + + if (!string.IsNullOrEmpty(receipt.ToString())) + { + _data.Receipt = receipt.ToString(); + } + } + else + { + _data.LastTxnResult = DictionaryFromType(r); + _data.DisplayDialog(false); + } + } + while (waiting); + + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + + } + #endregion + + #region Last Transaction + public async Task GetLastTransaction() + { + await SendRequest(new EFTGetLastTransactionRequest()); + } + + public async Task LastReceipt(ReceiptCutModeType cutMode, ReceiptPrintModeType printMode, ReprintType type) + { + await SendRequest(new EFTReprintReceiptRequest() + { + ReceiptCutMode = cutMode, + ReceiptPrintMode = printMode, + ReprintType = type + }); + } + #endregion + + #region Transaction + + + + public async Task DoTransaction(EFTTransactionRequest request) + { + bool result = await SendRequest(request); + if (result) + { + _data.TransactionReference = string.Empty; + } + } + #endregion + + #region ClientList + + public async Task DoClientList(EFTClientListRequest clientListRequest) + { + bool result = await SendRequest(clientListRequest); + if (result) + { + + } + + } + #endregion + + #region Set Dialog + public async Task SetDialog(int x, int y, bool displayEvents, DialogPosition pos, string title, bool topMost, DialogType dialogType) + { + await SendRequest(new SetDialogRequest + { + DialogX = x, + DialogY = y, + DisableDisplayEvents = displayEvents, + DialogPosition = pos, + DialogTitle = title, + EnableTopmost = topMost, + DialogType = dialogType + }); + } + + public async Task SetDialog(SetDialogRequest request) + { + await SendRequest(request); + } + + #endregion + + #region Query Card + public async Task QueryCard(PadField pad, QueryCardType cardType) + { + await SendRequest(new QueryCardRequest + { + QueryCardType = cardType, + PurchaseAnalysisData = pad + }); + } + #endregion + + #region Status + public async Task GetStatus(StatusType statusType) + { + await SendRequest(new EFTStatusRequest + { + StatusType = statusType + }); + } + #endregion + + #region Config Merchant + public async Task ConfigureMerchant(int aiic, string merchantId, int nii, string terminalId, int timeout) + { + await SendRequest(new EFTConfigureMerchantRequest + { + AIIC = aiic, + Caid = merchantId, + NII = nii, + Catid = terminalId, + Timeout = timeout + }); + } + + public async Task ConfigureMerchant(EFTConfigureMerchantRequest request) + { + await SendRequest(request); + } + #endregion + + #region Settlement + public async Task DoSettlement(SettlementType settlement, ReceiptCutModeType cutMode, + PadField padInfo, ReceiptPrintModeType printMode, bool resetTotals) + { + await SendRequest(new EFTSettlementRequest + { + SettlementType = settlement, + ReceiptCutMode = cutMode, + PurchaseAnalysisData = padInfo, + ReceiptPrintMode = printMode, + ResetTotals = resetTotals + }); + } + #endregion + + #region Cheque Auth + public async Task DoVerifyCheque(EFTChequeAuthRequest request) + { + await SendRequest(request); + } + #endregion + + #region Slave Mode + public async Task DoSlaveMode(string cmd) + { + //await SendRequest(new EFTSlaveRequest + //{ + // Command = cmd + //}); + } + #endregion + + #region SendKey + public async Task SendKey(EFTPOSKey option, string data = "") + { + try + { + await _eft.WriteRequestAsync(new EFTSendKeyRequest + { + Data = data, + Key = option + }); + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + } + + public async Task StartSendKeysTest(EFTPOSKey key) + { + _ct = new CancellationTokenSource(); + + var progress = new Progress((s) => + { + _data.Log(s); + }); + + _data.Progress = progress; + + await Task.Run(() => SpawnSendKeys(key, _ct.Token, progress), _ct.Token); + } + + public void StopSendKeysTest() + { + _ct.Cancel(); + _ct.Dispose(); + } + + private async Task SpawnSendKeys(EFTPOSKey key, CancellationToken token, IProgress p) + { + try + { + while (!token.IsCancellationRequested) + { + await Task.Delay(100); + await _eft.WriteRequestAsync(new EFTSendKeyRequest + { + Key = key + }); + p.Report(_eftLogs); + } + } + catch (Exception ex) + { + ShowError(ex.HResult.ToString(), ex.Message); + } + } + #endregion + + #region PIN + public async Task AuthPin() + { + await SendRequest(new EFTTransactionRequest + { + TxnType = TransactionType.AuthPIN + }); + } + + public async Task ChangePin() + { + await SendRequest(new EFTTransactionRequest + { + TxnType = TransactionType.EnhancedPIN + }); + } + #endregion + + } } diff --git a/IPInterface.TestPOS/ViewModels/ProxyViewModel.cs b/IPInterface.TestPOS/ViewModels/ProxyViewModel.cs index af91c50..b3fe746 100644 --- a/IPInterface.TestPOS/ViewModels/ProxyViewModel.cs +++ b/IPInterface.TestPOS/ViewModels/ProxyViewModel.cs @@ -1,80 +1,82 @@ -using System; -using System.Collections.Generic; + +using System; using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace PCEFTPOS.EFTClient.IPInterface.TestPOS.ViewModels { - public class ProxyViewModel : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - public event EventHandler OnSendKey; - - protected void OnPropertyChanged(string info) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info)); - } - - private EFTDisplayResponse _displayDetails = new EFTDisplayResponse(); - public EFTDisplayResponse DisplayDetails - { - get - { - _displayDetails.DisplayText[0] = _displayDetails.DisplayText[0].Trim(); - _displayDetails.DisplayText[1] = _displayDetails.DisplayText[1].Trim(); - - return _displayDetails; - } - set - { - _displayDetails = value; - OnPropertyChanged(nameof(DisplayDetails)); - } - } - - private bool EnumContains(string val, out T item) - { - bool result = false; - item = default(T); - - foreach (var i in Enum.GetValues(typeof(T))) - { - string s = i.ToString(); - if (s.ToUpper().Contains(val.ToUpper())) - { - T x = (T)Enum.Parse(typeof(T), s); - item = x; - result = true; - break; - } - } - - return result; - } - - EFTPOSKey _key; - public EFTPOSKey Key { get { return _key; } set { _key = value; OnPropertyChanged(nameof(Key)); } } - - RelayCommand _sendKeyCommand; - public RelayCommand SendKeyCommand => _sendKeyCommand ?? (_sendKeyCommand = new RelayCommand((o) => - { - string name = o.ToString(); - EFTPOSKey key = EFTPOSKey.OkCancel; - - if (EnumContains(name, out key)) - { - OnSendKey?.Invoke(this, key); - } - })); - - public bool ProxyWindowClosing = false; - - - public void SendKeyFunc(EFTPOSKey key) - { - OnSendKey?.Invoke(this, key); - } - } + public class ProxyViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public event EventHandler OnSendKey; + + protected void OnPropertyChanged(string info) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info)); + } + + private EFTDisplayResponse _displayDetails = new EFTDisplayResponse(); + public EFTDisplayResponse DisplayDetails + { + get + { + _displayDetails.DisplayText[0] = _displayDetails.DisplayText[0].Trim(); + _displayDetails.DisplayText[1] = _displayDetails.DisplayText[1].Trim(); + + return _displayDetails; + } + set + { + _displayDetails = value; + OnPropertyChanged(nameof(DisplayDetails)); + } + } + + private bool EnumContains(string val, out T item) + { + bool result = false; + item = default(T); + + foreach (var i in Enum.GetValues(typeof(T))) + { + string s = i.ToString(); + if (s.ToUpper().Contains(val.ToUpper())) + { + T x = (T)Enum.Parse(typeof(T), s); + item = x; + result = true; + break; + } + } + + return result; + } + + EFTPOSKey _key; + public EFTPOSKey Key { get { return _key; } set { _key = value; OnPropertyChanged(nameof(Key)); } } + + RelayCommand _sendKeyCommand; + public RelayCommand SendKeyCommand => _sendKeyCommand ?? (_sendKeyCommand = new RelayCommand((o) => + { + string name = o.ToString(); + EFTPOSKey key = EFTPOSKey.OkCancel; + + if (EnumContains(name, out key)) + { + OnSendKey?.Invoke(this, key); + } + })); + + + public bool ProxyWindowClosing = false; + + + public void SendKeyFunc(EFTPOSKey key) + { + OnSendKey?.Invoke(this, key); + } + + + public IEFTClientIPAsync EFTClientIPAsync { get; set; } + + } } diff --git a/IPInterface.TestPOS/Views/MainWindow.xaml b/IPInterface.TestPOS/Views/MainWindow.xaml index 36831d5..8ab35f4 100644 --- a/IPInterface.TestPOS/Views/MainWindow.xaml +++ b/IPInterface.TestPOS/Views/MainWindow.xaml @@ -344,12 +344,12 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IPInterface.TestPOS/Views/TestDialogUI.xaml.cs b/IPInterface.TestPOS/Views/TestDialogUI.xaml.cs new file mode 100644 index 0000000..b5b29e5 --- /dev/null +++ b/IPInterface.TestPOS/Views/TestDialogUI.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace IPInterface.TestPOS.Views +{ + /// + /// Interaction logic for TestDialogUI.xaml + /// + public partial class TestDialogUI : Window + { + public TestDialogUI() + { + InitializeComponent(); + } + } +} diff --git a/IPInterface/EFTClientIP.cs b/IPInterface/EFTClientIP.cs index af836a3..4c9022d 100644 --- a/IPInterface/EFTClientIP.cs +++ b/IPInterface/EFTClientIP.cs @@ -1,4 +1,5 @@ -using System; +using PCEFTPOS.EFTClient.IPInterface.Slave; +using System; using System.Runtime.CompilerServices; using System.Threading; @@ -70,7 +71,7 @@ public class EFTClientIP : IEFTClientIP #region Data SynchronizationContext syncContext; - IMessageParser parser; + IMessageParser _parser; ITcpSocket socket; EFTRequest currentRequest; AutoResetEvent hideDialogEvent; @@ -134,6 +135,7 @@ public void Disconnect() /// FALSE if an error occurs public bool DoRequest(EFTRequest request, [CallerMemberName] string member = "") { + SetCurrentRequest(request); Log(LogLevel.Info, tr => tr.Set($"Request via {member}")); // Save the current synchronization context so we can use it to send events @@ -264,9 +266,9 @@ public bool DoStatus(EFTStatusRequest request) } /// Send a request to PC-EFTPOS for a cheque authorization. - /// An object. + /// An object. /// FALSE if an error occured. - public bool DoChequeAuth(ChequeAuthRequest request) + public bool DoChequeAuth(EFTChequeAuthRequest request) { return DoRequest(request); } @@ -289,7 +291,7 @@ public bool DoGetPassword(EFTGetPasswordRequest request) /// FALSE if an error occured. public bool DoSlaveCommand(string command) { - return DoRequest(new EFTSlaveRequest() { Command = command }); + return DoRequest(new EFTSlaveRequest() { RawCommand = command }); } /// Send a request to PC-EFTPOS for a merchant config. @@ -343,7 +345,7 @@ void Initialise() { recvBuf = ""; recvTickCount = 0; - parser = new MessageParser(); + _parser = new DefaultMessageParser(); socket = new TcpSocket(HostName, HostPort); socket.OnTerminated += new TcpSocketEventHandler(_OnTerminated); @@ -391,6 +393,7 @@ void ProcessEFTResponse(EFTResponse response) break; case EFTDisplayResponse r: + //DialogUIHandler.HandleDisplayResponse(r); FireClientResponseEvent(nameof(OnDisplay), OnDisplay, new EFTEventArgs(r)); break; @@ -446,6 +449,10 @@ void ProcessEFTResponse(EFTResponse response) FireClientResponseEvent(nameof(OnConfigMerchant), OnConfigMerchant, new EFTEventArgs(r)); break; + case EFTClientListResponse r: + FireClientResponseEvent(nameof(OnClientList), OnClientList, new EFTEventArgs(r)); + break; + default: Log(LogLevel.Error, tr => tr.Set($"Unknown response type", response)); break; @@ -468,13 +475,13 @@ bool SendIPClientRequest(EFTRequest eftRequest) { // Store current request. this.currentRequest = eftRequest; - + // Build request var requestString = ""; try { - requestString = parser.EFTRequestToString(eftRequest); + requestString = _parser.EFTRequestToString(eftRequest); } catch (Exception e) { @@ -487,11 +494,22 @@ bool SendIPClientRequest(EFTRequest eftRequest) // Send the request string to the IP client. return socket.Send(requestString); } - #endregion - #region Parse response + private void SetCurrentRequest(EFTRequest request) + { + // Always set _currentRequest to the last request we send + currentRequest = request; - bool ReceiveEFTResponse(byte[] data) + if (request.GetIsStartOfTransactionRequest()) + { + _currentStartTxnRequest = request; + } + } + #endregion + + #region Parse response + + bool ReceiveEFTResponse(byte[] data) { // Clear the receive buffer if 5 seconds has lapsed since the last message var tc = System.Environment.TickCount; @@ -545,10 +563,16 @@ bool ReceiveEFTResponse(byte[] data) var response = recvBuf.Substring(index, length - 5); FireOnTcpReceive(response); - // Process the response + // Process the response + EFTResponse eftResponse = null; try { - ProcessEFTResponse(parser.StringToEFTResponse(response)); + eftResponse = _parser.StringToEFTResponse(response); + ProcessEFTResponse(eftResponse); + if(eftResponse.GetType() == _currentStartTxnRequest?.GetPairedResponseType()) + { + dialogUIHandler.HandleCloseDisplay(); + } } catch (ArgumentException argumentException) { @@ -688,7 +712,7 @@ public bool CheckConnectState() /// The IP port of the PC-EFTPOS IP Client. /// Type: The listening port of the EFT Client IP interface. /// The setting of this property is required.See example. - public int HostPort { get; set; } = 6001; + public int HostPort { get; set; } = 2011; /// Indicates whether to use SSL encryption. /// Type: Defaults to FALSE. @@ -710,6 +734,38 @@ public bool CheckConnectState() /// Defines the level of logging that should be passed back in the OnLog event. Default . See public LogLevel LogLevel { get; set; } = LogLevel.Off; + IDialogUIHandler dialogUIHandler = null; + private EFTRequest _currentStartTxnRequest; + + public IDialogUIHandler DialogUIHandler + { + get + { + return dialogUIHandler; + } + set + { + dialogUIHandler = value; + if (dialogUIHandler.EFTClientIP == null) + { + dialogUIHandler.EFTClientIP = this; + } + } + } + + public IMessageParser Parser + { + get + { + return _parser; + } + set + { + _parser = value; + } + } + + #endregion #region Events @@ -755,6 +811,8 @@ public bool CheckConnectState() public event EventHandler> OnSlave; /// Fired when a get config merchant result is received. public event EventHandler> OnConfigMerchant; + /// Fired whan a get client list result is received. + public event EventHandler> OnClientList; #endregion } } \ No newline at end of file diff --git a/IPInterface/EFTClientIPAsync.cs b/IPInterface/EFTClientIPAsync.cs index 494f6e1..5f5a92c 100644 --- a/IPInterface/EFTClientIPAsync.cs +++ b/IPInterface/EFTClientIPAsync.cs @@ -8,498 +8,591 @@ namespace PCEFTPOS.EFTClient.IPInterface { - /// - /// Encapsulates the PC-EFTPOS TCP/IP interface using the async pattern - /// - public class EFTClientIPAsync: IEFTClientIPAsync - { - IMessageParser _parser; - StringBuilder _recvBuf; - - /// - /// Set to TRUE when our receive buffer is awaiting more data, FALSE otherwise. - /// - bool _recvBufWaiting; - - int _recvTickCount; - EFTRequest _currentRequest; - byte[] _buffer; - - ITcpSocketAsync _clientStream; - - public EFTClientIPAsync() + /// + /// Encapsulates the PC-EFTPOS TCP/IP interface using the async pattern + /// + public class EFTClientIPAsync : IEFTClientIPAsync + { + IMessageParser _parser; + StringBuilder _recvBuf; + + /// + /// Set to TRUE when our receive buffer is awaiting more data, FALSE otherwise. + /// + bool _recvBufWaiting; + + int _recvTickCount; + EFTRequest _currentRequest; + EFTRequest _currentStartTxnRequest; + byte[] _buffer; + + ITcpSocketAsync _clientStream; + + public EFTClientIPAsync() + { + Initialise(); + } + + void Initialise() + { + _buffer = new byte[8192]; + _recvBuf = new StringBuilder(); + _recvBufWaiting = false; + _recvTickCount = 0; + _parser = new DefaultMessageParser(); + } + + public async Task ConnectAsync(string hostName, int hostPort, bool useSSL, bool useKeepAlive = false) + { + try + { + // If we already have an existing _clientStream we need to clean it up before creating a new one + if (_clientStream != null) + { + _clientStream.LogLevel -= (int)LogLevel; + _clientStream.OnLog -= _clientStream_OnLog; + _clientStream.Close(); + _clientStream = null; + } + + _hostName = hostName; + _hostPort = hostPort; + _useSSL = useSSL; + _useKeepAlive = useKeepAlive; + + if (useSSL) + { + // Check if there are any custom root certificates to load + var tcp = new TcpSocketSslAsync(); + try + { + var extns = new string[] { ".der", ".pem" }; + var certs = from f in Directory.EnumerateFiles(Directory.GetCurrentDirectory()) + where extns.Contains((new FileInfo(f)).Extension) + select f; + tcp.CustomeRootCerts = certs?.ToList(); + } + catch (Exception ex) + { + Log(LogLevel.Error, tr => tr.Set($"Failed to get certs: {ex.Message}")); + } + + _clientStream = tcp; + } + else + { + _clientStream = new TcpSocketAsync(); + } + + _clientStream.LogLevel = (LogLevel)LogLevel; + _clientStream.OnLog += _clientStream_OnLog; + + var connected = await _clientStream.ConnectAsync(hostName, hostPort, useSSL); + Log(LogLevel.Info, tr => tr.Set($"Connected state: {connected}")); + + return connected; + } + catch (ObjectDisposedException ox) + { + Log(LogLevel.Error, tr => tr.Set($"Attempt to access disposed object - {hostName}:{hostPort} with SSL {useSSL}", ox)); + throw new ConnectionException(ox.Message, ox.InnerException); + } + catch (AuthenticationException ax) + { + Log(LogLevel.Error, tr => tr.Set($"Authentication failed - closing connection to {hostName}:{hostPort} with SSL {useSSL}", ax)); + throw new ConnectionException(ax.Message, ax.InnerException); + } + catch (Exception ex) + { + Log(LogLevel.Error, tr => tr.Set($"Error connecting to {hostName}:{hostPort} with SSL {useSSL}", ex)); + throw new ConnectionException(ex.Message, ex.InnerException); + } + } + + private void _clientStream_OnLog(object sender, LogEventArgs e) + { + Log((LogLevel)e.LogLevel, tr => tr.Set(e.Message, e.Exception)); + } + + + public bool Disconnect() + { + //IsConnected = false; + + Log(LogLevel.Debug, tr => tr.Set("Disconnecting...")); + + try + { + if (_clientStream != null) + { + _clientStream.Close(); + _clientStream.OnLog -= _clientStream_OnLog; + } + } + catch (Exception ex) + { + Log(LogLevel.Error, tr => tr.Set($"Error disconnecting", ex)); + return false; + } + + return true; + } + + /// + /// Polls the socket to determine the current connect state + /// + /// True if connected, false otherwise + public async Task CheckConnectStateAsync() + { + if (_clientStream == null) + return false; + + return await _clientStream.CheckConnectStateAsync(); + } + + + /// + /// Sends a request to the EFT-Client, and waits for the next EFT response of type T from the client. + /// All other response types are discarded.. This function is useful for request/response pairs + /// where other message types are not being handled (such as SetDialogRequest/SetDialogResponse). + /// + /// Since receipts and dialog messages cannot be handled with this function, it is not + /// recommended for use with transaction, logon, settlement ets + /// + /// + /// This function will continue to wait until either a message is received, or an exception is thrown (e.g. socket disconnect, cancellationToken fires). + /// It is important to ensure cancellationToken is configured correctly. + /// + /// The type of EFTResponse to wait for + /// The to send + /// The token to monitor for cancellation requests + /// Used for internal logging. Ignore + /// An EFTResponse if one could be read, otherwise null + /// The socket is closed. + /// The cancellation token was cancelled before the task completed + public async Task WriteRequestAndWaitAsync(EFTRequest request, System.Threading.CancellationToken cancellationToken, [CallerMemberName] string member = "") where T : EFTResponse + { + if (!await WriteRequestAsync(request, member)) + { + return null; + } + return await ReadResponseAsync(cancellationToken); + } + + + /// Sends a request to the EFT-Client + /// The to send + /// Used for internal logging. Ignore + /// FALSE if an error occurs + public async Task WriteRequestAsync(EFTRequest request, [CallerMemberName] string member = "") + { + SetCurrentRequest(request); + + string msgString = ""; + try + { + msgString = (request is EFTPosAsPinpadRequest) ? _parser.EFTRequestToXMLString(request) : _parser.EFTRequestToString(request); + } + catch (Exception e) + { + Log(LogLevel.Error, tr => tr.Set($"An error occured parsing the request", e)); + throw; + } + + Log(LogLevel.Debug, tr => tr.Set($"Tx {msgString}")); + + // Send the request string to the IP client. + try + { + await _clientStream.WriteRequestAsync(msgString); + } + catch (Exception e) + { + Log(LogLevel.Error, tr => tr.Set($"An error occured sending the request", e)); + Disconnect(); + throw new ConnectionException(e.Message, e.InnerException); + } + return true; + } + + /// + /// Stores the current request and current start of transaction + /// + /// + private void SetCurrentRequest(EFTRequest request) + { + // Always set _currentRequest to the last request we send + _currentRequest = request; + + if (request.GetIsStartOfTransactionRequest()) + { + _currentStartTxnRequest = request; + } + } + + /// + /// Retrieves the next EFT response from the client + /// + /// An EFTResponse if one could be read, otherwise null + /// The socket is closed. + public async Task ReadResponseAsync() + { + return await ReadResponseAsync(System.Threading.CancellationToken.None); + } + + + /// + /// Retrieves the next EFT response of type T from the client. All other response types are discarded. + /// + /// + /// This function will continue to wait until either a message is received, or an exception is thrown (e.g. socket disconnect, cancellationToken fires). + /// It is important to ensure cancellationToken is configured correctly. + /// + /// The type of EFTResponse to wait for + /// The token to monitor for cancellation requests + /// An EFTResponse if one could be read, otherwise null + /// The socket is closed. + /// The cancellation token was cancelled before the task completed + public async Task ReadResponseAsync(System.Threading.CancellationToken cancellationToken) where T : EFTResponse + { + while (!cancellationToken.IsCancellationRequested) + { + var r = await ReadResponseAsync(cancellationToken); + if (r is T) + { + return r as T; + } + } + return null; + } + + /// + /// Retrieves the next EFT response from the client + /// + /// The token to monitor for cancellation requests + /// An EFTResponse if one could be read, otherwise null + /// The socket is closed. + /// The task was cancelled by cancellationToken + public async Task ReadResponseAsync(System.Threading.CancellationToken cancellationToken) + { + // Clear the receive buffer if we have been waiting for more than + // 5 seconds for the remaining parts of the message to arrive + var tc = System.Environment.TickCount; + if (_recvBufWaiting && tc - _recvTickCount > 5000 && _recvBuf.Length > 0) + { + Log(LogLevel.Debug, tr => tr.Set($"Data is being cleared from the buffer due to a timeout. Content {_recvBuf.ToString()}")); + _recvBufWaiting = false; + _recvBuf.Clear(); + } + + // Only try to read more data if our receive buffer is empty + Log(LogLevel.Debug, tr => tr.Set($"ReadResponseAsync() _recvBuf.Length={_recvBuf.Length} _recvBufWaiting={_recvBufWaiting}")); + if (_recvBuf.Length == 0 || _recvBufWaiting == true) + { + var bytesRead = 0; + try + { + bytesRead = await _clientStream.ReadResponseAsync(_buffer, cancellationToken); + _recvTickCount = System.Environment.TickCount; + } + catch (TaskCanceledException e) + { + Log(LogLevel.Debug, tr => tr.Set($"Socket read cancelled", e)); + throw e; + } + catch (Exception e) + { + Log(LogLevel.Error, tr => tr.Set($"An error occured sending the request", e)); + Disconnect(); + throw new ConnectionException(e.Message, e.InnerException); + } + + // Check for socket closed + if (bytesRead <= 0) + { + Log(LogLevel.Error, tr => tr.Set("Recv 0 bytes. Socket closed")); + Disconnect(); + throw new ConnectionException("Socket closed."); + } + + var msgString = DirectEncoding.DIRECT.GetString(_buffer, 0, bytesRead); + Log(LogLevel.Info, tr => tr.Set($"Rx {msgString}")); + // Append receive data to our buffer + _recvBuf.Append(msgString); + } + + // Keep parsing until no more characters + try + { + bool isXMLFormatted = false; + int index = 0; + while (index < _recvBuf.Length) + { + // If the current char isn't #/& then keep cycling through the message until we find one + if (_recvBuf[index] != (byte)'#' && !(isXMLFormatted = _recvBuf[index] == (byte)'&')) + { + index++; + continue; + } + + // We have a valid start char, check for length. + index++; + + int lengthSize = isXMLFormatted ? 6 : 4; + + // Check that we have enough bytes to validate length, if not wait for more + if (_recvBuf.Length < index + lengthSize) + { + Log(LogLevel.Debug, tr => tr.Set($"Unable to validate message header. Waiting for more data. Length:{_recvBuf.Length} Required:{index + lengthSize + 1}")); + _recvBufWaiting = true; + break; + } + + // Try to get the length of the new message. If it's not a valid length + // we might have some corrupt data, keep checking for a valid message + int length = 0; + var lengthStr = _recvBuf.Substring(index, lengthSize); + if (!int.TryParse(lengthStr, out length) || length <= (lengthSize + 1)) + { + Log(LogLevel.Error, tr => tr.Set($"Invalid length. Content:{lengthStr}")); + continue; + } + + // We have a valid length + index += lengthSize; + + // If the defined message length is > our current buffer size, wait for more data + if (_recvBuf.Length < index + length - (lengthSize + 1)) + { + Log(LogLevel.Debug, tr => tr.Set($"Buffer is less than the indicate length. Waiting for more data. Length:{_recvBuf.Length < index} Required:{length - (lengthSize + 1)}")); + _recvBuf = _recvBuf.Remove(0, index - (lengthSize + 1)); + _recvBufWaiting = true; + break; + } + + // We have a valid response + _recvBufWaiting = false; + var response = _recvBuf.Substring(index, length - (lengthSize + 1)); + index += (length - (lengthSize + 1)); + _recvBuf.Remove(0, index); + + // Process the response + EFTResponse eftResponse = null; + try + { + //if (response.Equals("3M") || response.Equals("3C")) // FOR PREPRINT TIMEOUT + //if (response.Substring(0,2).Equals("3R")) // FOR PRINT TIMEOUT + // System.Threading.Thread.Sleep(61000); + eftResponse = (isXMLFormatted) ? _parser.XMLStringToEFTResponse(response) : _parser.StringToEFTResponse(response); + + // If we have an EFTResponse we need to return it. + if (eftResponse != null) + { + // Print requests need a response + if (eftResponse is EFTReceiptResponse) + { + await WriteRequestAsync(new EFTReceiptRequest()); + } + //DialogUIHandler needs to be notified of display messages + else if (eftResponse is EFTDisplayResponse && DialogUIHandlerAsync != null) + { + await DialogUIHandlerAsync.HandleDisplayResponseAsync(eftResponse as EFTDisplayResponse); + } + else if (eftResponse.GetType() == _currentStartTxnRequest?.GetPairedResponseType() && DialogUIHandlerAsync != null) + { + await DialogUIHandlerAsync.HandleCloseDisplayAsync(); + } + + + return eftResponse; + } + } + catch (Exception e) + { + Log(LogLevel.Error, tr => tr.Set("Error parsing response string", e)); + } + } + + // Clear our buffer if we are all done (this shouldn't happen) + if (index == _recvBuf.Length) + { + _recvBufWaiting = false; + _recvBuf.Clear(); + } + } + catch (Exception ex) + { + Log(LogLevel.Error, tr => tr.Set($"Exception (ReceiveEFTResponse): {ex.Message}", ex)); + throw ex; + } + + return null; + } + + void Log(LogLevel level, Action traceAction, [CallerMemberName] string member = "", [CallerLineNumber] int line = 0) + { + // Check if this log level is enabled and client is subscribed to the OnLog event + if (OnLog == null || (int)LogLevel >= (int)level) + { + return; + } + + TraceRecord tr = new TraceRecord() { Level = level }; + traceAction(tr); + + + // StringBuild is faster than $"{member}() line {line}: {tr.Message}" + var sb = new StringBuilder(member.Length + tr.Message.Length + 100); + sb.Append(member); + sb.Append("() line "); + sb.Append(line); + sb.Append(": "); + sb.Append(tr.Message); + + OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = sb.ToString(), Exception = tr.Exception }); + } + + + #region Events + + /// Fired when a logging event occurs. + public event EventHandler OnLog; + + #endregion + + #region Properties + + /// The IP host name of the PC-EFTPOS IP Client. + /// Type: The IP address or host name of the EFT Client IP interface. + /// The setting of this property is required.See example. + string _hostName = "127.0.0.1"; + public string HostName => _hostName; + + /// The IP port of the PC-EFTPOS IP Client. + /// Type: The listening port of the EFT Client IP interface. + /// The setting of this property is required.See example. + int _hostPort = 6001; + public int HostPort => _hostPort; + + /// Indicates whether to use SSL encryption. + /// Type: Defaults to FALSE. + bool _useSSL = true; + public bool UseSSL => _useSSL; + + /// Indicates whether to allow TCP keep-alives. + /// Type: Defaults to FALSE. + bool _useKeepAlive = true; + public bool UseKeepAlive => _useKeepAlive; + + /// Indicates whether there is a request currently in progress. + public bool IsRequestInProgress { get; } + + /// + /// Returns the connected state as of the last + /// + /// Gets a value that indicates whether a System.Net.Sockets.Socket is connected + /// to a remote host as of the last Overload:System.Net.Sockets.Socket.Send or Overload:System.Net.Sockets.Socket.Receive + /// operation. + /// + public bool IsConnected + { + get + { + return _clientStream?.IsConnected ?? false; + } + } + + /// When TRUE, the SynchronizationContext will be captured from requests and used to call events + public bool UseSynchronizationContextForEvents { get; set; } = true; + + /// Defines the level of logging that should be passed back in the OnLog event. Default . See + public LogLevel LogLevel { get; set; } = LogLevel.All; + + + IDialogUIHandlerAsync dialogUIHandlerAsync = null; + /// + /// An interface for a UI that implements the PC-EFTPOS dialog messages. + /// Uses IEFTClientIPAsync to send key press messages to the EFT-Client + /// + public IDialogUIHandlerAsync DialogUIHandlerAsync + { + get + { + return dialogUIHandlerAsync; + } + set + { + dialogUIHandlerAsync = value; + if (dialogUIHandlerAsync.EFTClientIPAsync == null) + { + dialogUIHandlerAsync.EFTClientIPAsync = this; + } + } + } + + + public IMessageParser Parser { - Initialise(); - } - - void Initialise() - { - _buffer = new byte[8192]; - _recvBuf = new StringBuilder(); - _recvBufWaiting = false; - _recvTickCount = 0; - _parser = new MessageParser(); - } - - public async Task ConnectAsync(string hostName, int hostPort, bool useSSL, bool useKeepAlive = false) - { - try - { - // If we already have an existing _clientStream we need to clean it up before creating a new one - if(_clientStream != null) - { - _clientStream.LogLevel -= (int)LogLevel; - _clientStream.OnLog -= _clientStream_OnLog; - _clientStream.Close(); - _clientStream = null; - } - - _hostName = hostName; - _hostPort = hostPort; - _useSSL = useSSL; - _useKeepAlive = useKeepAlive; - - if (useSSL) - { - // Check if there are any custom root certificates to load - var tcp = new TcpSocketSslAsync(); - try - { - var extns = new string[] { ".der", ".pem" }; - var certs = from f in Directory.EnumerateFiles(Directory.GetCurrentDirectory()) - where extns.Contains((new FileInfo(f)).Extension) - select f; - tcp.CustomeRootCerts = certs?.ToList(); - } - catch (Exception ex) - { - Log(LogLevel.Error, tr => tr.Set($"Failed to get certs: {ex.Message}")); - } - - _clientStream = tcp; - } - else - { - _clientStream = new TcpSocketAsync(); - } - - _clientStream.LogLevel = (LogLevel)LogLevel; - _clientStream.OnLog += _clientStream_OnLog; - - var connected = await _clientStream.ConnectAsync(hostName, hostPort, useSSL); - Log(LogLevel.Info, tr => tr.Set($"Connected state: {connected}")); - - return connected; - } - catch (ObjectDisposedException ox) - { - Log(LogLevel.Error, tr => tr.Set($"Attempt to access disposed object - {hostName}:{hostPort} with SSL {useSSL}", ox)); - throw new ConnectionException(ox.Message, ox.InnerException); - } - catch (AuthenticationException ax) - { - Log(LogLevel.Error, tr => tr.Set($"Authentication failed - closing connection to {hostName}:{hostPort} with SSL {useSSL}", ax)); - throw new ConnectionException(ax.Message, ax.InnerException); - } - catch (Exception ex) - { - Log(LogLevel.Error, tr => tr.Set($"Error connecting to {hostName}:{hostPort} with SSL {useSSL}", ex)); - throw new ConnectionException(ex.Message, ex.InnerException); - } - } - - private void _clientStream_OnLog(object sender, LogEventArgs e) - { - Log((LogLevel)e.LogLevel, tr => tr.Set(e.Message, e.Exception)); - } - - - public bool Disconnect() - { - //IsConnected = false; - - Log(LogLevel.Debug, tr => tr.Set("Disconnecting...")); - - try - { - if (_clientStream != null) - { - _clientStream.Close(); - _clientStream.OnLog -= _clientStream_OnLog; - } - } - catch (Exception ex) - { - Log(LogLevel.Error, tr => tr.Set($"Error disconnecting", ex)); - return false; - } - - return true; - } - - /// - /// Polls the socket to determine the current connect state - /// - /// True if connected, false otherwise - public async Task CheckConnectStateAsync() - { - if (_clientStream == null) - return false; - - return await _clientStream.CheckConnectStateAsync(); - } - - - /// Sends a request to the EFT-Client - /// The to send - /// Used for internal logging. Ignore - /// FALSE if an error occurs - public async Task WriteRequestAsync(EFTRequest request, [CallerMemberName] string member = "") - { - // Store current request. - _currentRequest = request; - - string msgString = ""; - try - { - msgString = (request is EFTPosAsPinpadRequest) ? _parser.EFTRequestToXMLString(request) : _parser.EFTRequestToString(request); - } - catch (Exception e) - { - Log(LogLevel.Error, tr => tr.Set($"An error occured parsing the request", e)); - throw; - } - - Log(LogLevel.Debug, tr => tr.Set($"Tx {msgString}")); - - // Send the request string to the IP client. - try - { - await _clientStream.WriteRequestAsync(msgString); - } - catch (Exception e) - { - Log(LogLevel.Error, tr => tr.Set($"An error occured sending the request", e)); - Disconnect(); - throw new ConnectionException(e.Message, e.InnerException); - } - return true; - } - - /// - /// Retrieves the next EFT response from the client - /// - /// An EFTResponse if one could be read, otherwise null - /// The socket is closed. - public async Task ReadResponseAsync() - { - return await ReadResponseAsync(System.Threading.CancellationToken.None); - } - - - /// - /// Retrieves the next EFT response of type T from the client. All other response types are discarded. - /// - /// - /// This function will continue to wait until either a message is received, or an exception is thrown (e.g. socket disconnect, cancellationToken fires). - /// It is important to ensure cancellationToken is configured correctly. - /// - /// The type of EFTResponse to wait for - /// The token to monitor for cancellation requests - /// An EFTResponse if one could be read, otherwise null - /// The socket is closed. - /// The cancellation token was cancelled before the task completed - public async Task ReadResponseAsync(System.Threading.CancellationToken cancellationToken) where T : EFTResponse - { - while (!cancellationToken.IsCancellationRequested) - { - var r = await ReadResponseAsync(cancellationToken); - if (r is T) - { - return r as T; - } - } - return null; - } - - /// - /// Retrieves the next EFT response from the client - /// - /// The token to monitor for cancellation requests - /// An EFTResponse if one could be read, otherwise null - /// The socket is closed. - /// The task was cancelled by cancellationToken - public async Task ReadResponseAsync(System.Threading.CancellationToken cancellationToken) - { - // Clear the receive buffer if we have been waiting for more than - // 5 seconds for the remaining parts of the message to arrive - var tc = System.Environment.TickCount; - if (_recvBufWaiting && tc - _recvTickCount > 5000 && _recvBuf.Length > 0) - { - Log(LogLevel.Debug, tr => tr.Set($"Data is being cleared from the buffer due to a timeout. Content {_recvBuf.ToString()}")); - _recvBufWaiting = false; - _recvBuf.Clear(); - } - - // Only try to read more data if our receive buffer is empty - Log(LogLevel.Debug, tr => tr.Set($"ReadResponseAsync() _recvBuf.Length={_recvBuf.Length} _recvBufWaiting={_recvBufWaiting}")); - if (_recvBuf.Length == 0 || _recvBufWaiting == true) - { - var bytesRead = 0; - try - { - bytesRead = await _clientStream.ReadResponseAsync(_buffer, cancellationToken); - _recvTickCount = System.Environment.TickCount; - } - catch (TaskCanceledException e) - { - Log(LogLevel.Debug, tr => tr.Set($"Socket read cancelled", e)); - throw e; - } - catch (Exception e) - { - Log(LogLevel.Error, tr => tr.Set($"An error occured sending the request", e)); - Disconnect(); - throw new ConnectionException(e.Message, e.InnerException); - } - - // Check for socket closed - if (bytesRead <= 0) - { - Log(LogLevel.Error, tr => tr.Set("Recv 0 bytes. Socket closed")); - Disconnect(); - throw new ConnectionException("Socket closed."); - } - - var msgString = DirectEncoding.DIRECT.GetString(_buffer, 0, bytesRead); - Log(LogLevel.Info, tr => tr.Set($"Rx {msgString}")); - // Append receive data to our buffer - _recvBuf.Append(msgString); - } - - // Keep parsing until no more characters - try + get { - bool isXMLFormatted = false; - int index = 0; - while (index < _recvBuf.Length) - { - // If the current char isn't #/& then keep cycling through the message until we find one - if (_recvBuf[index] != (byte)'#' && !(isXMLFormatted = _recvBuf[index] == (byte)'&')) - { - index++; - continue; - } - - // We have a valid start char, check for length. - index++; - - int lengthSize = isXMLFormatted ? 6 : 4; - - // Check that we have enough bytes to validate length, if not wait for more - if (_recvBuf.Length < index + lengthSize) - { - Log(LogLevel.Debug, tr => tr.Set($"Unable to validate message header. Waiting for more data. Length:{_recvBuf.Length} Required:{index + lengthSize + 1}")); - _recvBufWaiting = true; - break; - } - - // Try to get the length of the new message. If it's not a valid length - // we might have some corrupt data, keep checking for a valid message - int length = 0; - var lengthStr = _recvBuf.Substring(index, lengthSize); - if (!int.TryParse(lengthStr, out length) || length <= (lengthSize + 1)) - { - Log(LogLevel.Error, tr => tr.Set($"Invalid length. Content:{lengthStr}")); - continue; - } - - // We have a valid length - index += lengthSize; - - // If the defined message length is > our current buffer size, wait for more data - if (_recvBuf.Length < index + length - (lengthSize + 1)) - { - Log(LogLevel.Debug, tr => tr.Set($"Buffer is less than the indicate length. Waiting for more data. Length:{_recvBuf.Length < index} Required:{length - (lengthSize + 1)}")); - _recvBuf = _recvBuf.Remove(0, index - (lengthSize + 1)); - _recvBufWaiting = true; - break; - } - - // We have a valid response - _recvBufWaiting = false; - var response = _recvBuf.Substring(index, length - (lengthSize + 1)); - index += (length - (lengthSize + 1)); - _recvBuf.Remove(0, index); - - // Process the response - EFTResponse eftResponse = null; - try - { - eftResponse = (isXMLFormatted) ? _parser.XMLStringToEFTResponse(response) : _parser.StringToEFTResponse(response); - - // If we have an EFTResponse we need to return it. - if (eftResponse != null) - { - // Print requests need a response - if (eftResponse is EFTReceiptResponse) - { - await WriteRequestAsync(new EFTReceiptRequest()); - } - return eftResponse; - } - } - catch (Exception e) - { - Log(LogLevel.Error, tr => tr.Set("Error parsing response string", e)); - } - } - - // Clear our buffer if we are all done (this shouldn't happen) - if (index == _recvBuf.Length) - { - _recvBufWaiting = false; - _recvBuf.Clear(); - } + return _parser; } - catch (Exception ex) + set { - Log(LogLevel.Error, tr => tr.Set($"Exception (ReceiveEFTResponse): {ex.Message}", ex)); - throw ex; + _parser = value; } - - return null; } - void Log(LogLevel level, Action traceAction, [CallerMemberName] string member = "", [CallerLineNumber] int line = 0) - { - // Check if this log level is enabled and client is subscribed to the OnLog event - if (OnLog == null || (int)LogLevel >= (int)level) - { - return; - } - - TraceRecord tr = new TraceRecord() { Level = level }; - traceAction(tr); - - - // StringBuild is faster than $"{member}() line {line}: {tr.Message}" - var sb = new StringBuilder(member.Length + tr.Message.Length + 100); - sb.Append(member); - sb.Append("() line "); - sb.Append(line); - sb.Append(": "); - sb.Append(tr.Message); - - OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = sb.ToString(), Exception = tr.Exception }); - } - - - #region Events - - /// Fired when a logging event occurs. - public event EventHandler OnLog; - - #endregion - - #region Properties - - /// The IP host name of the PC-EFTPOS IP Client. - /// Type: The IP address or host name of the EFT Client IP interface. - /// The setting of this property is required.See example. - string _hostName = "127.0.0.1"; - public string HostName => _hostName; - - /// The IP port of the PC-EFTPOS IP Client. - /// Type: The listening port of the EFT Client IP interface. - /// The setting of this property is required.See example. - int _hostPort = 6001; - public int HostPort => _hostPort; - - /// Indicates whether to use SSL encryption. - /// Type: Defaults to FALSE. - bool _useSSL = true; - public bool UseSSL => _useSSL; - - /// Indicates whether to allow TCP keep-alives. - /// Type: Defaults to FALSE. - bool _useKeepAlive = true; - public bool UseKeepAlive => _useKeepAlive; - - /// Indicates whether there is a request currently in progress. - public bool IsRequestInProgress { get; } - - /// - /// Returns the connected state as of the last - /// - /// Gets a value that indicates whether a System.Net.Sockets.Socket is connected - /// to a remote host as of the last Overload:System.Net.Sockets.Socket.Send or Overload:System.Net.Sockets.Socket.Receive - /// operation. - /// - public bool IsConnected - { - get - { - return _clientStream?.IsConnected ?? false; - } - } - - /// When TRUE, the SynchronizationContext will be captured from requests and used to call events - public bool UseSynchronizationContextForEvents { get; set; } = true; - - /// Defines the level of logging that should be passed back in the OnLog event. Default . See - public LogLevel LogLevel { get; set; } = LogLevel.All; - #endregion #region IDisposable Support private bool disposedValue = false; // To detect redundant calls - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - _clientStream?.Close(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~EFTClientIPAsync2() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - - #region Obsolete - [Obsolete("DisconnectAsync() is deprecated, please use Disconnect() instead.")] - public bool DisconnectAsync() - { - return Disconnect(); - } - - [Obsolete("Close() is deprecated, please use Disconnect() instead.")] - public void Close() - { - Disconnect(); - } - #endregion - } + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _clientStream?.Close(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~EFTClientIPAsync2() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + + #region Obsolete + [Obsolete("DisconnectAsync() is deprecated, please use Disconnect() instead.")] + public bool DisconnectAsync() + { + return Disconnect(); + } + + [Obsolete("Close() is deprecated, please use Disconnect() instead.")] + public void Close() + { + Disconnect(); + } + #endregion + } } diff --git a/IPInterface/IDialogUIHandler.cs b/IPInterface/IDialogUIHandler.cs new file mode 100644 index 0000000..598b745 --- /dev/null +++ b/IPInterface/IDialogUIHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PCEFTPOS.EFTClient.IPInterface +{ + /// + /// An interface for a UI that implements the PC-EFTPOS dialog messages + /// Uses IEFTClientIP to send key press messages to the EFT-Client + /// + public interface IDialogUIHandler + { + /// + /// An instance to the IEFTClientIP. Used to send key press requests + /// + IEFTClientIP EFTClientIP { get; set; } + + /// + /// Called by the EFTClientIP when a EFTDisplayResponse is received + /// + void HandleDisplayResponse(EFTDisplayResponse eftDisplayResponse); + + /// + /// Called by the EFTClientIP when the dialog needs to be closed + /// + void HandleCloseDisplay(); + } +} diff --git a/IPInterface/IDialogUIHandlerAsync.cs b/IPInterface/IDialogUIHandlerAsync.cs new file mode 100644 index 0000000..55a70e8 --- /dev/null +++ b/IPInterface/IDialogUIHandlerAsync.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace PCEFTPOS.EFTClient.IPInterface +{ + /// + /// An interface for a UI that implements the PC-EFTPOS dialog messages. + /// Uses IEFTClientIPAsync to send key press messages to the EFT-Client + /// + public interface IDialogUIHandlerAsync + { + /// + /// An instance to the IEFTClientIP. Used to send key press requests + /// + IEFTClientIPAsync EFTClientIPAsync { get; set; } + + /// + /// Called by the EFTClientIPAsync when a EFTDisplayResponse is received + /// + Task HandleDisplayResponseAsync(EFTDisplayResponse eftDisplayResponse); + + /// + /// Called by the EFTClientIPAsync when the dialog needs to be closed + /// + Task HandleCloseDisplayAsync(); + } +} diff --git a/IPInterface/IEFTClientIP.cs b/IPInterface/IEFTClientIP.cs index 416d72f..431a3d8 100644 --- a/IPInterface/IEFTClientIP.cs +++ b/IPInterface/IEFTClientIP.cs @@ -1,4 +1,5 @@ -using System; +using PCEFTPOS.EFTClient.IPInterface.Slave; +using System; using System.Runtime.CompilerServices; namespace PCEFTPOS.EFTClient.IPInterface @@ -41,6 +42,8 @@ public interface IEFTClientIP: IDisposable /// Defines the level of logging that should be passed back in the OnLog event. Default . See PCEFTPOS.EFTClient.IPInterface.LogLevel LogLevel { get; set; } + /// Implementation of a dialog manager + IDialogUIHandler DialogUIHandler { get; set; } #endregion #region Methods @@ -137,9 +140,9 @@ public interface IEFTClientIP: IDisposable bool DoStatus(EFTStatusRequest request); /// Send a request to PC-EFTPOS for a cheque authorization. - /// An object. + /// An object. /// FALSE if an error occured. - bool DoChequeAuth(ChequeAuthRequest request); + bool DoChequeAuth(EFTChequeAuthRequest request); /// Send a request to PC-EFTPOS for a query card. /// FALSE if an error occured. diff --git a/IPInterface/IEFTClientIPAsync.cs b/IPInterface/IEFTClientIPAsync.cs index 5b16a44..f2b4687 100644 --- a/IPInterface/IEFTClientIPAsync.cs +++ b/IPInterface/IEFTClientIPAsync.cs @@ -12,6 +12,7 @@ public interface IEFTClientIPAsync : IDisposable /// Address of host connection. /// Port Id of server connection. /// Enables SSL connection. + /// True to enable TCP keep alives /// Task ConnectAsync(string hostName, int hostPort, bool useSSL = true, bool useKeepAlive = true); @@ -33,6 +34,27 @@ public interface IEFTClientIPAsync : IDisposable /// The socket is closed. Task WriteRequestAsync(EFTRequest request, [CallerMemberName] string member = ""); + /// + /// Sends a request to the EFT-Client, and waits for the next EFT response of type T from the client. + /// All other response types are discarded.. This function is useful for request/response pairs + /// where other message types are not being handled (such as SetDialogRequest/SetDialogResponse). + /// + /// Since receipts and dialog messages cannot be handled with this function, it is not + /// recommended for use with transaction, logon, settlement ets + /// + /// + /// This function will continue to wait until either a message is received, or an exception is thrown (e.g. socket disconnect, cancellationToken fires). + /// It is important to ensure cancellationToken is configured correctly. + /// + /// The type of EFTResponse to wait for + /// The to send + /// The token to monitor for cancellation requests + /// Used for internal logging. Ignore + /// An EFTResponse if one could be read, otherwise null + /// The socket is closed. + /// The cancellation token was cancelled before the task completed + Task WriteRequestAndWaitAsync(EFTRequest request, System.Threading.CancellationToken cancellationToken, [CallerMemberName] string member = "") where T : EFTResponse; + /// /// Retrieves the next EFT response from the client /// @@ -109,5 +131,8 @@ public interface IEFTClientIPAsync : IDisposable /// Defines the level of logging that should be passed back in the OnLog event. Default . See LogLevel LogLevel { get; set; } + + /// Implementation of a dialog manager + IDialogUIHandlerAsync DialogUIHandlerAsync { get; set; } } } diff --git a/IPInterface/IPInterface.csproj b/IPInterface/IPInterface.csproj index a6c7a90..5271482 100644 --- a/IPInterface/IPInterface.csproj +++ b/IPInterface/IPInterface.csproj @@ -6,7 +6,7 @@ PCEFTPOS.EFTClient.IPInterface PCEFTPOS.EFTClient.IPInterface 1.3.5.0 - 1.3.5.0 + 1.4.0.0 TCP/IP library used for connecting to PC-EFTPOS Client PC-EFTPOS Pty Ltd PC-EFTPOS Pty Ltd @@ -14,8 +14,8 @@ false PCEFTPOS PC-EFTPOS https://github.com/pceftpos/EFTClient.IPInterface.CSharp - false - pceftpos.pfx + true + PCEFTPOS.EFTClient.IPInterface.snk 1.3.5.0 (2018-02-11) @@ -23,14 +23,13 @@ * Added support for basket data API * Updated some property names to bring EFTClientIP more inline with the existing ActiveX interface. Old property names have been marked obsolete, but are still supported. - 1.3.5.0 (2018-02-16) -* Added support for .NET Standard 2.0 -* Added support for basket data API -* Updated some property names to bring EFTClientIP more inline with the existing ActiveX interface. Old property names have been marked obsolete, but are still supported. - - 1.3.5.0 - 1.3.5.0 + 1.4.0.0 (2018-04-30) +* Added IDialogUIHandler for easier handling of POS custom dialogs. +* Updated MessageParser to allow for custom parsing. + 1.4.0.0 + 1.4.0.0 true + false diff --git a/IPInterface/Model/ChequeAuthRequest.cs b/IPInterface/Model/ChequeAuthRequest.cs index 5610135..252c445 100644 --- a/IPInterface/Model/ChequeAuthRequest.cs +++ b/IPInterface/Model/ChequeAuthRequest.cs @@ -14,10 +14,10 @@ public enum ChequeType } /// A PC-EFTPOS cheque authorization request object. - public class ChequeAuthRequest : EFTRequest + public class EFTChequeAuthRequest : EFTRequest { /// Constructs a default ChequeAuthRequest object. - public ChequeAuthRequest() : base() + public EFTChequeAuthRequest() : base(true, typeof(EFTChequeAuthResponse)) { } @@ -63,7 +63,7 @@ public class EFTChequeAuthResponse : EFTResponse /// Constructs a default cheque authorization response object. public EFTChequeAuthResponse() - : base() + : base(typeof(EFTChequeAuthRequest)) { amount = 0; authNumber = 0; diff --git a/IPInterface/Model/ControlPanel.cs b/IPInterface/Model/ControlPanel.cs index bec60a7..e9ccc71 100644 --- a/IPInterface/Model/ControlPanel.cs +++ b/IPInterface/Model/ControlPanel.cs @@ -28,11 +28,18 @@ public enum ControlPanelReturnType ImmediatelyAndWhenClosed = '2', } - /// A PC-EFTPOS show control panel request object. - public class ControlPanelRequest : EFTRequest - { + /// A PC-EFTPOS show control panel request object. + public class EFTControlPanelRequest : ControlPanelRequest + { + + } + + /// ControlPanelRequest is obsolete. Please use EFTControlPanelRequest + [Obsolete("ControlPanelRequest is obsolete. Please use EFTControlPanelRequest")] + public class ControlPanelRequest : EFTRequest + { /// Constructs a default show control panel request object. - public ControlPanelRequest() : base() + public ControlPanelRequest() : base(true, typeof(EFTControlPanelResponse)) { } @@ -58,7 +65,7 @@ public ControlPanelRequest() : base() public class EFTControlPanelResponse : EFTResponse { /// Constructs a default show control panel response object. - public EFTControlPanelResponse() : base() + public EFTControlPanelResponse() : base(typeof(EFTControlPanelRequest)) { } diff --git a/IPInterface/Model/Display.cs b/IPInterface/Model/Display.cs index f09462b..fd647e9 100644 --- a/IPInterface/Model/Display.cs +++ b/IPInterface/Model/Display.cs @@ -7,7 +7,7 @@ public enum GraphicCode { Processing = '0', Verify = '1', Question = '2', Card = public class EFTDisplayResponse : EFTResponse { /// Constructs a default display response object. - public EFTDisplayResponse() : base() + public EFTDisplayResponse() : base(null) { } diff --git a/IPInterface/Model/EFTBasketData.cs b/IPInterface/Model/EFTBasketData.cs index b0ecd2f..b62ef26 100644 --- a/IPInterface/Model/EFTBasketData.cs +++ b/IPInterface/Model/EFTBasketData.cs @@ -188,8 +188,15 @@ public string Tag } + /// + /// Represents a default basket request + /// public class EFTBasketDataRequest : EFTRequest { + public EFTBasketDataRequest() : base(false, typeof(EFTBasketDataResponse)) + { + } + /// /// The command to perform (create, update, delete) /// @@ -207,6 +214,11 @@ public class EFTBasketDataRequest : EFTRequest public class EFTBasketDataResponse : EFTResponse { + public EFTBasketDataResponse() : base(typeof(EFTBasketDataRequest)) + { + } + + /// /// True if the request was successful /// diff --git a/IPInterface/Model/EFTClientList.cs b/IPInterface/Model/EFTClientList.cs index 2996bb0..03bf987 100644 --- a/IPInterface/Model/EFTClientList.cs +++ b/IPInterface/Model/EFTClientList.cs @@ -10,8 +10,8 @@ namespace PCEFTPOS.EFTClient.IPInterface public class EFTClientListRequest : EFTRequest { /// Constructs a default client list object. - public EFTClientListRequest() : base() - { + public EFTClientListRequest() : base(false, typeof(EFTClientListResponse)) + { } } @@ -29,10 +29,12 @@ public class EFTClient } /// Constructs a default EFT-Client list response object. - public EFTClientListResponse() : base() + public EFTClientListResponse() : base(typeof(EFTClientListRequest)) { } + public bool Success { get; set; } = true; + public List EFTClients { get; set; } = new List(); } diff --git a/IPInterface/Model/EFTCloudLogon.cs b/IPInterface/Model/EFTCloudLogon.cs index 04d65ba..e863d25 100644 --- a/IPInterface/Model/EFTCloudLogon.cs +++ b/IPInterface/Model/EFTCloudLogon.cs @@ -6,7 +6,7 @@ namespace PCEFTPOS.EFTClient.IPInterface public class EFTCloudLogonRequest : EFTRequest { /// Constructs a default cloud logon request object. - public EFTCloudLogonRequest() : base() + public EFTCloudLogonRequest() : base(true, typeof(EFTCloudLogonResponse)) { } @@ -27,7 +27,7 @@ public EFTCloudLogonRequest() : base() public class EFTCloudLogonResponse : EFTResponse { /// Constructs a default terminal logon response object. - public EFTCloudLogonResponse() : base() + public EFTCloudLogonResponse() : base(typeof(EFTCloudLogonRequest)) { } diff --git a/IPInterface/Model/EFTConfigMerchant.cs b/IPInterface/Model/EFTConfigMerchant.cs index 0ec591b..640ef7a 100644 --- a/IPInterface/Model/EFTConfigMerchant.cs +++ b/IPInterface/Model/EFTConfigMerchant.cs @@ -6,7 +6,7 @@ namespace PCEFTPOS.EFTClient.IPInterface public class EFTConfigureMerchantRequest : EFTRequest { /// Constructs a default terminal configure request object. - public EFTConfigureMerchantRequest() : base() + public EFTConfigureMerchantRequest() : base(true, typeof(EFTConfigureMerchantResponse)) { } @@ -56,7 +56,7 @@ public EFTConfigureMerchantRequest() : base() public class EFTConfigureMerchantResponse : EFTResponse { /// Constructs a default terminal configure response object. - public EFTConfigureMerchantResponse() : base() + public EFTConfigureMerchantResponse() : base(typeof(EFTConfigureMerchantRequest)) { } diff --git a/IPInterface/Model/EFTDuplicate.cs b/IPInterface/Model/EFTDuplicate.cs index df19ddc..5fe8a05 100644 --- a/IPInterface/Model/EFTDuplicate.cs +++ b/IPInterface/Model/EFTDuplicate.cs @@ -16,7 +16,7 @@ public enum ReprintType public class EFTReprintReceiptRequest : EFTRequest { /// Constructs a default EFTDuplicateReceiptRequest object. - public EFTReprintReceiptRequest() : base() + public EFTReprintReceiptRequest() : base(true, typeof(EFTReprintReceiptResponse)) { } @@ -45,7 +45,7 @@ public EFTReprintReceiptRequest() : base() public class EFTReprintReceiptResponse : EFTResponse { /// Constructs a default duplicate receipt response object. - public EFTReprintReceiptResponse() : base() + public EFTReprintReceiptResponse() : base(typeof(EFTReprintReceiptResponse)) { } diff --git a/IPInterface/Model/EFTGetLast.cs b/IPInterface/Model/EFTGetLast.cs index 4f33c4c..c544723 100644 --- a/IPInterface/Model/EFTGetLast.cs +++ b/IPInterface/Model/EFTGetLast.cs @@ -6,7 +6,7 @@ namespace PCEFTPOS.EFTClient.IPInterface public class EFTGetLastTransactionRequest : EFTRequest { /// Constructs a default EFTGetLastTransactionRequest object. - public EFTGetLastTransactionRequest() : base() + public EFTGetLastTransactionRequest() : base(true, typeof(EFTGetLastTransactionResponse)) { } @@ -24,7 +24,7 @@ public class EFTGetLastTransactionResponse : EFTResponse { /// Constructs a default terminal transaction response object. public EFTGetLastTransactionResponse() - : base() + : base(typeof(EFTGetLastTransactionRequest)) { } /// Two digit merchant code diff --git a/IPInterface/Model/EFTLogon.cs b/IPInterface/Model/EFTLogon.cs index e2bb542..2cd32dd 100644 --- a/IPInterface/Model/EFTLogon.cs +++ b/IPInterface/Model/EFTLogon.cs @@ -29,13 +29,13 @@ public enum LogonType public class EFTLogonRequest : EFTRequest { /// Constructs a default terminal logon request object. - public EFTLogonRequest() : base() + public EFTLogonRequest() : this(LogonType.Standard) { } /// Constructs a terminal logon request object. /// The logon type to perform. - public EFTLogonRequest(LogonType LogonType) : base() + public EFTLogonRequest(LogonType LogonType) : base(true, typeof(EFTLogonResponse)) { this.LogonType = LogonType; } @@ -69,7 +69,7 @@ public EFTLogonRequest(LogonType LogonType) : base() public class EFTLogonResponse : EFTResponse { /// Constructs a default terminal logon response object. - public EFTLogonResponse() : base() + public EFTLogonResponse() : base(typeof(EFTLogonRequest)) { } diff --git a/IPInterface/Model/EFTPosAsPinpad.cs b/IPInterface/Model/EFTPosAsPinpad.cs index c3302d4..efdc4ce 100644 --- a/IPInterface/Model/EFTPosAsPinpad.cs +++ b/IPInterface/Model/EFTPosAsPinpad.cs @@ -20,7 +20,7 @@ public enum EntryPointType { Undefined, Default, Button = Default, PaymentMenu, public class EFTPosAsPinpadRequest : EFTRequest { /// Constructs a default POS as pinpad request object. - public EFTPosAsPinpadRequest() : base() + public EFTPosAsPinpadRequest() : base(false, typeof(EFTPosAsPinpadResponse)) { } @@ -44,7 +44,7 @@ public EFTPosAsPinpadRequest() : base() public class EFTPosAsPinpadResponse : EFTResponse { /// Constructs a default POS as pinpad response object. - public EFTPosAsPinpadResponse() : base() + public EFTPosAsPinpadResponse() : base(typeof(EFTPosAsPinpadRequest)) { } diff --git a/IPInterface/Model/EFTRequest.cs b/IPInterface/Model/EFTRequest.cs index 861394c..b6a7862 100644 --- a/IPInterface/Model/EFTRequest.cs +++ b/IPInterface/Model/EFTRequest.cs @@ -31,10 +31,69 @@ public enum ReceiptCutModeType /// Abstract base class for EFT client requests. public abstract class EFTRequest { + protected bool isStartOfTransactionRequest = true; + protected Type pairedResponseType = null; + + private EFTRequest() + { + + } + + /// + /// + /// + /// + /// + public EFTRequest(bool isStartOfTransactionRequest, Type pairedResponseType) + { + if(pairedResponseType != null && pairedResponseType.IsSubclassOf(typeof(EFTResponse)) != true) + { + throw new InvalidOperationException("pairedResponseType must be based on EFTResponse"); + } + + this.isStartOfTransactionRequest = isStartOfTransactionRequest; + this.pairedResponseType = pairedResponseType; + } + + /// + /// True if this request starts a paired transaction request/response with displays etc (i.e. transaction, logon, settlement etc) + /// + public virtual bool GetIsStartOfTransactionRequest() { return isStartOfTransactionRequest; } + + /// + /// Indicates the paired EFTResponse for this EFTRequest if one exists. Null otherwise. + /// e.g. EFTLogonRequest will have a paired EFTLogonResponse response + /// + public virtual Type GetPairedResponseType() { return pairedResponseType; } } /// Abstract base class for EFT client responses. public abstract class EFTResponse { + protected Type pairedRequestType = null; + + /// + /// Hidden default constructor + /// + private EFTResponse() + { + + } + + public EFTResponse(Type pairedRequestType) + { + if (pairedRequestType != null && pairedRequestType.IsSubclassOf(typeof(EFTRequest)) != true) + { + throw new InvalidOperationException("pairedRequestType must be based on EFTRequest"); + } + + this.pairedRequestType = pairedRequestType; + } + + /// + /// Indicates the paired EFTRequest for this EFTResponse if one exists. Null otherwise. + /// e.g. EFTLogonResponse will have a paired EFTLogonRequest request + /// + public virtual Type GetPairedRequestType() { return pairedRequestType; } } } diff --git a/IPInterface/Model/EFTSendKey.cs b/IPInterface/Model/EFTSendKey.cs index a5c064e..c8e082b 100644 --- a/IPInterface/Model/EFTSendKey.cs +++ b/IPInterface/Model/EFTSendKey.cs @@ -23,8 +23,9 @@ public enum EFTPOSKey public class EFTSendKeyRequest : EFTRequest { /// Constructs a default client list object. - public EFTSendKeyRequest() : base() + public EFTSendKeyRequest() : base(false, null) { + isStartOfTransactionRequest = false; } /// The type of key to send diff --git a/IPInterface/Model/EFTSettlement.cs b/IPInterface/Model/EFTSettlement.cs index d3236ae..278fc85 100644 --- a/IPInterface/Model/EFTSettlement.cs +++ b/IPInterface/Model/EFTSettlement.cs @@ -131,7 +131,7 @@ public SettlementTotals(): base() public class EFTSettlementRequest : EFTRequest { /// Constructs a default terminal settlement request object. - public EFTSettlementRequest(): base() + public EFTSettlementRequest(): base(true, typeof(EFTSettlementResponse)) { } @@ -171,7 +171,7 @@ public class EFTSettlementResponse : EFTResponse //SettlementTotals totalsData; /// Constructs a default terminal settlement response object. - public EFTSettlementResponse() : base() + public EFTSettlementResponse() : base(typeof(EFTSettlementRequest)) { //settleCardData = new List(); //totalsData = new SettlementTotals(); diff --git a/IPInterface/Model/EFTTransaction.cs b/IPInterface/Model/EFTTransaction.cs index bd31278..9849675 100644 --- a/IPInterface/Model/EFTTransaction.cs +++ b/IPInterface/Model/EFTTransaction.cs @@ -2,7 +2,7 @@ namespace PCEFTPOS.EFTClient.IPInterface { - /// PC-EFTPOS terminal applications. + /// PC-EFTPOS terminal applications. public enum TerminalApplication { /// The request is for the EFTPOS application. @@ -22,136 +22,138 @@ public enum TerminalApplication /// The request is for the Loyalty application. Loyalty, /// The request is for the PrePaidCard application. - PrePaidCard + PrePaidCard, + /// The request is for the ETS application. + ETS } - /// EFTPOS transaction types. - public enum TransactionType - { - /// Transaction type was not set by the PIN pad (' '). - NotSet = ' ', - /// A purchase with optional cash-out EFT transaction type ('P'). - PurchaseCash = 'P', - /// A cash-out only EFT transaction type ('C'). - CashOut = 'C', - /// A refund EFT transaction type ('R'). - Refund = 'R', - /// A pre-authorization EFT transaction type ('A'). - PreAuth = 'A', - /// A pre-authorization / completion EFT transaction type ('L'). - PreAuthCompletion = 'L', - /// A pre-authorization / enquiry EFT transaction type ('N'). - PreAuthEnquiry = 'N', - /// A pre-authorization / cancel EFT transaction type ('Q'). - PreAuthCancel = 'Q', - /// A completion EFT transaction type ('M'). - Completion = 'M', - /// A tip adjustment EFT transaction type ('T'). - TipAdjust = 'T', - /// A deposit EFT transaction type ('D'). - Deposit = 'D', - /// A witdrawal EFT transaction type ('W'). - Withdrawal = 'W', - /// A balance EFT transaction type ('B'). - Balance = 'B', - /// A voucher EFT transaction type ('V'). - Voucher = 'V', - /// A funds transfer EFT transaction type ('F'). - FundsTransfer = 'F', - /// A order request EFT transaction type ('O'). - OrderRequest = 'O', - /// A mini transaction history EFT transaction type ('H'). - MiniTransactionHistory = 'H', - /// A auth pin EFT transaction type ('X'). - AuthPIN = 'X', - /// A enhanced pin EFT transaction type ('K'). - EnhancedPIN = 'K', - - /// A Redemption allows the POS to use the card as a payment type. This will take the amount from the Card balance ('P'). - [Filter("ETS")] - Redemption = 'P', - /// A Refund to Card allows the POS to return the value of a previous sale to a Card ('R'). - [Filter("ETS")] - RefundToCard = 'R', - /// - [Filter("ETS")] - CardSaleTopUp = 'T', - /// - [Filter("ETS")] - CardSale = 'D', - /// A Refund from card allows the POS to instruct the host to take an amount from a Card ('W'). - [Filter("ETS")] - RefundFromCard = 'W', - /// A Balance returns the current balance of funds on the card ('B'). - [Filter("ETS")] - CardBalance = 'B', - /// Activates the card ('A'). - [Filter("ETS")] - CardActivate = 'A', - /// A de-activate returns a cards to state where the card requires activation before it can be used ('F'). - [Filter("ETS")] - CardDeactivate = 'F', - /// This command will add a number of points (or dollars) to a card ('N'). - [Filter("ETS")] - AddPointsToCard = 'N', - /// This command will subtract a number of points (or dollars) from a card ('K'). - [Filter("ETS")] - DecrementPointsFromCard = 'K', - /// This command allows a POS to transfer points from a card to another source ('M'). - [Filter("ETS")] - TransferPoints = 'M', - /// This command will return the amount of cash that is currently on the card and decrement the entire amount from the card ('X'). - [Filter("ETS")] - CashBackFromCard = 'X', - /// This command will cancel or void a previous sale ('I'). - [Filter("ETS")] - CancelVoid = 'I', - /// This command adds a card to the card list on the Host ('L'). - [Filter("ETS")] - AddCard = 'L', - - None = 0 - } - - public class FilterAttribute : Attribute - { - public FilterAttribute(string customString) - { - this.customString = customString; - } - private string customString; - public string CustomString - { - get { return customString; } - set { customString = value; } - } - } - - public class DescriptionAttribute : Attribute - { - public DescriptionAttribute(string description) - { - this.description = description; - } - private string description; - public string Description - { - get { return description; } - set { description = value; } - } - } - - /// Supported EFTPOS account types. - public enum AccountType + /// EFTPOS transaction types. + public enum TransactionType + { + /// Transaction type was not set by the PIN pad (' '). + NotSet = ' ', + /// A purchase with optional cash-out EFT transaction type ('P'). + PurchaseCash = 'P', + /// A cash-out only EFT transaction type ('C'). + CashOut = 'C', + /// A refund EFT transaction type ('R'). + Refund = 'R', + /// A pre-authorization EFT transaction type ('A'). + PreAuth = 'A', + /// A pre-authorization / completion EFT transaction type ('L'). + PreAuthCompletion = 'L', + /// A pre-authorization / enquiry EFT transaction type ('N'). + PreAuthEnquiry = 'N', + /// A pre-authorization / cancel EFT transaction type ('Q'). + PreAuthCancel = 'Q', + /// A completion EFT transaction type ('M'). + Completion = 'M', + /// A tip adjustment EFT transaction type ('T'). + TipAdjust = 'T', + /// A deposit EFT transaction type ('D'). + Deposit = 'D', + /// A witdrawal EFT transaction type ('W'). + Withdrawal = 'W', + /// A balance EFT transaction type ('B'). + Balance = 'B', + /// A voucher EFT transaction type ('V'). + Voucher = 'V', + /// A funds transfer EFT transaction type ('F'). + FundsTransfer = 'F', + /// A order request EFT transaction type ('O'). + OrderRequest = 'O', + /// A mini transaction history EFT transaction type ('H'). + MiniTransactionHistory = 'H', + /// A auth pin EFT transaction type ('X'). + AuthPIN = 'X', + /// A enhanced pin EFT transaction type ('K'). + EnhancedPIN = 'K', + + /// A Redemption allows the POS to use the card as a payment type. This will take the amount from the Card balance ('P'). + [Filter("ETS")] + Redemption = 'P', + /// A Refund to Card allows the POS to return the value of a previous sale to a Card ('R'). + [Filter("ETS")] + RefundToCard = 'R', + /// + [Filter("ETS")] + CardSaleTopUp = 'T', + /// + [Filter("ETS")] + CardSale = 'D', + /// A Refund from card allows the POS to instruct the host to take an amount from a Card ('W'). + [Filter("ETS")] + RefundFromCard = 'W', + /// A Balance returns the current balance of funds on the card ('B'). + [Filter("ETS")] + CardBalance = 'B', + /// Activates the card ('A'). + [Filter("ETS")] + CardActivate = 'A', + /// A de-activate returns a cards to state where the card requires activation before it can be used ('F'). + [Filter("ETS")] + CardDeactivate = 'F', + /// This command will add a number of points (or dollars) to a card ('N'). + [Filter("ETS")] + AddPointsToCard = 'N', + /// This command will subtract a number of points (or dollars) from a card ('K'). + [Filter("ETS")] + DecrementPointsFromCard = 'K', + /// This command allows a POS to transfer points from a card to another source ('M'). + [Filter("ETS")] + TransferPoints = 'M', + /// This command will return the amount of cash that is currently on the card and decrement the entire amount from the card ('X'). + [Filter("ETS")] + CashBackFromCard = 'X', + /// This command will cancel or void a previous sale ('I'). + [Filter("ETS")] + CancelVoid = 'I', + /// This command adds a card to the card list on the Host ('L'). + [Filter("ETS")] + AddCard = 'L', + + None = 0 + } + + public class FilterAttribute : Attribute + { + public FilterAttribute(string customString) + { + this.customString = customString; + } + private string customString; + public string CustomString + { + get { return customString; } + set { customString = value; } + } + } + + public class DescriptionAttribute : Attribute + { + public DescriptionAttribute(string description) + { + this.description = description; + } + private string description; + public string Description + { + get { return description; } + set { description = value; } + } + } + + /// Supported EFTPOS account types. + public enum AccountType { /// The default account type for a card. Default = ' ', /// The savings account type. - Savings = '1', + Savings = '3', /// The cheque account type. - Cheque = '2', + Cheque = '1', /// The credit account type. - Credit = '3' + Credit = '2' } /// The card entry type of the transaction. @@ -210,457 +212,457 @@ public enum PayPassStatus PayPassNotUsed = '0' } - /// Flags that indicate how the transaction was processed. - public class TxnFlags - { - char[] flags; - - /// Constructs a TxnFlags object with default values. - public TxnFlags() - { - } - - /// Constructs a TxnFlags object. - /// A Char array representing the flags. - public TxnFlags(char[] flags) - { - this.flags = new char[8] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; - System.Array.Copy(flags, 0, this.flags, 0, (flags.Length > 8) ? 8 : flags.Length); - - - Offline = this.flags[0] == '1'; - ReceiptPrinted = this.flags[1] == '1'; - CardEntry = (CardEntryType)this.flags[2]; - CommsMethod = (CommsMethodType)this.flags[3]; - Currency = (CurrencyStatus)this.flags[4]; - PayPass = (PayPassStatus)this.flags[5]; - UndefinedFlag6 = this.flags[6]; - UndefinedFlag7 = this.flags[7]; - } - - /// Indicates if a receipt was printed for this transaction. - /// Type: Set to TRUE if a receipt was printed. - public bool ReceiptPrinted { get; set; } = false; - - /// Indicates if the transaction was approved offline. - /// Type: Set to TRUE if the transaction was approved offline. - public bool Offline { get; set; } = false; - - /// Indicates the card entry type. - /// Type: - public CardEntryType CardEntry { get; set; } = CardEntryType.NotSet; - - /// Indicates the communications method used for this transaction. - /// Type: - public CommsMethodType CommsMethod { get; set; } = CommsMethodType.NotSet; - - /// Indicates the currency conversion status for this transaction. - /// Type: - public CurrencyStatus Currency { get; set; } = CurrencyStatus.NotSet; - - /// Indicates the Pay Pass status for this transaction. - /// Type: - public PayPassStatus PayPass { get; set; } = PayPassStatus.NotSet; - - /// Undefined flag 6 - public char UndefinedFlag6 { get; set; } = ' '; - - /// Undefined flag 7 - public char UndefinedFlag7 { get; set; } = ' '; - } - - /// A PC-EFTPOS transaction request object. - public class EFTTransactionRequest : EFTRequest - { - /// Constructs a default EFTTransactionRequest object. - public EFTTransactionRequest() : base() - { - } - - /// The type of transaction to perform. - /// Type: The default is - public TransactionType TxnType { get; set; } = TransactionType.PurchaseCash; - - /// The type of transaction to perform. - /// Type: The default is - [System.Obsolete("Please use TxnType instead of Type")] - public TransactionType Type { get { return TxnType; } set { TxnType = value; } } - - /// Two digit merchant code - /// Type: The default is "00" - public string Merchant { get; set; } = "00"; - - /// The currency code for this transaction. - /// Type: A 3 digit ISO currency code. The default is " ". - public string CurrencyCode { get; set; } = " "; - - /// The original type of transaction for voucher entry. - /// Type: The default is - public TransactionType OriginalTxnType { get; set; } = TransactionType.PurchaseCash; - - /// Date. Used for voucher or completion only - /// Type: The default is null - public DateTime? Date { get; set; } = null; - - /// Time. Used for voucher or completion only - /// Type: The default is null - public DateTime? Time { get; set; } = null; - - /// Determines if the transaction is a training mode transaction. - /// Type: Set to TRUE if the transaction is to be performed in training mode. The default is FALSE. - public bool TrainingMode { get; set; } = false; - - /// Indicates if the transaction should be tipable. - /// Type: Set to TRUE if tipping is to be enabled for this transaction. The default is FALSE. - public bool EnableTip { get; set; } = false; - - /// Indicates if the transaction should be tipable. - /// Type: Set to TRUE if tipping is to be enabled for this transaction. The default is FALSE. - [System.Obsolete("Please use EnableTip instead of EnableTipping")] - public bool EnableTipping { get { return EnableTip; } set { EnableTip = value; } } - - /// The cash amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for a transaction type. - public decimal AmtCash { get; set; } = 0; - - /// The cash amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for a transaction type. - [System.Obsolete("Please use AmtCash instead of AmountCash")] - public decimal AmountCash { get { return AmtCash; } set { AmtCash = value; } } - - /// The purchase amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for all but transaction types. - public decimal AmtPurchase { get; set; } = 0; - - /// The purchase amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for all but transaction types. - [System.Obsolete("Please use AmtPurchase instead of AmountPurchase")] - public decimal AmountPurchase { get { return AmtPurchase; } set { AmtPurchase = value; } } - - /// The authorisation number for the transaction. - /// Type: - /// This property is required for a transaction type. - public int AuthCode { get; set; } = 0; - - /// The authorisation number for the transaction. - /// Type: - /// This property is required for a transaction type. - [System.Obsolete("Please use AuthCode instead of AuthNumber")] - public int AuthNumber { get { return AuthCode; } set { AuthCode = value; } } - - - /// The reference number to attach to the transaction. This will appear on the receipt. - /// Type: - /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. - public string TxnRef { get; set; } = ""; - - /// The reference number to attach to the transaction. This will appear on the receipt. - /// Type: - /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. - [System.Obsolete("Please use TxnRef instead of ReferenceNumber")] - public string ReferenceNumber { get { return TxnRef; } set { TxnRef = value; } } - - - - /// Indicates the source of the card number. - /// Type: The default is . - /// Use this property for card not present transactions. - public PanSource PanSource { get; set; } = PanSource.Default; - - - /// Indicates the source of the card number. - /// Type: The default is . - /// Use this property for card not present transactions. - [System.Obsolete("Please use PanSource instead of CardPANSource")] - public PanSource CardPANSource { get { return PanSource; } set { PanSource = value; } } - - /// The card number to use when pan source of POS keyed is used. - /// Type: - /// Use this property in conjunction with . - public string Pan { get; set; } = ""; - - /// The card number to use when pan source of POS keyed is used. - /// Type: - /// Use this property in conjunction with . - [System.Obsolete("Please use Pan instead of CardPAN")] - public string CardPAN { get { return Pan; } set { Pan = value; } } - - /// The expiry date of the card when of POS keyed is used. - /// Type: In MMYY format. - /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. - public string DateExpiry { get; set; } = ""; - - /// The expiry date of the card when of POS keyed is used. - /// Type: In MMYY format. - /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. - [System.Obsolete("Please use DateExpiry instead of ExpiryDate")] - public string ExpiryDate { get { return DateExpiry; } set { DateExpiry = value; } } - - /// The track 2 to use when of POS swiped is used. - /// Type: - /// Use this property when is set to and passing the full Track2 from the card magnetic stripe to PC-EFTPOS. - public string Track2 { get; set; } = ""; - - /// The account to use for this transaction. - /// Type: Default is . Use default to prompt user to enter the account type. - /// Use this property in conjunction with when passing the account type to PC-EFTPOS. - public AccountType CardAccountType { get; set; } = AccountType.Default; - - /// The retrieval reference number for the transaction. - /// Type: - /// This property is required for a transaction type. - public string RRN { get; set; } = ""; - - /// Additional information sent with the request. - /// Type: - public PadField PurchaseAnalysisData { get; set; } = new PadField(); - - /// Indicates where the request is to be sent to. Should normally be EFTPOS. - /// Type: The default is . - public TerminalApplication Application { get; set; } = TerminalApplication.EFTPOS; - - /// Indicates whether to trigger receipt events. - /// Type: The default is POSPrinter. - public ReceiptPrintModeType ReceiptPrintMode { get; set; } = ReceiptPrintModeType.POSPrinter; - - /// Indicates whether PC-EFTPOS should cut receipts. - /// Type: The default is DontCut. This property only applies when is set to EFTClientPrinter. - public ReceiptCutModeType ReceiptCutMode { get; set; } = ReceiptCutModeType.DontCut; - - /// - /// - /// - public int CVV { get; set; } = 0; - } + /// Flags that indicate how the transaction was processed. + public class TxnFlags + { + char[] flags; + + /// Constructs a TxnFlags object with default values. + public TxnFlags() + { + } + + /// Constructs a TxnFlags object. + /// A Char array representing the flags. + public TxnFlags(char[] flags) + { + this.flags = new char[8] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + System.Array.Copy(flags, 0, this.flags, 0, (flags.Length > 8) ? 8 : flags.Length); + + + Offline = this.flags[0] == '1'; + ReceiptPrinted = this.flags[1] == '1'; + CardEntry = (CardEntryType)this.flags[2]; + CommsMethod = (CommsMethodType)this.flags[3]; + Currency = (CurrencyStatus)this.flags[4]; + PayPass = (PayPassStatus)this.flags[5]; + UndefinedFlag6 = this.flags[6]; + UndefinedFlag7 = this.flags[7]; + } + + /// Indicates if a receipt was printed for this transaction. + /// Type: Set to TRUE if a receipt was printed. + public bool ReceiptPrinted { get; set; } = false; + + /// Indicates if the transaction was approved offline. + /// Type: Set to TRUE if the transaction was approved offline. + public bool Offline { get; set; } = false; + + /// Indicates the card entry type. + /// Type: + public CardEntryType CardEntry { get; set; } = CardEntryType.NotSet; + + /// Indicates the communications method used for this transaction. + /// Type: + public CommsMethodType CommsMethod { get; set; } = CommsMethodType.NotSet; + + /// Indicates the currency conversion status for this transaction. + /// Type: + public CurrencyStatus Currency { get; set; } = CurrencyStatus.NotSet; + + /// Indicates the Pay Pass status for this transaction. + /// Type: + public PayPassStatus PayPass { get; set; } = PayPassStatus.NotSet; + + /// Undefined flag 6 + public char UndefinedFlag6 { get; set; } = ' '; + + /// Undefined flag 7 + public char UndefinedFlag7 { get; set; } = ' '; + } + + /// A PC-EFTPOS transaction request object. + public class EFTTransactionRequest : EFTRequest + { + /// Constructs a default EFTTransactionRequest object. + public EFTTransactionRequest() : base(true, typeof(EFTTransactionResponse)) + { + } + + /// The type of transaction to perform. + /// Type: The default is + public TransactionType TxnType { get; set; } = TransactionType.PurchaseCash; + + /// The type of transaction to perform. + /// Type: The default is + [System.Obsolete("Please use TxnType instead of Type")] + public TransactionType Type { get { return TxnType; } set { TxnType = value; } } + + /// Two digit merchant code + /// Type: The default is "00" + public string Merchant { get; set; } = "00"; + + /// The currency code for this transaction. + /// Type: A 3 digit ISO currency code. The default is " ". + public string CurrencyCode { get; set; } = " "; + + /// The original type of transaction for voucher entry. + /// Type: The default is + public TransactionType OriginalTxnType { get; set; } = TransactionType.PurchaseCash; + + /// Date. Used for voucher or completion only + /// Type: The default is null + public DateTime? Date { get; set; } = null; + + /// Time. Used for voucher or completion only + /// Type: The default is null + public DateTime? Time { get; set; } = null; + + /// Determines if the transaction is a training mode transaction. + /// Type: Set to TRUE if the transaction is to be performed in training mode. The default is FALSE. + public bool TrainingMode { get; set; } = false; + + /// Indicates if the transaction should be tipable. + /// Type: Set to TRUE if tipping is to be enabled for this transaction. The default is FALSE. + public bool EnableTip { get; set; } = false; + + /// Indicates if the transaction should be tipable. + /// Type: Set to TRUE if tipping is to be enabled for this transaction. The default is FALSE. + [System.Obsolete("Please use EnableTip instead of EnableTipping")] + public bool EnableTipping { get { return EnableTip; } set { EnableTip = value; } } + + /// The cash amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for a transaction type. + public decimal AmtCash { get; set; } = 0; + + /// The cash amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for a transaction type. + [System.Obsolete("Please use AmtCash instead of AmountCash")] + public decimal AmountCash { get { return AmtCash; } set { AmtCash = value; } } + + /// The purchase amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for all but transaction types. + public decimal AmtPurchase { get; set; } = 0; + + /// The purchase amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for all but transaction types. + [System.Obsolete("Please use AmtPurchase instead of AmountPurchase")] + public decimal AmountPurchase { get { return AmtPurchase; } set { AmtPurchase = value; } } + + /// The authorisation number for the transaction. + /// Type: + /// This property is required for a transaction type. + public int AuthCode { get; set; } = 0; + + /// The authorisation number for the transaction. + /// Type: + /// This property is required for a transaction type. + [System.Obsolete("Please use AuthCode instead of AuthNumber")] + public int AuthNumber { get { return AuthCode; } set { AuthCode = value; } } + + + /// The reference number to attach to the transaction. This will appear on the receipt. + /// Type: + /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. + public string TxnRef { get; set; } = ""; + + /// The reference number to attach to the transaction. This will appear on the receipt. + /// Type: + /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. + [System.Obsolete("Please use TxnRef instead of ReferenceNumber")] + public string ReferenceNumber { get { return TxnRef; } set { TxnRef = value; } } + + + + /// Indicates the source of the card number. + /// Type: The default is . + /// Use this property for card not present transactions. + public PanSource PanSource { get; set; } = PanSource.Default; + + + /// Indicates the source of the card number. + /// Type: The default is . + /// Use this property for card not present transactions. + [System.Obsolete("Please use PanSource instead of CardPANSource")] + public PanSource CardPANSource { get { return PanSource; } set { PanSource = value; } } + + /// The card number to use when pan source of POS keyed is used. + /// Type: + /// Use this property in conjunction with . + public string Pan { get; set; } = ""; + + /// The card number to use when pan source of POS keyed is used. + /// Type: + /// Use this property in conjunction with . + [System.Obsolete("Please use Pan instead of CardPAN")] + public string CardPAN { get { return Pan; } set { Pan = value; } } + + /// The expiry date of the card when of POS keyed is used. + /// Type: In MMYY format. + /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. + public string DateExpiry { get; set; } = ""; + + /// The expiry date of the card when of POS keyed is used. + /// Type: In MMYY format. + /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. + [System.Obsolete("Please use DateExpiry instead of ExpiryDate")] + public string ExpiryDate { get { return DateExpiry; } set { DateExpiry = value; } } + + /// The track 2 to use when of POS swiped is used. + /// Type: + /// Use this property when is set to and passing the full Track2 from the card magnetic stripe to PC-EFTPOS. + public string Track2 { get; set; } = ""; + + /// The account to use for this transaction. + /// Type: Default is . Use default to prompt user to enter the account type. + /// Use this property in conjunction with when passing the account type to PC-EFTPOS. + public AccountType CardAccountType { get; set; } = AccountType.Default; + + /// The retrieval reference number for the transaction. + /// Type: + /// This property is required for a transaction type. + public string RRN { get; set; } = ""; + + /// Additional information sent with the request. + /// Type: + public PadField PurchaseAnalysisData { get; set; } = new PadField(); + + /// Indicates where the request is to be sent to. Should normally be EFTPOS. + /// Type: The default is . + public TerminalApplication Application { get; set; } = TerminalApplication.EFTPOS; + + /// Indicates whether to trigger receipt events. + /// Type: The default is POSPrinter. + public ReceiptPrintModeType ReceiptPrintMode { get; set; } = ReceiptPrintModeType.POSPrinter; + + /// Indicates whether PC-EFTPOS should cut receipts. + /// Type: The default is DontCut. This property only applies when is set to EFTClientPrinter. + public ReceiptCutModeType ReceiptCutMode { get; set; } = ReceiptCutModeType.DontCut; + + /// + /// + /// + public int CVV { get; set; } = 0; + } /// A PC-EFTPOS terminal transaction response object. public class EFTTransactionResponse : EFTResponse { /// Constructs a default terminal transaction response object. - public EFTTransactionResponse() : base() + public EFTTransactionResponse() : base(typeof(EFTGetLastTransactionRequest)) { } - /// The type of transaction to perform. - /// Type: The default is - public TransactionType TxnType { get; set; } = TransactionType.PurchaseCash; - - /// The type of transaction to perform. - /// Type: The default is - [System.Obsolete("Please use TxnType instead")] - public TransactionType Type { get { return TxnType; } set { TxnType = value; } } - - /// Two digit merchant code - /// Type: The default is "00" - public string Merchant { get; set; } = "00"; - - /// Indicates the card type that was used in the transaction. - /// Type: - /// - public string CardType { get; set; } = ""; - - /// Indicates the card type that was used in the transaction. - /// Type: - /// - /// Card BINCard Type - /// 0Unknown - /// 1Debit - /// 2Bankcard - /// 3Mastercard - /// 4Visa - /// 5American Express - /// 6Diner Club - /// 7JCB - /// 8Label Card - /// 9JCB - /// 11JCB - /// 12Other - /// - public int CardName { get; set; } = 0; - - /// Indicates the card type that was used in the transaction. - /// Type: - /// - /// Card BINCard Type - /// 0Unknown - /// 1Debit - /// 2Bankcard - /// 3Mastercard - /// 4Visa - /// 5American Express - /// 6Diner Club - /// 7JCB - /// 8Label Card - /// 9JCB - /// 11JCB - /// 12Other - /// - [System.Obsolete("Please use CardName instead of CardBIN")] - public int CardBIN { get { return CardName; } set { CardName = value; } } - - /// Used to retrieve the transaction from the batch. - /// Type: - /// The retrieval reference number is used when performing a tip adjustment transaction. - public string RRN { get; set; } = ""; - - /// Indicates which settlement batch this transaction will be included in. - /// Type: Settlement date is returned from the bank. - /// Use this property to balance POS EFT totals with settlement EFT totals. - public DateTime SettlementDate { get; set; } = DateTime.MinValue; - - /// The cash amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for a transaction type. - public decimal AmtCash { get; set; } = 0; - - /// The cash amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for a transaction type. - [System.Obsolete("Please use AmtCash instead of AmountCash")] - public decimal AmountCash { get { return AmtCash; } set { AmtCash = value; } } - - /// The purchase amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for all but transaction types. - public decimal AmtPurchase { get; set; } = 0; - - /// The purchase amount for the transaction. - /// Type: The default is 0. - /// This property is mandatory for all but transaction types. - [System.Obsolete("Please use AmtPurchase instead of AmountPurchase")] - public decimal AmountPurchase { get { return AmtPurchase; } set { AmtPurchase = value; } } - - /// The tip amount for the transaction. - /// Type: Echoed from the request. - public decimal AmtTip { get; set; } = 0; - - /// The tip amount for the transaction. - /// Type: Echoed from the request. - [System.Obsolete("Please use AmtTip instead of AmountTip")] - public decimal AmountTip { get { return AmtTip; } set { AmtTip = value; } } - - /// The authorisation number for the transaction. - /// Type: - /// This property is required for a transaction type. - public int AuthCode { get; set; } = 0; - - /// The authorisation number for the transaction. - /// Type: - /// This property is required for a transaction type. - [System.Obsolete("Please use AuthCode instead of AuthNumber")] - public int AuthNumber { get { return AuthCode; } set { AuthCode = value; } } - - /// The reference number to attach to the transaction. This will appear on the receipt. - /// Type: - /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. - public string TxnRef { get; set; } = ""; - - /// The reference number to attach to the transaction. This will appear on the receipt. - /// Type: - /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. - [System.Obsolete("Please use TxnRef instead of ReferenceNumber")] - public string ReferenceNumber { get { return TxnRef; } set { TxnRef = value; } } - - /// The card number to use when pan source of POS keyed is used. - /// Type: - /// Use this property in conjunction with . - public string Pan { get; set; } = ""; - - /// The card number to use when pan source of POS keyed is used. - /// Type: - /// Use this property in conjunction with . - [System.Obsolete("Please use PAN instead of CardPAN")] - public string CardPAN { get { return Pan; } set { Pan = value; } } - - /// The expiry date of the card when of POS keyed is used. - /// Type: In MMYY format. - /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. - public string DateExpiry { get; set; } = ""; - - /// The expiry date of the card when of POS keyed is used. - /// Type: In MMYY format. - /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. - [System.Obsolete("Please use DateExpiry instead of ExpiryDate")] - public string ExpiryDate { get { return DateExpiry; } set { DateExpiry = value; } } - - /// The track 2 data on the magnetic stripe of the card. - /// Type: This property contains the partial track 2 data from the card used in this transaction. - public string Track2 { get; set; } = ""; - - /// The account used for the transaction. - /// Type: This is the account type selected by the customer or provided in the request. - public AccountType CardAccountType { get; set; } = AccountType.Default; - - /// Flags that indicate how the transaction was processed. - /// Type: - public TxnFlags TxnFlags { get; set; } = new TxnFlags(); - - /// Indicates if an available balance is present in the response. - /// Type: - public bool BalanceReceived { get; set; } = false; - - /// Balance available on the processed account. - /// Type: - public decimal AvailableBalance { get; set; } = 0; - - /// Cleared balance on the processed account. - /// Type: - public decimal ClearedFundsBalance { get; set; } = 0; - - /// Indicates if the request was successful. - /// Type: - public bool Success { get; set; } = false; - - /// The response code of the request. - /// Type: A 2 character response code. "00" indicates a successful response. - public string ResponseCode { get; set; } = ""; - - /// The response text for the response code. - /// Type: - public string ResponseText { get; set; } = ""; - - /// Date and time of the response returned by the bank. - /// Type: - public DateTime Date { get; set; } = DateTime.MinValue; - - /// Date and time of the response returned by the bank. - /// Type: - [System.Obsolete("Please use Date instead of BankDateTime")] - public DateTime BankDateTime { get { return Date; } set { Date = value; } } - - /// Terminal ID configured in the PIN pad. - /// Type: - public string Catid { get; set; } = ""; - - /// Terminal ID configured in the PIN pad. - /// Type: - [System.Obsolete("Please use Catid instead of TerminalID")] - public string TerminalID { get { return Catid; } set { Catid = value; } } - - /// Merchant ID configured in the PIN pad. - /// Type: - public string Caid { get; set; } = ""; - - /// Merchant ID configured in the PIN pad. - /// Type: - [System.Obsolete("Please use Caid instead of MerchantID")] - public string MerchantID { get { return Caid; } set { Caid = value; } } - - /// System Trace Audit Number - /// Type: - public int Stan { get; set; } = 0; - - /// System Trace Audit Number - /// Type: - [System.Obsolete("Please use Stan instead of STAN")] - public int STAN { get { return Stan; } set { Stan = value; } } - - /// Additional information sent with the response. - /// Type: - public PadField PurchaseAnalysisData { get; set; } = new PadField(); - } + /// The type of transaction to perform. + /// Type: The default is + public TransactionType TxnType { get; set; } = TransactionType.PurchaseCash; + + /// The type of transaction to perform. + /// Type: The default is + [System.Obsolete("Please use TxnType instead")] + public TransactionType Type { get { return TxnType; } set { TxnType = value; } } + + /// Two digit merchant code + /// Type: The default is "00" + public string Merchant { get; set; } = "00"; + + /// Indicates the card type that was used in the transaction. + /// Type: + /// + public string CardType { get; set; } = ""; + + /// Indicates the card type that was used in the transaction. + /// Type: + /// + /// Card BINCard Type + /// 0Unknown + /// 1Debit + /// 2Bankcard + /// 3Mastercard + /// 4Visa + /// 5American Express + /// 6Diner Club + /// 7JCB + /// 8Label Card + /// 9JCB + /// 11JCB + /// 12Other + /// + public int CardName { get; set; } = 0; + + /// Indicates the card type that was used in the transaction. + /// Type: + /// + /// Card BINCard Type + /// 0Unknown + /// 1Debit + /// 2Bankcard + /// 3Mastercard + /// 4Visa + /// 5American Express + /// 6Diner Club + /// 7JCB + /// 8Label Card + /// 9JCB + /// 11JCB + /// 12Other + /// + [System.Obsolete("Please use CardName instead of CardBIN")] + public int CardBIN { get { return CardName; } set { CardName = value; } } + + /// Used to retrieve the transaction from the batch. + /// Type: + /// The retrieval reference number is used when performing a tip adjustment transaction. + public string RRN { get; set; } = ""; + + /// Indicates which settlement batch this transaction will be included in. + /// Type: Settlement date is returned from the bank. + /// Use this property to balance POS EFT totals with settlement EFT totals. + public DateTime SettlementDate { get; set; } = DateTime.MinValue; + + /// The cash amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for a transaction type. + public decimal AmtCash { get; set; } = 0; + + /// The cash amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for a transaction type. + [System.Obsolete("Please use AmtCash instead of AmountCash")] + public decimal AmountCash { get { return AmtCash; } set { AmtCash = value; } } + + /// The purchase amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for all but transaction types. + public decimal AmtPurchase { get; set; } = 0; + + /// The purchase amount for the transaction. + /// Type: The default is 0. + /// This property is mandatory for all but transaction types. + [System.Obsolete("Please use AmtPurchase instead of AmountPurchase")] + public decimal AmountPurchase { get { return AmtPurchase; } set { AmtPurchase = value; } } + + /// The tip amount for the transaction. + /// Type: Echoed from the request. + public decimal AmtTip { get; set; } = 0; + + /// The tip amount for the transaction. + /// Type: Echoed from the request. + [System.Obsolete("Please use AmtTip instead of AmountTip")] + public decimal AmountTip { get { return AmtTip; } set { AmtTip = value; } } + + /// The authorisation number for the transaction. + /// Type: + /// This property is required for a transaction type. + public int AuthCode { get; set; } = 0; + + /// The authorisation number for the transaction. + /// Type: + /// This property is required for a transaction type. + [System.Obsolete("Please use AuthCode instead of AuthNumber")] + public int AuthNumber { get { return AuthCode; } set { AuthCode = value; } } + + /// The reference number to attach to the transaction. This will appear on the receipt. + /// Type: + /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. + public string TxnRef { get; set; } = ""; + + /// The reference number to attach to the transaction. This will appear on the receipt. + /// Type: + /// This property is optional but it usually populated by a unique transaction identifier that can be used for retrieval. + [System.Obsolete("Please use TxnRef instead of ReferenceNumber")] + public string ReferenceNumber { get { return TxnRef; } set { TxnRef = value; } } + + /// The card number to use when pan source of POS keyed is used. + /// Type: + /// Use this property in conjunction with . + public string Pan { get; set; } = ""; + + /// The card number to use when pan source of POS keyed is used. + /// Type: + /// Use this property in conjunction with . + [System.Obsolete("Please use PAN instead of CardPAN")] + public string CardPAN { get { return Pan; } set { Pan = value; } } + + /// The expiry date of the card when of POS keyed is used. + /// Type: In MMYY format. + /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. + public string DateExpiry { get; set; } = ""; + + /// The expiry date of the card when of POS keyed is used. + /// Type: In MMYY format. + /// Use this property in conjunction with when passing the card expiry date to PC-EFTPOS. + [System.Obsolete("Please use DateExpiry instead of ExpiryDate")] + public string ExpiryDate { get { return DateExpiry; } set { DateExpiry = value; } } + + /// The track 2 data on the magnetic stripe of the card. + /// Type: This property contains the partial track 2 data from the card used in this transaction. + public string Track2 { get; set; } = ""; + + /// The account used for the transaction. + /// Type: This is the account type selected by the customer or provided in the request. + public AccountType CardAccountType { get; set; } = AccountType.Default; + + /// Flags that indicate how the transaction was processed. + /// Type: + public TxnFlags TxnFlags { get; set; } = new TxnFlags(); + + /// Indicates if an available balance is present in the response. + /// Type: + public bool BalanceReceived { get; set; } = false; + + /// Balance available on the processed account. + /// Type: + public decimal AvailableBalance { get; set; } = 0; + + /// Cleared balance on the processed account. + /// Type: + public decimal ClearedFundsBalance { get; set; } = 0; + + /// Indicates if the request was successful. + /// Type: + public bool Success { get; set; } = false; + + /// The response code of the request. + /// Type: A 2 character response code. "00" indicates a successful response. + public string ResponseCode { get; set; } = ""; + + /// The response text for the response code. + /// Type: + public string ResponseText { get; set; } = ""; + + /// Date and time of the response returned by the bank. + /// Type: + public DateTime Date { get; set; } = DateTime.MinValue; + + /// Date and time of the response returned by the bank. + /// Type: + [System.Obsolete("Please use Date instead of BankDateTime")] + public DateTime BankDateTime { get { return Date; } set { Date = value; } } + + /// Terminal ID configured in the PIN pad. + /// Type: + public string Catid { get; set; } = ""; + + /// Terminal ID configured in the PIN pad. + /// Type: + [System.Obsolete("Please use Catid instead of TerminalID")] + public string TerminalID { get { return Catid; } set { Catid = value; } } + + /// Merchant ID configured in the PIN pad. + /// Type: + public string Caid { get; set; } = ""; + + /// Merchant ID configured in the PIN pad. + /// Type: + [System.Obsolete("Please use Caid instead of MerchantID")] + public string MerchantID { get { return Caid; } set { Caid = value; } } + + /// System Trace Audit Number + /// Type: + public int Stan { get; set; } = 0; + + /// System Trace Audit Number + /// Type: + [System.Obsolete("Please use Stan instead of STAN")] + public int STAN { get { return Stan; } set { Stan = value; } } + + /// Additional information sent with the response. + /// Type: + public PadField PurchaseAnalysisData { get; set; } = new PadField(); + } } \ No newline at end of file diff --git a/IPInterface/Model/GenericCommand.cs b/IPInterface/Model/GenericCommand.cs index 0843983..d48a3c8 100644 --- a/IPInterface/Model/GenericCommand.cs +++ b/IPInterface/Model/GenericCommand.cs @@ -44,7 +44,11 @@ public enum PasswordDisplay public class EFTGetPasswordRequest : EFTRequest { - public int MinPasswordLength { get; set; } + public EFTGetPasswordRequest() : base(true, typeof(EFTGetPasswordResponse)) + { + } + + public int MinPasswordLength { get; set; } public int MaxPassworkLength { get; set; } public int Timeout { get; set; } public PasswordDisplay PasswordDisplay { get; set; } @@ -52,7 +56,11 @@ public class EFTGetPasswordRequest : EFTRequest public class EFTGetPasswordResponse : EFTResponse { - public string Password { get; set; } + public EFTGetPasswordResponse() : base(typeof(EFTGetPasswordRequest)) + { + } + + public string Password { get; set; } /// Indicates if the request was successful. /// Type: @@ -67,26 +75,23 @@ public class EFTGetPasswordResponse : EFTResponse public string ResponseText { get; set; } = ""; } - public class EFTSlaveRequest : EFTRequest - { - public string Command { get; set; } - } - - public class EFTSlaveResponse : EFTResponse - { - public bool Success { get { return ResponseCode == "00"; } } - public string ResponseCode { get; set; } - public string Response { get; set; } - } - public class EFTPayAtTableRequest : EFTRequest { + public EFTPayAtTableRequest(): base(false, typeof(EFTPayAtTableResponse)) + { + } + public string Header { get; set; } public string Content { get; set; } } public class EFTPayAtTableResponse : EFTResponse { + public EFTPayAtTableResponse() : base(typeof(EFTPayAtTableResponse)) + { + + } + public string Header { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; } diff --git a/IPInterface/Model/QueryCard.cs b/IPInterface/Model/QueryCard.cs index a2f01a0..a6db44a 100644 --- a/IPInterface/Model/QueryCard.cs +++ b/IPInterface/Model/QueryCard.cs @@ -38,11 +38,19 @@ public enum TrackFlags Track3 } - /// A PC-EFTPOS terminal query card request object. - public class QueryCardRequest : EFTRequest + /// A PC-EFTPOS terminal query card request object. + public class EFTQueryCardRequest : QueryCardRequest + { + } + + /// + /// QueryCardRequest is obsolete. Please use EFTQueryCardRequest + /// + [Obsolete("QueryCardRequest is obsolete. Please use EFTQueryCardRequest")] + public class QueryCardRequest : EFTRequest { /// Constructs a default terminal query card request object. - public QueryCardRequest() : base() + public QueryCardRequest() : base(true, typeof(EFTQueryCardResponse)) { } @@ -68,7 +76,7 @@ public class EFTQueryCardResponse : EFTResponse { /// Constructs a default terminal logon response object. public EFTQueryCardResponse() - : base() + : base(typeof(EFTQueryCardRequest)) { } diff --git a/IPInterface/Model/Receipt.cs b/IPInterface/Model/Receipt.cs index 88f571c..0c0a426 100644 --- a/IPInterface/Model/Receipt.cs +++ b/IPInterface/Model/Receipt.cs @@ -21,7 +21,7 @@ public enum ReceiptType public class EFTReceiptRequest : EFTRequest { /// Constructs a default display response object. - public EFTReceiptRequest() : base() + public EFTReceiptRequest() : base(false, typeof(EFTReceiptResponse)) { } } @@ -29,10 +29,15 @@ public EFTReceiptRequest() : base() /// A PC-EFTPOS receipt response object. public class EFTReceiptResponse : EFTResponse { + public EFTReceiptResponse() : base(typeof(EFTReceiptRequest)) + { + + } + /// Constructs a default display response object. - public EFTReceiptResponse(): base() - { - } + //public EFTReceiptResponse(): base(false, typeof(EFTReceiptRequest)) + //{ + //} /// The receipt type. /// Type: diff --git a/IPInterface/Model/SetDialog.cs b/IPInterface/Model/SetDialog.cs index 6d1b307..7df45a3 100644 --- a/IPInterface/Model/SetDialog.cs +++ b/IPInterface/Model/SetDialog.cs @@ -41,10 +41,9 @@ public enum DialogPosition public class SetDialogRequest : EFTRequest { /// Constructs a default set dialog request object. - public SetDialogRequest() : base() + public SetDialogRequest() : base(false, typeof(SetDialogResponse)) { - } - + } /// Indicates if the type of PC-EFTPOS dialog to use. /// Type: The default is . @@ -100,7 +99,7 @@ public SetDialogRequest() : base() public class SetDialogResponse : EFTResponse { /// Constructs a default set dialog response object. - public SetDialogResponse() : base() + public SetDialogResponse() : base(typeof(SetDialogRequest)) { } diff --git a/IPInterface/Model/Status.cs b/IPInterface/Model/Status.cs index ba8ca5d..4b625c7 100644 --- a/IPInterface/Model/Status.cs +++ b/IPInterface/Model/Status.cs @@ -41,7 +41,7 @@ public enum EFTTerminalType } /// PIN pad terminal supported options. - [FlagsAttribute] + [Flags] public enum PINPadOptionFlags { /// Tipping enabled flag. @@ -115,7 +115,7 @@ public enum TerminalCommsType public class EFTStatusRequest : EFTRequest { /// Constructs a default terminal status request object. - public EFTStatusRequest() : base() + public EFTStatusRequest() : base(true, typeof(EFTStatusResponse)) { } @@ -137,7 +137,7 @@ public class EFTStatusResponse : EFTResponse { /// Constructs a default terminal status response object. public EFTStatusResponse() - : base() + : base(typeof(EFTStatusRequest)) { } diff --git a/IPInterface/PCEFTPOS/Net/TcpSocketAsync.cs b/IPInterface/PCEFTPOS/Net/TcpSocketAsync.cs index 6f2e43e..8144f10 100644 --- a/IPInterface/PCEFTPOS/Net/TcpSocketAsync.cs +++ b/IPInterface/PCEFTPOS/Net/TcpSocketAsync.cs @@ -5,180 +5,183 @@ namespace PCEFTPOS.EFTClient.IPInterface { - class TcpSocketAsync : ITcpSocketAsync - { - TcpClient _client = null; - NetworkStream _clientStream = null; - - public async Task ConnectAsync(string hostName, int hostPort, bool keepAlive) - { - try - { - _client = new TcpClient(); - _client.Client.SetKeepAlive(keepAlive, 60000UL /*60 seconds*/, 1000UL /*1 sec*/); - await _client.ConnectAsync(hostName, hostPort); - return _client.Connected; - } - catch (ObjectDisposedException ox) - { - Close(); - throw new ConnectionException(ox.Message, ox.InnerException); - } - catch (AuthenticationException ax) - { - Close(); - throw new ConnectionException(ax.Message, ax.InnerException); - } - catch (Exception ex) - { - Close(); - throw new ConnectionException(ex.Message, ex.InnerException); - } - } - - public async Task ReadResponseAsync(byte[] buffer, System.Threading.CancellationToken token) - { - try - { - // If we aren't using a CancellationToken we can just call ReadAsync directly - if (token == System.Threading.CancellationToken.None) - { - return await _client.GetStream().ReadAsync(buffer, 0, buffer.Length, token); - } - // Else we need to jump through some hoops as ReadAsync doesn't return when token is cancelled - else - { - var readTask = _client.GetStream().ReadAsync(buffer, 0, buffer.Length); - var timeoutTask = Task.Delay(int.MaxValue, token); // Task.Delay handles CancellationToken correctly - return await Task.Factory.ContinueWhenAny(new Task[] { readTask, timeoutTask }, (completedTask) => - { - if (completedTask == timeoutTask) //the timeout task was the first to complete - { - throw new TaskCanceledException(); - } - else //the readTask completed - { - return readTask.Result; - } - }); - } - } - catch(TaskCanceledException tc) - { - throw tc; - } - catch (Exception e) - { - Close(); - throw new ConnectionException(e.Message, e.InnerException); - } - } - - /// - /// Polls the socket to determine the current connect state - /// - /// True if connected, false otherwise - public async Task CheckConnectStateAsync() - { - // TcpClient.Connected returns the state of the last send/recv operation. It doesn't accurately - // reflect the current socket state. To get the current state we need to send a packet - try - { - // Check if the state is disconnected based on the last operation - if (_client?.Connected != true || _clientStream == null) - return false; - - // Otherwise the socket was connected the last time we used it, send 0 byte packet to see if it still is... - await _clientStream?.WriteAsync(new byte[1], 0, 0); - } - catch (System.IO.IOException e) - { - // 10035 == WSAEWOULDBLOCK - if (e.InnerException != null && e.InnerException is SocketException && (e.InnerException as SocketException).NativeErrorCode == 10035) - return true; - - return false; - } - catch (Exception) - { - return false; - } - - return true; - } - - public async Task WriteRequestAsync(string request) - { - // Build request - var requestBytes = DirectEncoding.DIRECT.GetBytes(request); - - // Send the request string to the IP client. - try - { - await _client.GetStream().WriteAsync(requestBytes, 0, requestBytes.Length); - } - catch (Exception e) - { - Close(); - throw new ConnectionException(e.Message, e.InnerException); - } - return true; - } - - public void Close() - { - _client?.Close(); - } - - /// Defines the level of logging that should be passed back in the OnLog event. Default . See - public LogLevel LogLevel { get; set; } - - /// The log event to be called - public event EventHandler OnLog; - - void Log(LogLevel level, Action traceAction) - { - // Check if this log level is enabled and client is subscribed to the OnLog event - if (OnLog == null || this.LogLevel >= level) - { - return; - } - - TraceRecord tr = new TraceRecord() { Level = level }; - traceAction(tr); - OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = tr.Message, Exception = tr.Exception }); - } - - /// - /// Returns the connected state as of the last read or write operation. This does not necessarily represent - /// the current state of the connection. - /// To check the current socket state call - /// - public bool IsConnected => _client?.Connected ?? false; - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - _client?.Close(); - _client = null; - } - disposedValue = true; - } - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - } - #endregion - } + class TcpSocketAsync : ITcpSocketAsync + { + TcpClient _client = null; + NetworkStream _clientStream = null; + + public async Task ConnectAsync(string hostName, int hostPort, bool keepAlive) + { + try + { + _client = new TcpClient(); + _client.Client.SetKeepAlive(keepAlive, 60000UL /*60 seconds*/, 1000UL /*1 sec*/); + await _client.ConnectAsync(hostName, hostPort); + return _client.Connected; + } + catch (ObjectDisposedException ox) + { + Close(); + throw new ConnectionException(ox.Message, ox.InnerException); + } + catch (AuthenticationException ax) + { + Close(); + throw new ConnectionException(ax.Message, ax.InnerException); + } + catch (Exception ex) + { + Close(); + throw new ConnectionException(ex.Message, ex.InnerException); + } + } + + public async Task ReadResponseAsync(byte[] buffer, System.Threading.CancellationToken token) + { + try + { + // If we aren't using a CancellationToken we can just call ReadAsync directly + if (token == System.Threading.CancellationToken.None) + { + return await _client.GetStream().ReadAsync(buffer, 0, buffer.Length, token); + } + // Else we need to jump through some hoops as ReadAsync doesn't return when token is cancelled + else + { + var readTask = _client.GetStream().ReadAsync(buffer, 0, buffer.Length); + var timeoutTask = Task.Delay(int.MaxValue, token); // Task.Delay handles CancellationToken correctly + return await Task.Factory.ContinueWhenAny(new Task[] { readTask, timeoutTask }, (completedTask) => + { + if (completedTask == timeoutTask) //the timeout task was the first to complete + { + throw new TaskCanceledException(); + } + else //the readTask completed + { + return readTask.Result; + } + }); + } + } + catch (TaskCanceledException tc) + { + throw tc; + } + catch (Exception e) + { + Close(); + throw new ConnectionException(e.Message, e.InnerException); + } + } + + /// + /// Polls the socket to determine the current connect state + /// + /// True if connected, false otherwise + public async Task CheckConnectStateAsync() + { + // TcpClient.Connected returns the state of the last send/recv operation. It doesn't accurately + // reflect the current socket state. To get the current state we need to send a packet + try + { + // Check if the state is disconnected based on the last operation + if (_client?.Connected != true || _clientStream == null) + return false; + + // Otherwise the socket was connected the last time we used it, send 0 byte packet to see if it still is... + if (_clientStream != null) + { + await _clientStream.WriteAsync(new byte[1], 0, 0); + } + } + catch (System.IO.IOException e) + { + // 10035 == WSAEWOULDBLOCK + if (e.InnerException != null && e.InnerException is SocketException && (e.InnerException as SocketException).NativeErrorCode == 10035) + return true; + + return false; + } + catch (Exception) + { + return false; + } + + return true; + } + + public async Task WriteRequestAsync(string request) + { + // Build request + var requestBytes = DirectEncoding.DIRECT.GetBytes(request); + + // Send the request string to the IP client. + try + { + await _client.GetStream().WriteAsync(requestBytes, 0, requestBytes.Length); + } + catch (Exception e) + { + Close(); + throw new ConnectionException(e.Message, e.InnerException); + } + return true; + } + + public void Close() + { + _client?.Close(); + } + + /// Defines the level of logging that should be passed back in the OnLog event. Default . See + public LogLevel LogLevel { get; set; } + + /// The log event to be called + public event EventHandler OnLog; + + void Log(LogLevel level, Action traceAction) + { + // Check if this log level is enabled and client is subscribed to the OnLog event + if (OnLog == null || this.LogLevel >= level) + { + return; + } + + TraceRecord tr = new TraceRecord() { Level = level }; + traceAction(tr); + OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = tr.Message, Exception = tr.Exception }); + } + + /// + /// Returns the connected state as of the last read or write operation. This does not necessarily represent + /// the current state of the connection. + /// To check the current socket state call + /// + public bool IsConnected => _client?.Connected ?? false; + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _client?.Close(); + _client = null; + } + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + } } diff --git a/IPInterface/PCEFTPOS/Net/TcpSocketSslAsync.cs b/IPInterface/PCEFTPOS/Net/TcpSocketSslAsync.cs index e57a36f..cdb6b3b 100644 --- a/IPInterface/PCEFTPOS/Net/TcpSocketSslAsync.cs +++ b/IPInterface/PCEFTPOS/Net/TcpSocketSslAsync.cs @@ -8,286 +8,289 @@ namespace PCEFTPOS.EFTClient.IPInterface { - class TcpSocketSslAsync : ITcpSocketAsync - { - TcpClient _client = null; - SslStream _clientStream = null; + class TcpSocketSslAsync : ITcpSocketAsync + { + TcpClient _client = null; + SslStream _clientStream = null; - public List CustomeRootCerts { get; set; } = null; + public List CustomeRootCerts { get; set; } = null; - public async Task ConnectAsync(string hostName, int hostPort, bool keepAlive = true) - { - try - { - // Connect client - _client = new TcpClient(); - _client.Client.SetKeepAlive(keepAlive, 60000UL /*60 secconds*/, 1000UL /*1 sec*/); - await _client.ConnectAsync(hostName, hostPort); + public async Task ConnectAsync(string hostName, int hostPort, bool keepAlive = true) + { + try + { + // Connect client + _client = new TcpClient(); + _client.Client.SetKeepAlive(keepAlive, 60000UL /*60 secconds*/, 1000UL /*1 sec*/); + await _client.ConnectAsync(hostName, hostPort); - // If we are using SSL, create the SSL stream - _clientStream = new SslStream(_client.GetStream(),true, RemoteCertificateValidationCallback); - await _clientStream.AuthenticateAsClientAsync(hostName); + // If we are using SSL, create the SSL stream + _clientStream = new SslStream(_client.GetStream(), true, RemoteCertificateValidationCallback); + await _clientStream.AuthenticateAsClientAsync(hostName); - if (_clientStream.IsAuthenticated && _clientStream.IsEncrypted && _clientStream.IsSigned) - { - return true; - } - else - { - throw new AuthenticationException("Server and client's security protocol doesn't match."); - } - } - catch (ObjectDisposedException ox) - { - Close(); - throw new ConnectionException(ox.Message, ox.InnerException); - } - catch (AuthenticationException ax) - { - Close(); - throw new ConnectionException(ax.Message, ax.InnerException); - } - catch (Exception ex) - { - Close(); - throw new ConnectionException(ex.Message, ex.InnerException); - } - } + if (_clientStream.IsAuthenticated && _clientStream.IsEncrypted && _clientStream.IsSigned) + { + return true; + } + else + { + throw new AuthenticationException("Server and client's security protocol doesn't match."); + } + } + catch (ObjectDisposedException ox) + { + Close(); + throw new ConnectionException(ox.Message, ox.InnerException); + } + catch (AuthenticationException ax) + { + Close(); + throw new ConnectionException(ax.Message, ax.InnerException); + } + catch (Exception ex) + { + Close(); + throw new ConnectionException(ex.Message, ex.InnerException); + } + } - void LogRemoteCertificateFailure(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - Log(LogLevel.Error, tr => - { - var msg = new System.Text.StringBuilder(); - msg.AppendLine($"SslPolicyErrors={sslPolicyErrors.ToString()}"); - msg.AppendLine($"{chain.ChainElements.Count} certificates in chain"); - int i = 0; - foreach (var c in chain.ChainElements) - { - msg.AppendLine($"Idx:\"{i++}\", Subject:\"{c.Certificate.Subject}\", Issuer:\"{c.Certificate.Issuer}\", Serial:\"{c.Certificate.SerialNumber}\", Before:\"{c.Certificate.NotBefore}\", After:\"{c.Certificate.NotAfter}\", Thumbprint:\"{c.Certificate.Thumbprint}\""); - } + void LogRemoteCertificateFailure(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + Log(LogLevel.Error, tr => + { + var msg = new System.Text.StringBuilder(); + msg.AppendLine($"SslPolicyErrors={sslPolicyErrors.ToString()}"); + msg.AppendLine($"{chain.ChainElements.Count} certificates in chain"); + int i = 0; + foreach (var c in chain.ChainElements) + { + msg.AppendLine($"Idx:\"{i++}\", Subject:\"{c.Certificate.Subject}\", Issuer:\"{c.Certificate.Issuer}\", Serial:\"{c.Certificate.SerialNumber}\", Before:\"{c.Certificate.NotBefore}\", After:\"{c.Certificate.NotAfter}\", Thumbprint:\"{c.Certificate.Thumbprint}\""); + } - tr.Message = msg.ToString(); - }); - } + tr.Message = msg.ToString(); + }); + } - /// - /// Validate the PC-EFTPOS Cloud server certificate. - /// - /// TRUE if the certificate is valid, FALSE otherwise - bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - // Certificate chain is valid via a commercial 3rd party chain - if (sslPolicyErrors == SslPolicyErrors.None) - { - Log(LogLevel.Info, tr => tr.Set("Remote certificate validated successfull by installed CA")); - return true; - } + /// + /// Validate the PC-EFTPOS Cloud server certificate. + /// + /// TRUE if the certificate is valid, FALSE otherwise + bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + // Certificate chain is valid via a commercial 3rd party chain + if (sslPolicyErrors == SslPolicyErrors.None) + { + Log(LogLevel.Info, tr => tr.Set("Remote certificate validated successfull by installed CA")); + return true; + } - // Certificate has an invalid CN or isn't available from the server - if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNotAvailable || sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch) - { - LogRemoteCertificateFailure(certificate, chain, sslPolicyErrors); - return false; - } + // Certificate has an invalid CN or isn't available from the server + if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNotAvailable || sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch) + { + LogRemoteCertificateFailure(certificate, chain, sslPolicyErrors); + return false; + } - // The certificate is invalid due to an invalid chain. If we have included custom certificates we can attempt to validate here - if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && CustomeRootCerts?.Count > 0) - { - // Load custom certificates - var x509Certificates = new List(); - foreach (var certFilename in CustomeRootCerts) - { - try - { - x509Certificates.Add(new X509Certificate2(certFilename)); - } - catch (System.Security.Cryptography.CryptographicException e) - { - Log(LogLevel.Error, tr => tr.Set($"Error loading certificate ({certFilename})", e)); - return false; - } - } + // The certificate is invalid due to an invalid chain. If we have included custom certificates we can attempt to validate here + if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && CustomeRootCerts?.Count > 0) + { + // Load custom certificates + var x509Certificates = new List(); + foreach (var certFilename in CustomeRootCerts) + { + try + { + x509Certificates.Add(new X509Certificate2(certFilename)); + } + catch (System.Security.Cryptography.CryptographicException e) + { + Log(LogLevel.Error, tr => tr.Set($"Error loading certificate ({certFilename})", e)); + return false; + } + } - var c = new X509Chain(); - try - { - c.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - c.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage; - c.ChainPolicy.ExtraStore.AddRange(x509Certificates.ToArray()); - // Check if the chain is valid - if (c.Build((X509Certificate2)certificate)) - { - Log(LogLevel.Info, tr => tr.Set($"Remote certificate validated successfull by custom cert chain")); - return true; - } - // The chain may not be valid, but if the only fault is an UntrustedRoot we can check if we have the custom root - else - { - if (c?.ChainStatus?.Length > 0 && c?.ChainStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) - { - var root = c.ChainElements[c.ChainElements.Count - 1]; - if(x509Certificates.Find(x509Certificate => x509Certificate.Thumbprint == root.Certificate.Thumbprint) != null) - { - Log(LogLevel.Info, tr => tr.Set($"Remote certificate validated successfull by custom cert chain and root")); - return true; - } - } - } - } - finally - { - c.Reset(); - } - } + var c = new X509Chain(); + try + { + c.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + c.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage; + c.ChainPolicy.ExtraStore.AddRange(x509Certificates.ToArray()); + // Check if the chain is valid + if (c.Build((X509Certificate2)certificate)) + { + Log(LogLevel.Info, tr => tr.Set($"Remote certificate validated successfull by custom cert chain")); + return true; + } + // The chain may not be valid, but if the only fault is an UntrustedRoot we can check if we have the custom root + else + { + if (c?.ChainStatus?.Length > 0 && c?.ChainStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) + { + var root = c.ChainElements[c.ChainElements.Count - 1]; + if (x509Certificates.Find(x509Certificate => x509Certificate.Thumbprint == root.Certificate.Thumbprint) != null) + { + Log(LogLevel.Info, tr => tr.Set($"Remote certificate validated successfull by custom cert chain and root")); + return true; + } + } + } + } + finally + { + c.Reset(); + } + } - LogRemoteCertificateFailure(certificate, chain, sslPolicyErrors); - return false; - } + LogRemoteCertificateFailure(certificate, chain, sslPolicyErrors); + return false; + } - /// - /// Polls the socket to determine the current connect state - /// - /// True if connected, false otherwise - public async Task CheckConnectStateAsync() - { - // TcpClient.Connected returns the state of the last send/recv operation. It doesn't accurately - // reflect the current socket state. To get the current state we need to send a packet - try - { - // Check if the state is disconnected based on the last operation - if (_client?.Connected != true || _clientStream == null) - return false; + /// + /// Polls the socket to determine the current connect state + /// + /// True if connected, false otherwise + public async Task CheckConnectStateAsync() + { + // TcpClient.Connected returns the state of the last send/recv operation. It doesn't accurately + // reflect the current socket state. To get the current state we need to send a packet + try + { + // Check if the state is disconnected based on the last operation + if (_client?.Connected != true || _clientStream == null) + return false; - // Otherwise the socket was connected the last time we used it, send 0 byte packet to see if it still is... - await _clientStream?.WriteAsync(new byte[1], 0, 0); - } - catch (System.IO.IOException e) - { - // 10035 == WSAEWOULDBLOCK - if (e.InnerException != null && e.InnerException is SocketException && (e.InnerException as SocketException).NativeErrorCode == 10035) - return true; + // Otherwise the socket was connected the last time we used it, send 0 byte packet to see if it still is... + if (_clientStream != null) + { + await _clientStream.WriteAsync(new byte[1], 0, 0); + } + } + catch (System.IO.IOException e) + { + // 10035 == WSAEWOULDBLOCK + if (e.InnerException != null && e.InnerException is SocketException && (e.InnerException as SocketException).NativeErrorCode == 10035) + return true; - return false; - } - catch(Exception) - { - return false; - } + return false; + } + catch (Exception) + { + return false; + } - return true; - } - + return true; + } - public async Task WriteRequestAsync(string msgString) - { - // Build request - var requestBytes = DirectEncoding.DIRECT.GetBytes(msgString); - try - { - // Send the request string to the IP client. - await _clientStream.WriteAsync(requestBytes, 0, requestBytes.Length); - } - catch (Exception e) - { - Close(); - throw new ConnectionException(e.Message, e.InnerException); - } - return true; + public async Task WriteRequestAsync(string msgString) + { + // Build request + var requestBytes = DirectEncoding.DIRECT.GetBytes(msgString); - } + try + { + // Send the request string to the IP client. + await _clientStream.WriteAsync(requestBytes, 0, requestBytes.Length); + } + catch (Exception e) + { + Close(); + throw new ConnectionException(e.Message, e.InnerException); + } + return true; - public async Task ReadResponseAsync(byte[] buffer, System.Threading.CancellationToken token) - { - try - { - // If we aren't using a CancellationToken we can just call ReadAsync directly - if (token == System.Threading.CancellationToken.None) - { - return await _clientStream.ReadAsync(buffer, 0, buffer.Length, token); - } - // Else we need to jump through some hoops as ReadAsync doesn't return when token is cancelled - else - { - var readTask = _clientStream.ReadAsync(buffer, 0, buffer.Length); - var timeoutTask = Task.Delay(int.MaxValue, token); // Task.Delay handles CancellationToken correctly - return await Task.Factory.ContinueWhenAny(new Task[] { readTask, timeoutTask }, (completedTask) => - { - if (completedTask == timeoutTask) //the timeout task was the first to complete - { - throw new TaskCanceledException(); - } - else //the readTask completed - { - return readTask.Result; - } - }); - } - } - catch (TaskCanceledException tc) - { - throw tc; - } - catch (Exception e) - { - Close(); - throw new ConnectionException(e.Message, e.InnerException); - } - } + } - public void Close() - { - _clientStream?.Close(); - _client?.Close(); - } - void Log(LogLevel level, Action traceAction) - { - // Check if this log level is enabled and client is subscribed to the OnLog event - if (OnLog == null || this.LogLevel >= level) - { - return; - } + public async Task ReadResponseAsync(byte[] buffer, System.Threading.CancellationToken token) + { + try + { + // If we aren't using a CancellationToken we can just call ReadAsync directly + if (token == System.Threading.CancellationToken.None) + { + return await _clientStream.ReadAsync(buffer, 0, buffer.Length, token); + } + // Else we need to jump through some hoops as ReadAsync doesn't return when token is cancelled + else + { + var readTask = _clientStream.ReadAsync(buffer, 0, buffer.Length); + var timeoutTask = Task.Delay(int.MaxValue, token); // Task.Delay handles CancellationToken correctly + return await Task.Factory.ContinueWhenAny(new Task[] { readTask, timeoutTask }, (completedTask) => + { + if (completedTask == timeoutTask) //the timeout task was the first to complete + { + throw new TaskCanceledException(); + } + else //the readTask completed + { + return readTask.Result; + } + }); + } + } + catch (TaskCanceledException tc) + { + throw tc; + } + catch (Exception e) + { + Close(); + throw new ConnectionException(e.Message, e.InnerException); + } + } - TraceRecord tr = new TraceRecord() { Level = level }; - traceAction(tr); - OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = tr.Message, Exception = tr.Exception }); - } + public void Close() + { + _clientStream?.Close(); + _client?.Close(); + } + void Log(LogLevel level, Action traceAction) + { + // Check if this log level is enabled and client is subscribed to the OnLog event + if (OnLog == null || this.LogLevel >= level) + { + return; + } - /// - /// Returns the connected state as of the last read or write operation. This does not necessarily represent - /// the current state of the connection. - /// To check the current socket state call - /// - public bool IsConnected => _client?.Connected ?? false; + TraceRecord tr = new TraceRecord() { Level = level }; + traceAction(tr); + OnLog?.Invoke(this, new LogEventArgs() { LogLevel = level, Message = tr.Message, Exception = tr.Exception }); + } - /// Defines the level of logging that should be passed back in the OnLog event. Default . See - public LogLevel LogLevel { get; set; } + /// + /// Returns the connected state as of the last read or write operation. This does not necessarily represent + /// the current state of the connection. + /// To check the current socket state call + /// + public bool IsConnected => _client?.Connected ?? false; - /// The log event to be called - public event EventHandler OnLog; + /// Defines the level of logging that should be passed back in the OnLog event. Default . See + public LogLevel LogLevel { get; set; } - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls + /// The log event to be called + public event EventHandler OnLog; - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - Close(); - } - disposedValue = true; - } - } + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - } - #endregion - } + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Close(); + } + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + } } diff --git a/IPInterface/Slave/EFTSlave.cs b/IPInterface/Slave/EFTSlave.cs new file mode 100644 index 0000000..7e996f4 --- /dev/null +++ b/IPInterface/Slave/EFTSlave.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PCEFTPOS.EFTClient.IPInterface.Slave +{ + public class EFTSlaveRequest : EFTRequest + { + public EFTSlaveRequest() : base(false, typeof(EFTSlaveResponse)) + { + + } + + /// + /// If != null, will be serialised into a slave command string and sent, otherwise the contents of will be sent + /// + public string RawCommand { get; set; } + + /// + /// If != null, will be serialised into a slave command string and sent, otherwise the contents of will be sent + /// + public SlaveCommandResponse Command { get; set; } = null; + } + + public class EFTSlaveResponse : EFTResponse + { + public EFTSlaveResponse() : base(typeof(EFTSlaveRequest)) + { + + } + + public bool Success { get { return ResponseCode == "00"; } } + public string ResponseCode { get; set; } + public string Response { get; set; } + } +} diff --git a/IPInterface/Model/SlaveHelper.cs b/IPInterface/Slave/SlaveHelper.cs similarity index 90% rename from IPInterface/Model/SlaveHelper.cs rename to IPInterface/Slave/SlaveHelper.cs index ab69193..a4a5f23 100644 --- a/IPInterface/Model/SlaveHelper.cs +++ b/IPInterface/Slave/SlaveHelper.cs @@ -10,7 +10,7 @@ public enum CommandCode NullCommand = ' ', SlaveMode = 'S', Status = 'N', DisplayMode = 'Z', Display = 'D', Print = 'P', MagneticCardRead = 'J', Audio = 'A', Key = 'K', Input = 'E', Storage = 'M', Connection = 'C', External = '+', SendReceive = 'X', ICC = 'I', } - public enum StatusType { Version = '0', DateTime = '1', SerialNumber = '2' } + public enum SlaveStatusType { Version = '0', DateTime = '1', SerialNumber = '2' } public enum LightStatus { NoChange = ' ', Off = '0', On = '1', Auto = 'A' } public enum TextAlignment { Left, Centre, Right } public enum CardReadOptions { Once = '1', Off = '0', Multiple = '2' } @@ -18,12 +18,61 @@ public enum KeyOptions { Once = '1', Off = '0', Multiple = '2' } public enum InputPromptMode { Default = ' ', AmountEntry = '$', Apha = 'A', Password = '*' } public enum KeyPressed { Enter = 'E', Cancel = 'C', Clear = 'B', ClearAll = 'D', Function = 'F', Alpha = 'A', Cheque = 'X', Savings = 'Y', Credit = 'Z', F0 = 'a', F1 = 'b', F2 = 'c', F3 = 'd', F4 = 'e', F5 = 'f', F6 = 'g', F7 = 'h', F8 = 'i', F9 = 'j', Zero = '0', One = '1', Two = '2', Three = '3', Four = '4', Five = '5', Six = '6', Seven = '7', Eight = '8', Nine = '9' } + public enum SlaveMode { Exit = '0', Enter = '1' } + public enum ComPortSpeed { NoChange = ' ', Baud9600 = '0', Baud19200 = '1', Baud38400 = '2', Baud115200 = '3' } + [Flags] public enum KeyMask { Cancel = 0x01, Enter = 0x02, Clear = 0x04, NumberKeys = 0x08, FunctionKeys = 0x10, AccountKeys = 0x20, Function = 0x40, OtherKeys = 0x80 } [Flags] public enum TrackFlags { Track1 = 0x01, Track2 = 0x02, Track3 = 0x04 } - public class SlaveHelper + /// + /// Base class for all slave request commands + /// + public class SlaveCommandRequest + { + } + + /// + /// Enter/exit slave mode + /// + public class SlaveCommandRequestMode : SlaveCommandRequest + { + public SlaveMode SlaveMode { get; set; } = SlaveMode.Exit; + + /// + /// Seconds to stay in slave mode(‘000’ is infinite). Maximum = 999 + /// + int _timeout = 60; + public int Timeout + { + get + { + return _timeout; + } + set + { + if (value < 0) + _timeout = 0; + else if (value > 999) + _timeout = 999; + else + _timeout = value; + } + } + + public ComPortSpeed ComPortSpeed { get; set; } = ComPortSpeed.NoChange; + } + + public class SlaveCommandRequestStatus: SlaveCommandRequest + { + } + + + + + + public class SlaveHelper { public static SlaveCommandResponse ResponseHelper( EFTSlaveResponse Response ) { @@ -122,8 +171,7 @@ public class SlaveCommandBuilder char deviceCode = '1'; #region Slave Mode - - public void AddEnterSlaveModeRequest() + public void AddEnterSlaveModeRequest() { AddEnterSlaveModeRequest( 0 ); } diff --git a/IPInterface/Util/Extensions.cs b/IPInterface/Util/Extensions.cs index 0040299..0c560cb 100644 --- a/IPInterface/Util/Extensions.cs +++ b/IPInterface/Util/Extensions.cs @@ -18,6 +18,7 @@ public static string ToApplicationString(this TerminalApplication v) return "01"; case TerminalApplication.Loyalty: case TerminalApplication.PrePaidCard: + case TerminalApplication.ETS: return "02"; // PCEFTCSA case TerminalApplication.GiftCard: return "03"; diff --git a/IPInterface/Util/MessageParser.cs b/IPInterface/Util/MessageParser.cs index 9e05a19..c27bc62 100644 --- a/IPInterface/Util/MessageParser.cs +++ b/IPInterface/Util/MessageParser.cs @@ -1,1246 +1,1302 @@ -using System; +using PCEFTPOS.EFTClient.IPInterface.Slave; +using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization.Json; using System.Text; -using System.Linq; namespace PCEFTPOS.EFTClient.IPInterface { - interface IMessageParser - { - EFTResponse StringToEFTResponse(string msg); - EFTResponse XMLStringToEFTResponse(string msg); - string EFTRequestToString(EFTRequest eftRequest); - string EFTRequestToXMLString(EFTRequest eftRequest); - } - - public class MessageParser: IMessageParser - { - ReceiptType lastReceiptType; - - enum IPClientResponseType - { - Logon = 'G', Transaction = 'M', QueryCard = 'J', Configure = '1', ControlPanel = '5', SetDialog = '2', Settlement = 'P', - DuplicateReceipt = 'C', GetLastTransaction = 'N', Status = 'K', Receipt = '3', Display = 'S', GenericPOSCommand = 'X', PINRequest = 'W', - ChequeAuth = 'H', SendKey = 'Y', ClientList = 'Q', CloudLogon = 'A' - } - - #region StringToEFTResponse - /// Parses a string to an EFTResponse message - /// string to parse - /// An EFTResponse message - /// An ArgumentException is thrown if the contents of msg is invalid - public EFTResponse StringToEFTResponse(string msg) - { - if (msg?.Length < 1) - { - throw new ArgumentException("msg is null or zero length", nameof(msg)); - } - - EFTResponse eftResponse = null; - switch ((IPClientResponseType)msg[0]) - { - case IPClientResponseType.Display: - eftResponse = ParseDisplayResponse(msg); - break; - case IPClientResponseType.Receipt: - eftResponse = ParseReceiptResponse(msg); - break; - case IPClientResponseType.Logon: - eftResponse = ParseEFTLogonResponse(msg); - break; - case IPClientResponseType.Transaction: - eftResponse = ParseEFTTransactionResponse(msg); - break; - case IPClientResponseType.SetDialog: - eftResponse = ParseSetDialogResponse(msg); - break; - case IPClientResponseType.GetLastTransaction: - eftResponse = ParseEFTGetLastTransactionResponse(msg); - break; - case IPClientResponseType.DuplicateReceipt: - eftResponse = ParseEFTReprintReceiptResponse(msg); - break; - case IPClientResponseType.ControlPanel: - eftResponse = ParseControlPanelResponse(msg); - break; - case IPClientResponseType.Settlement: - eftResponse = ParseEFTSettlementResponse(msg); - break; - case IPClientResponseType.Status: - eftResponse = ParseEFTStatusResponse(msg); - break; - case IPClientResponseType.ChequeAuth: - eftResponse = ParseChequeAuthResponse(msg); - break; - case IPClientResponseType.QueryCard: - eftResponse = ParseQueryCardResponse(msg); - break; - case IPClientResponseType.GenericPOSCommand: - eftResponse = ParseGenericPOSCommandResponse(msg); - break; - case IPClientResponseType.Configure: - eftResponse = ParseConfigMerchantResponse(msg); - break; - case IPClientResponseType.CloudLogon: - eftResponse = ParseCloudLogonResponse(msg); - break; - - default: - throw new ArgumentException($"Unknown message type: {msg}", nameof(msg)); - } - - return eftResponse; - } - - T TryParse(string input, int length, ref int index) - { - return TryParse(input, length, ref index, ""); - } - T TryParse(string input, int length, ref int index, string format) - { - T result = default(T); - - if (input.Length - index >= length) - { - if (result is bool && length == 1) - { - result = (T)Convert.ChangeType((input[index] == '1' || input[index] == 'Y'), typeof(T)); - index += length; - } - else - { - object data = input.Substring(index, length); - try - { - if (result is Enum && length == 1) - result = (T)Enum.ToObject(typeof(T), ((string)data)[0]); - else if (result is DateTime && format.Length > 1) - result = (T)(object)DateTime.ParseExact((string)data, format, null); - else - result = (T)Convert.ChangeType(data, typeof(T)); - } - catch - { - var idx = index; - //Log(LogLevel.Error, tr => tr.Set($"Unable to parse field. Input={input}, Index={idx}, Length={length}")); - } - finally - { - index += length; - } - } - } - else - index = length; - - return result; - } - - EFTResponse ParseEFTTransactionResponse(string msg) - { - var index = 1; - - var r = new EFTTransactionResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - r.Merchant = TryParse(msg, 2, ref index); - r.TxnType = TryParse(msg, 1, ref index); - r.CardAccountType = r.CardAccountType.FromString(TryParse(msg, 7, ref index)); - r.AmtCash = TryParse(msg, 9, ref index) / 100; - r.AmtPurchase = TryParse(msg, 9, ref index) / 100; - r.AmtTip = TryParse(msg, 9, ref index) / 100; - r.AuthCode = TryParse(msg, 6, ref index); - r.TxnRef = TryParse(msg, 16, ref index); - r.Stan= TryParse(msg, 6, ref index); - r.Caid = TryParse(msg, 15, ref index); - r.Catid = TryParse(msg, 8, ref index); - r.DateExpiry = TryParse(msg, 4, ref index); - r.SettlementDate = TryParse(msg, 4, ref index, "ddMM"); - r.Date = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); - r.CardType = TryParse(msg, 20, ref index); - r.Pan = TryParse(msg, 20, ref index); - r.Track2 = TryParse(msg, 40, ref index); - r.RRN = TryParse(msg, 12, ref index); - r.CardName = TryParse(msg, 2, ref index); - r.TxnFlags = new TxnFlags(TryParse(msg, 8, ref index).ToCharArray()); - r.BalanceReceived = TryParse(msg, 1, ref index); - r.AvailableBalance = TryParse(msg, 9, ref index) / 100; - r.ClearedFundsBalance = TryParse(msg, 9, ref index) / 100; - r.PurchaseAnalysisData = new PadField(TryParse(msg, msg.Length - index, ref index)); - - return r; - } - EFTResponse ParseEFTGetLastTransactionResponse(string msg) - { - var index = 1; - - var r = new EFTGetLastTransactionResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.LastTransactionSuccess = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - r.Merchant = TryParse(msg, 2, ref index); - - if (char.IsLower(msg[index])) - { - r.IsTrainingMode = true; - msg = msg.Substring(0, index) + char.ToUpper(msg[index]) + msg.Substring(index + 1); - } - r.TxnType = TryParse(msg, 1, ref index); - string accountType = TryParse(msg, 7, ref index); - if (accountType == "Credit ") r.CardAccountType = AccountType.Credit; - else if (accountType == "Savings") r.CardAccountType = AccountType.Savings; - else if (accountType == "Cheque ") r.CardAccountType = AccountType.Cheque; - else r.CardAccountType = AccountType.Default; - r.AmtCash = TryParse(msg, 9, ref index) / 100; - r.AmtPurchase = TryParse(msg, 9, ref index) / 100; - r.AmtTip = TryParse(msg, 9, ref index) / 100; - r.AuthCode = TryParse(msg, 6, ref index); - r.TxnRef = TryParse(msg, 16, ref index); - r.Stan = TryParse(msg, 6, ref index); - r.Caid = TryParse(msg, 15, ref index); - r.Catid = TryParse(msg, 8, ref index); - r.DateExpiry = TryParse(msg, 4, ref index); - r.SettlementDate = TryParse(msg, 4, ref index, "ddMM"); - r.BankDateTime = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); - r.CardType = TryParse(msg, 20, ref index); - r.Pan = TryParse(msg, 20, ref index); - r.Track2 = TryParse(msg, 40, ref index); - r.RRN = TryParse(msg, 12, ref index); - r.CardName = TryParse(msg, 2, ref index); - string txnFlags = TryParse(msg, 8, ref index); - r.TxnFlags = new TxnFlags(txnFlags.ToCharArray()); - r.BalanceReceived = TryParse(msg, 1, ref index); - r.AvailableBalance = TryParse(msg, 9, ref index) / 100; - int padLength = TryParse(msg, 3, ref index); - r.PurchaseAnalysisData = new PadField(TryParse(msg, padLength, ref index)); - - return r; - } - EFTResponse ParseSetDialogResponse(string msg) - { - var index = 1; - - var r = new SetDialogResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - - return r; - } - EFTResponse ParseEFTLogonResponse(string msg) - { - var index = 1; - - var r = new EFTLogonResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - if (msg.Length > 25) - { - r.Catid = TryParse(msg, 8, ref index); - r.Caid = TryParse(msg, 15, ref index); - r.Date = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); - r.Stan = TryParse(msg, 6, ref index); - r.PinPadVersion = TryParse(msg, 16, ref index); - r.PurchaseAnalysisData = new PadField(TryParse(msg, msg.Length - index, ref index)); - } - return r; - } - EFTResponse ParseDisplayResponse(string msg) - { - int index = 1; - - var r = new EFTDisplayResponse(); - index++; // Skip sub code. - r.NumberOfLines = TryParse(msg, 2, ref index); - r.LineLength = TryParse(msg, 2, ref index); - for (int i = 0; i < r.NumberOfLines; i++) - r.DisplayText[i] = TryParse(msg, r.LineLength, ref index); - r.CancelKeyFlag = TryParse(msg, 1, ref index); - r.AcceptYesKeyFlag = TryParse(msg, 1, ref index); - r.DeclineNoKeyFlag = TryParse(msg, 1, ref index); - r.AuthoriseKeyFlag = TryParse(msg, 1, ref index); - r.InputType = TryParse(msg, 1, ref index); - r.OKKeyFlag = TryParse(msg, 1, ref index); - index += 2; - r.GraphicCode = TryParse(msg, 1, ref index); - int padLength = TryParse(msg, 3, ref index); - r.PurchaseAnalysisData = new PadField(TryParse(msg, padLength, ref index)); - - return r; - } - EFTResponse ParseReceiptResponse(string msg) - { - int index = 1; - - var r = new EFTReceiptResponse - { - Type = TryParse(msg, 1, ref index) - }; - - if (r.Type != ReceiptType.ReceiptText) - { - lastReceiptType = r.Type; - r.IsPrePrint = true; - } - else - { - List receiptLines = new List(); - bool done = false; - while (!done) - { - int lineLength = msg.Substring(index).IndexOf("\r\n"); - if (lineLength > 0) - { - receiptLines.Add(msg.Substring(index, lineLength)); - index += lineLength + 2; - if (index >= msg.Length) - done = true; - } - else - done = true; - } - - r.ReceiptText = receiptLines.ToArray(); - r.Type = lastReceiptType; - } - - return r; - } - EFTResponse ParseControlPanelResponse(string msg) - { - int index = 1; - - var r = new EFTControlPanelResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - return r; - } - EFTResponse ParseEFTReprintReceiptResponse(string msg) - { - int index = 1; - - var r = new EFTReprintReceiptResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - List receiptLines = new List(); - bool done = false; - while (!done) - { - int lineLength = msg.Substring(index).IndexOf("\r\n"); - if (lineLength > 0) - { - receiptLines.Add(msg.Substring(index, lineLength)); - index += lineLength + 2; - if (index >= msg.Length) - done = true; - } - else - done = true; - } - - r.ReceiptText = receiptLines.ToArray(); - - return r; - } - EFTResponse ParseEFTSettlementResponse(string msg) - { - var index = 1; - - var r = new EFTSettlementResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - if (msg.Length > 25) - { - r.SettlementData = msg.Substring(index); - - //int cardCount = int.Parse( response.Substring( index, 9 ) ); index += 9; - //for( int i = 0; i < cardCount; i++ ) - //{ - // int cardTotalsDataLength = int.Parse( response.Substring( index, 3 ) ); index += 3; - // if( cardTotalsDataLength >= 69 ) - // { - // SettlementCardTotals cardTotals = new SettlementCardTotals(); - // cardTotals.CardName = response.Substring( index, 20 ); index += 20; - // try { cardTotals.PurchaseAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { cardTotals.PurchaseAmount = 0; } - // finally { index += 9; } - // try { cardTotals.PurchaseCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { cardTotals.PurchaseCount = 0; } - // finally { index += 3; } - // try { cardTotals.CashOutAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { cardTotals.CashOutAmount = 0; } - // finally { index += 9; } - // try { cardTotals.CashOutCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { cardTotals.CashOutCount = 0; } - // finally { index += 3; } - // try { cardTotals.RefundAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { cardTotals.RefundAmount = 0; } - // finally { index += 9; } - // try { cardTotals.RefundCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { cardTotals.RefundCount = 0; } - // finally { index += 3; } - // try { cardTotals.TotalAmount = decimal.Parse( response.Substring( index, 10 ), System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite ) / 100; } - // catch { cardTotals.TotalAmount = 0; } - // finally { index += 9; } - // try { cardTotals.TotalCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { cardTotals.TotalCount = 0; } - // finally { index += 3; } - // displayResponse.SettlementCardData.Add( cardTotals ); - // index += cardTotalsDataLength - 69; - // } - //} - - //int totalsDataLength = int.Parse( response.Substring( index, 3 ) ); index += 3; - //if( totalsDataLength >= 69 ) - //{ - // SettlementTotals settleTotals = new SettlementTotals(); - // settleTotals.TotalsDescription = response.Substring( index, 20 ); index += 20; - // try { settleTotals.PurchaseAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { settleTotals.PurchaseAmount = 0; } - // finally { index += 9; } - // try { settleTotals.PurchaseCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { settleTotals.PurchaseCount = 0; } - // finally { index += 3; } - // try { settleTotals.CashOutAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { settleTotals.CashOutAmount = 0; } - // finally { index += 9; } - // try { settleTotals.CashOutCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { settleTotals.CashOutCount = 0; } - // finally { index += 3; } - // try { settleTotals.RefundAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } - // catch { settleTotals.RefundAmount = 0; } - // finally { index += 9; } - // try { settleTotals.RefundCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { settleTotals.RefundCount = 0; } - // finally { index += 3; } - // try { settleTotals.TotalAmount = decimal.Parse( response.Substring( index, 10 ), System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite ) / 100; } - // catch { settleTotals.TotalAmount = 0; } - // finally { index += 9; } - // try { settleTotals.TotalCount = int.Parse( response.Substring( index, 3 ) ); } - // catch { settleTotals.TotalCount = 0; } - // finally { index += 3; } - // displayResponse.TotalsData = settleTotals; - // index += totalsDataLength - 69; - //} - - //int padLength; - //try - //{ - // padLength = int.Parse( response.Substring( index, 3 ) ); - // displayResponse.PurchaseAnalysisData = response.Substring( index, padLength ); index += padLength; - //} - //catch { padLength = 0; } - //finally { index += 3; } - } - - return r; - } - EFTResponse ParseQueryCardResponse(string msg) - { - int index = 1; - - var r = new EFTQueryCardResponse - { - AccountType = (AccountType)msg[index++], - Success = TryParse(msg, 1, ref index), - ResponseCode = TryParse(msg, 2, ref index), - ResponseText = TryParse(msg, 20, ref index) - }; - - if (msg.Length > 25) - { - r.Track2 = msg.Substring(index, 40); index += 40; - string track1or3 = msg.Substring(index, 80); index += 80; - char trackFlag = msg[index++]; - switch (trackFlag) - { - case '1': - r.TrackFlags = TrackFlags.Track1; - r.Track1 = track1or3; - break; - case '2': - r.TrackFlags = TrackFlags.Track2; - break; - case '3': - r.TrackFlags = TrackFlags.Track1 | TrackFlags.Track2; - r.Track1 = track1or3; - break; - case '4': - r.TrackFlags = TrackFlags.Track3; - r.Track3 = track1or3; - break; - case '6': - r.TrackFlags = TrackFlags.Track2 | TrackFlags.Track3; - r.Track3 = track1or3; - break; - } - - r.CardName = int.Parse(msg.Substring(index, 2)); index += 2; - - int padLength; - try - { - padLength = int.Parse(msg.Substring(index, 3)); - r.PurchaseAnalysisData = new PadField(msg.Substring(index, padLength)); - index += padLength; - } - catch { padLength = 0; } - finally { index += 3; } - } - - return r; - } - EFTResponse ParseEFTConfigureMerchantResponse(string msg) - { - int index = 1; - - var r = new EFTConfigureMerchantResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - return r; - } - EFTResponse ParseEFTStatusResponse(string msg) - { - int index = 1; - - var r = new EFTStatusResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - if (index >= msg.Length) return r; - r.Merchant = TryParse(msg, 2, ref index); - r.AIIC = TryParse(msg, 11, ref index); - r.NII = TryParse(msg, 3, ref index); - r.Caid = TryParse(msg, 15, ref index); - r.Catid = TryParse(msg, 8, ref index); - r.Timeout = TryParse(msg, 3, ref index); - r.LoggedOn = TryParse(msg, 1, ref index); - r.PinPadSerialNumber = TryParse(msg, 16, ref index); - r.PinPadVersion = TryParse(msg, 16, ref index); - r.BankDescription = TryParse(msg, 32, ref index); - int padLength = TryParse(msg, 3, ref index); - if (msg.Length - index < padLength) - return r; - r.SAFCount = TryParse(msg, 4, ref index); - r.NetworkType = TryParse(msg, 1, ref index); - r.HardwareSerial = TryParse(msg, 16, ref index); - r.RetailerName = TryParse(msg, 40, ref index); - r.OptionsFlags = ParseStatusOptionFlags(msg.Substring(index, 32).ToCharArray()); index += 32; - r.SAFCreditLimit = TryParse(msg, 9, ref index) / 100; - r.SAFDebitLimit = TryParse(msg, 9, ref index) / 100; - r.MaxSAF = TryParse(msg, 3, ref index); - r.KeyHandlingScheme = ParseKeyHandlingType(msg[index++]); - r.CashoutLimit = TryParse(msg, 9, ref index) / 100; - r.RefundLimit = TryParse(msg, 9, ref index) / 100; - r.CPATVersion = TryParse(msg, 6, ref index); - r.NameTableVersion = TryParse(msg, 6, ref index); - r.TerminalCommsType = ParseTerminalCommsType(msg[index++]); - r.CardMisreadCount = TryParse(msg, 6, ref index); - r.TotalMemoryInTerminal = TryParse(msg, 4, ref index); - r.FreeMemoryInTerminal = TryParse(msg, 4, ref index); - r.EFTTerminalType = ParseEFTTerminalType(msg.Substring(index, 4)); index += 4; - r.NumAppsInTerminal = TryParse(msg, 2, ref index); - r.NumLinesOnDisplay = TryParse(msg, 2, ref index); - r.HardwareInceptionDate = TryParse(msg, 6, ref index, "ddMMyy"); - - return r; - } - - TerminalCommsType ParseTerminalCommsType(char CommsType) - { - TerminalCommsType commsType = TerminalCommsType.Unknown; - - if (CommsType == '0') commsType = TerminalCommsType.Cable; - else if (CommsType == '1') commsType = TerminalCommsType.Infrared; - - return commsType; - } - KeyHandlingType ParseKeyHandlingType(char KeyHandlingScheme) - { - KeyHandlingType keyHandlingType = KeyHandlingType.Unknown; - - if (KeyHandlingScheme == '0') keyHandlingType = KeyHandlingType.SingleDES; - else if (KeyHandlingScheme == '1') keyHandlingType = KeyHandlingType.TripleDES; - - return keyHandlingType; - } - EFTTerminalType ParseEFTTerminalType(string TerminalType) - { - EFTTerminalType terminalType = EFTTerminalType.Unknown; - - if (TerminalType == "0062") terminalType = EFTTerminalType.IngenicoNPT710; - else if (TerminalType == "0069") terminalType = EFTTerminalType.IngenicoPX328; - else if (TerminalType == "7010") terminalType = EFTTerminalType.Ingenicoi3070; - else if (TerminalType == "5110") terminalType = EFTTerminalType.Ingenicoi5110; - - return terminalType; - } - PINPadOptionFlags ParseStatusOptionFlags(char[] Flags) - { - PINPadOptionFlags flags = 0; - int index = 0; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Tipping; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.PreAuth; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Completions; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.CashOut; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Refund; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Balance; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Deposit; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Voucher; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.MOTO; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.AutoCompletion; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.EFB; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.EMV; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Training; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Withdrawal; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.Transfer; - if (Flags[index++] == '1') flags |= PINPadOptionFlags.StartCash; - return flags; - } - EFTResponse ParseChequeAuthResponse(string msg) - { - int index = 1; - - var r = new EFTChequeAuthResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - if (msg.Length > 25) - { - r.Merchant = TryParse(msg, 2, ref index); - try { r.Amount = decimal.Parse(msg.Substring(index, 9)) / 100; } - catch { r.Amount = 0; } - finally { index += 9; } - try { r.AuthNumber = int.Parse(msg.Substring(index, 6)); } - catch { r.AuthNumber = 0; } - finally { index += 6; } - r.ReferenceNumber = msg.Substring(index, 12); index += 12; - } - - return r; - } - EFTResponse ParseGenericPOSCommandResponse(string msg) - { - // Validate response length - if(string.IsNullOrEmpty(msg)) - { - return null; - } - - int index = 1; - - CommandType commandType = (CommandType)msg[index++]; - switch (commandType) - { - case CommandType.GetPassword: - var pwdResponse = new EFTGetPasswordResponse - { - ResponseCode = msg.Substring(index, 2) - }; - index += 2; - pwdResponse.Success = pwdResponse.ResponseCode == "00"; - pwdResponse.ResponseText = msg.Substring(index, 20); index += 20; - - if (msg.Length > 25) - { - int pwdLength = 0; - try { pwdLength = int.Parse(msg.Substring(index, 2)); } - finally { index += 2; } - pwdResponse.Password = msg.Substring(index, pwdLength); index += pwdLength; - } - return pwdResponse; - case CommandType.Slave: - var slaveResponse = new EFTSlaveResponse - { - ResponseCode = msg.Substring(index, 2) - }; - index += 2; - slaveResponse.Response = msg.Substring(index); - return slaveResponse; - - case CommandType.PayAtTable: - var patResponse = new EFTPayAtTableResponse(); - index = 22; - - var headerLength = msg.Substring(index, 6); index += 6; - int len = 0; - int.TryParse(headerLength, out len); - - patResponse.Header = msg.Substring(index, len); index += len; - patResponse.Content = msg.Substring(index, msg.Length - index); - - return patResponse; - - case CommandType.BasketData: - return ParseBasketDataResponse(msg); - } - - return null; - } - EFTResponse ParseConfigMerchantResponse(string msg) - { - int index = 1; - - var r = new EFTConfigureMerchantResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - return r; - } - EFTResponse ParseCloudLogonResponse(string msg) - { - int index = 1; - - var r = new EFTCloudLogonResponse(); - index++; // Skip sub code. - r.Success = TryParse(msg, 1, ref index); - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - return r; - } - - EFTResponse ParseBasketDataResponse(string msg) - { - int index = 1; // msg[0] is the command code - - var r = new EFTBasketDataResponse(); - index++; // Skip sub code. - r.ResponseCode = TryParse(msg, 2, ref index); - r.ResponseText = TryParse(msg, 20, ref index); - - r.Success = r.ResponseCode == "00"; - - return r; - } - - - /// - /// Convert a PC-EFTPOS message (e.g. #0010K0000) to a human readable debug string - /// - public static string MsgToDebugString(string msg) - { - if((msg?.Length ?? 0) < 2) - { - return $"Unable to parse msg{Environment.NewLine}ContentLength={msg?.Length ?? 0}{Environment.NewLine}Content={msg}"; - } - - // Remove the header if one exists - if (msg[0] == '#') - { - if (msg.Length < 7) - return $"Unable to parse msg{Environment.NewLine}ContentLength={msg?.Length ?? 0}{Environment.NewLine}Content={msg}"; - - msg = msg.Substring(5); - } - - var messageParser = new MessageParser(); - try - { - var eftResponse = messageParser.StringToEFTResponse(msg); - return PrintProperties(eftResponse); - } - catch(ArgumentException) - { - // Try StringToEFTRequest() - return $"Unable to parse msg{Environment.NewLine}ContentLength={msg.Length}{Environment.NewLine}Content={msg}"; - } - } - - static string PrintProperties(object obj) - { - if (obj == null) - return "NULL"; - - var sb = new StringBuilder(); - - var objType = obj.GetType(); - var properties = objType.GetProperties(); - foreach (var p in properties) - { - sb.AppendFormat("{1}: {2}", p.Name, p.ToString()); - } - - return sb.ToString(); - } - #endregion - - #region EFTResponseToString - - public string EFTRequestToString(EFTRequest eftRequest) - { - // Build the request string. - var request = BuildRequest(eftRequest); - var len = request.Length + 5; - request.Insert(0, '#'); - request.Insert(1, len.PadLeft(4)); - return request.ToString(); - } - - StringBuilder BuildRequest(EFTRequest eftRequest) - { - if (eftRequest is EFTLogonRequest) - { - return BuildEFTLogonRequest((EFTLogonRequest)eftRequest); - } - - if (eftRequest is EFTTransactionRequest) - { - return BuildEFTTransactionRequest((EFTTransactionRequest)eftRequest); - } - - if (eftRequest is EFTGetLastTransactionRequest) - { - return BuildEFTGetLastTransactionRequest((EFTGetLastTransactionRequest)eftRequest); - } - - if (eftRequest is EFTReprintReceiptRequest) - { - return BuildEFTReprintReceiptRequest((EFTReprintReceiptRequest)eftRequest); - } - - if (eftRequest is SetDialogRequest) - { - return BuildSetDialogRequest((SetDialogRequest)eftRequest); - } - - if (eftRequest is ControlPanelRequest) - { - return BuildControlPanelRequest((ControlPanelRequest)eftRequest); - } - - if (eftRequest is EFTSettlementRequest) - { - return BuildSettlementRequest((EFTSettlementRequest)eftRequest); - } - - if (eftRequest is EFTStatusRequest) - { - return BuildStatusRequest((EFTStatusRequest)eftRequest); - } - - if (eftRequest is ChequeAuthRequest) - { - return BuildChequeAuthRequest((ChequeAuthRequest)eftRequest); - } - - if (eftRequest is QueryCardRequest) - { - return BuildQueryCardRequest((QueryCardRequest)eftRequest); - } - - if (eftRequest is EFTGetPasswordRequest) - { - return BuildGetPasswordRequest((EFTGetPasswordRequest)eftRequest); - } - - if (eftRequest is EFTSlaveRequest) - { - return BuildSlaveRequest((EFTSlaveRequest)eftRequest); - } - - if (eftRequest is EFTConfigureMerchantRequest) - { - return BuildConfigMerchantRequest((EFTConfigureMerchantRequest)eftRequest); - } - - if (eftRequest is EFTCloudLogonRequest) - { - return BuildCloudLogonRequest((EFTCloudLogonRequest)eftRequest); - } - - if (eftRequest is EFTClientListRequest) - { - return BuildGetClientListRequest((EFTClientListRequest)eftRequest); - } - - if (eftRequest is EFTSendKeyRequest) - { - return BuildSendKeyRequest((EFTSendKeyRequest)eftRequest); - } - - if (eftRequest is EFTReceiptRequest) - { - return BuildReceiptRequest((EFTReceiptRequest)eftRequest); - } - - if (eftRequest is EFTPayAtTableRequest) - { - return BuildPayAtTableRequest((EFTPayAtTableRequest)eftRequest); - } - - if (eftRequest is EFTBasketDataRequest) - { - return BuildBasketDataRequest((EFTBasketDataRequest)eftRequest); - } - - throw new Exception("Unknown EFTRequest type."); - } - StringBuilder BuildEFTTransactionRequest(EFTTransactionRequest v) - { - var r = new StringBuilder(); - r.Append("M"); - r.Append("0"); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append((char)v.TxnType); - r.Append(v.TrainingMode ? '1' : '0'); - r.Append(v.EnableTip ? '1' : '0'); - r.Append(v.AmtCash.PadLeftAsInt(9)); - r.Append(v.AmtPurchase.PadLeftAsInt(9)); - r.Append(v.AuthCode.PadLeft(6)); - r.Append(v.TxnRef.PadRightAndCut(16)); - r.Append((char)v.ReceiptPrintMode); - r.Append((char)v.ReceiptCutMode); - r.Append((char)v.PanSource); - r.Append(v.Pan.PadRightAndCut(20)); - r.Append(v.DateExpiry.PadRightAndCut(4)); - r.Append(v.Track2.PadRightAndCut(40)); - r.Append((char)v.CardAccountType); - r.Append(v.Application.ToApplicationString()); - r.Append(v.RRN.PadRightAndCut(12)); - r.Append(v.CurrencyCode.PadRightAndCut(3)); - r.Append((char)v.OriginalTxnType); - r.Append(v.Date != null ? v.Date.Value.ToString("ddMMyy") : " "); - r.Append(v.Time != null ? v.Time.Value.ToString("HHmmss") : " "); - r.Append(" ".PadRightAndCut(8)); // Reserved - r.Append(v.PurchaseAnalysisData.GetAsString(true)); - - return r; - } - StringBuilder BuildEFTLogonRequest(EFTLogonRequest v) - { - var r = new StringBuilder(); - r.Append("G"); - r.Append((char)v.LogonType); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append((char)v.ReceiptPrintMode); - r.Append((char)v.ReceiptCutMode); - r.Append(v.Application.ToApplicationString()); - r.Append(v.PurchaseAnalysisData.GetAsString(true)); - return r; - } - StringBuilder BuildEFTReprintReceiptRequest(EFTReprintReceiptRequest v) - { - var r = new StringBuilder(); - r.Append("C"); - r.Append((char)v.ReprintType); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append((char)v.ReceiptCutMode); - r.Append((char)v.ReceiptPrintMode); - r.Append(v.Application.ToApplicationString()); - return r; - } - StringBuilder BuildEFTGetLastTransactionRequest(EFTGetLastTransactionRequest v) - { - var r = new StringBuilder(); - r.Append("N"); - r.Append("0"); - r.Append(v.Application.ToApplicationString()); - return r; - } - StringBuilder BuildSetDialogRequest(SetDialogRequest v) - { - var r = new StringBuilder(); - r.Append("2"); - r.Append(v.DisableDisplayEvents ? '5' : ' '); - r.Append((char)v.DialogType); - r.Append(v.DialogX.PadLeft(4)); - r.Append(v.DialogY.PadLeft(4)); - r.Append(v.DialogPosition.ToString().PadRightAndCut(12)); - r.Append(v.EnableTopmost ? '1' : '0'); - r.Append(v.DialogTitle.PadRightAndCut(32)); - return r; - } - StringBuilder BuildControlPanelRequest(ControlPanelRequest v) - { - var r = new StringBuilder(); - r.Append("5"); // ControlPanel - r.Append((char)v.ControlPanelType); - r.Append((char)v.ReceiptPrintMode); - r.Append((char)v.ReceiptCutMode); - r.Append((char)v.ReturnType); - return r; - } - StringBuilder BuildSettlementRequest(EFTSettlementRequest v) - { - var r = new StringBuilder(); - r.Append("P"); - r.Append((char)v.SettlementType); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append((char)v.ReceiptPrintMode); - r.Append((char)v.ReceiptCutMode); - r.Append(v.ResetTotals ? '1' : '0'); - r.Append(v.Application.ToApplicationString()); - r.Append(v.PurchaseAnalysisData.GetAsString(true)); - return r; - } - StringBuilder BuildQueryCardRequest(QueryCardRequest v) - { - var r = new StringBuilder(); - r.Append("J"); - r.Append((char)v.QueryCardType); - r.Append(v.Application.ToApplicationString()); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append(v.PurchaseAnalysisData.GetAsString(true)); - return r; - } - StringBuilder BuildConfigMerchantRequest(EFTConfigureMerchantRequest v) - { - var r = new StringBuilder(); - r.Append("10"); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append(v.AIIC.PadLeft(11)); - r.Append(v.NII.PadLeft(3)); - r.Append(v.Caid.PadRightAndCut(15)); - r.Append(v.Catid.PadRightAndCut(8)); - r.Append(v.Timeout.PadLeft(3)); - r.Append(v.Application.ToApplicationString()); - return r; - } - StringBuilder BuildStatusRequest(EFTStatusRequest v) - { - var r = new StringBuilder(); - r.Append("K"); - r.Append((char)v.StatusType); - r.Append(v.Merchant.PadRightAndCut(2)); - r.Append(v.Application.ToApplicationString()); - return r; - } - StringBuilder BuildChequeAuthRequest(ChequeAuthRequest v) - { - var r = new StringBuilder(); - r.Append("H0"); - r.Append(v.Application.ToApplicationString()); - r.Append(' '); - r.Append(v.BranchCode.PadRightAndCut(6)); - r.Append(v.AccountNumber.PadRightAndCut(14)); - r.Append(v.SerialNumber.PadRightAndCut(14)); - r.Append(v.Amount.PadLeftAsInt(9)); - r.Append((char)v.ChequeType); - r.Append(v.ReferenceNumber.PadRightAndCut(12)); - - return r; - } - - StringBuilder BuildGetPasswordRequest(EFTGetPasswordRequest v) - { - var r = new StringBuilder(); - r.Append("X"); - r.Append((char)CommandType.GetPassword); - r.Append(v.MinPasswordLength.PadLeft(2)); - r.Append(v.MaxPassworkLength.PadLeft(2)); - r.Append(v.Timeout.PadLeft(3)); - r.Append("0" + (char)v.PasswordDisplay); - return r; - } - - StringBuilder BuildSlaveRequest(EFTSlaveRequest v) - { - var r = new StringBuilder(); - r.Append("X"); - r.Append((char)CommandType.Slave); - r.Append(v.Command); - - return r; - } - - StringBuilder BuildGetClientListRequest(EFTClientListRequest v) - { - var r = new StringBuilder(); - r.Append("Q0"); - return r; - } - - StringBuilder BuildCloudLogonRequest(EFTCloudLogonRequest v) - { - var r = new StringBuilder(); - r.Append("A "); - r.Append(v.ClientID.PadRightAndCut(16)); - r.Append(v.Password.PadRightAndCut(16)); - r.Append(v.PairingCode.PadRightAndCut(16)); - return r; - } - - StringBuilder BuildSendKeyRequest(EFTSendKeyRequest v) - { - var r = new StringBuilder(); - r.Append("Y0"); - r.Append((char)v.Key); - if (v.Key == EFTPOSKey.Authorise && v.Data != null) - { - r.Append(v.Data.PadRightAndCut(20)); - } - - return r; - } - - StringBuilder BuildReceiptRequest(EFTReceiptRequest v) - { - return new StringBuilder("3 "); - } - - StringBuilder BuildPayAtTableRequest(EFTPayAtTableRequest request) - { - var r = new StringBuilder(); - r.Append("X"); - r.Append((char)CommandType.PayAtTable); - r.Append(request.Header); - r.Append(request.Content); - - return r; - } - - StringBuilder BuildBasketDataRequest(EFTBasketDataRequest request) - { - var jsonContent = "{}"; - - switch (request.Command) - { - case EFTBasketDataCommandCreate c: - // Serializer the Basket object to JSON - using (var ms = new System.IO.MemoryStream()) - { - // Would be better to use use Newtonsoft.Json here, but we don't want the dependency, so we are stuck with DataContractJsonSerializer - // We need to add the possible known types from c.Basket.Items to the "known types" of the serializer otherwise it throws an exception - var serializerSettings = new DataContractJsonSerializerSettings() - { - EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Never, - IgnoreExtensionDataObject = false, - SerializeReadOnlyTypes = true, - KnownTypes = (c?.Basket?.Items?.Count > 0) ? c.Basket.Items.Select(bi => bi.GetType()).Distinct() : new List() { typeof(EFTBasketItem) } - }; - var serializer = new DataContractJsonSerializer((c?.Basket != null) ? c.Basket.GetType() : typeof(EFTBasket), serializerSettings); - serializer.WriteObject(ms, c.Basket); - var json = ms.ToArray(); - ms.Close(); - jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); - } - break; - - case EFTBasketDataCommandAdd c: - // TODO: We can only have one item in an "add" command. Should we validate this request?? - - // Serializer the Basket object to JSON - using (var ms = new System.IO.MemoryStream()) - { - // Would be better to use use Newtonsoft.Json here, but we don't want the dependency, so we are stuck with DataContractJsonSerializer - // We need to add the possible known types from c.Basket.Items to the "known types" of the serializer otherwise it throws an exception - var serializerSettings = new DataContractJsonSerializerSettings() - { - EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Never, - IgnoreExtensionDataObject = false, - SerializeReadOnlyTypes = true, - KnownTypes = (c?.Basket?.Items?.Count > 0) ? c.Basket.Items.Select(bi => bi.GetType()).Distinct() : new List() { typeof(EFTBasketItem) } - }; - var serializer = new DataContractJsonSerializer((c?.Basket != null) ? c.Basket.GetType() : typeof(EFTBasket), serializerSettings); - serializer.WriteObject(ms, c.Basket); - var json = ms.ToArray(); - ms.Close(); - jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); - } - break; - - case EFTBasketDataCommandDelete c: - // Build our fake basket - var b = new EFTBasket() - { - Id = c.BasketId, - Items = new List() - { - new EFTBasketItem() - { - Id = c.BasketItemId - } - } - }; - - // Serializer the Basket object to JSON - using (var ms = new System.IO.MemoryStream()) - { - var serializer = new DataContractJsonSerializer(typeof(EFTBasket), new DataContractJsonSerializerSettings() { IgnoreExtensionDataObject = false, SerializeReadOnlyTypes = true }); - serializer.WriteObject(ms, b); - var json = ms.ToArray(); - ms.Close(); - jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); - } - break; - - case EFTBasketDataCommandRaw c: - jsonContent = c.BasketContent; - break; - } - - var r = new StringBuilder(); - r.Append("X"); - r.Append((char)CommandType.BasketData); - r.Append(jsonContent.Length.PadLeft(6)); - r.Append(jsonContent); - return r; - } - - - #endregion - - public EFTResponse XMLStringToEFTResponse(string msg) - { - EFTPosAsPinpadResponse response = null; - try - { - response = XMLSerializer.Deserialize(msg); - } - catch (Exception) - { - } - - return response; - } - - public string EFTRequestToXMLString(EFTRequest eftRequest) - { - switch (eftRequest) - { - case EFTPosAsPinpadRequest r: return EFTRequestToXMLString(r); - default: return string.Empty; - } - } - - private string EFTRequestToXMLString(EFTPosAsPinpadRequest eftRequest) - { - try - { - var response = XMLSerializer.Serialize(eftRequest); - return response.Insert(0, $"&{(response.Length + 7).ToString("000000")}"); - } - catch (Exception) - { - return string.Empty; - } - } - - - } + public interface IMessageParser + { + EFTResponse StringToEFTResponse(string msg); + EFTResponse XMLStringToEFTResponse(string msg); + string EFTRequestToString(EFTRequest eftRequest); + string EFTRequestToXMLString(EFTRequest eftRequest); + } + + public class DefaultMessageParser : IMessageParser + { + ReceiptType lastReceiptType; + + enum IPClientResponseType + { + Logon = 'G', Transaction = 'M', QueryCard = 'J', Configure = '1', ControlPanel = '5', SetDialog = '2', Settlement = 'P', + DuplicateReceipt = 'C', GetLastTransaction = 'N', Status = 'K', Receipt = '3', Display = 'S', GenericPOSCommand = 'X', PINRequest = 'W', + ChequeAuth = 'H', SendKey = 'Y', ClientList = 'Q', CloudLogon = 'A' + } + + #region StringToEFTResponse + /// Parses a string to an EFTResponse message + /// string to parse + /// An EFTResponse message + /// An ArgumentException is thrown if the contents of msg is invalid + public EFTResponse StringToEFTResponse(string msg) + { + if (msg?.Length < 1) + { + throw new ArgumentException("msg is null or zero length", nameof(msg)); + } + + EFTResponse eftResponse = null; + switch ((IPClientResponseType)msg[0]) + { + case IPClientResponseType.Display: + eftResponse = ParseDisplayResponse(msg); + break; + case IPClientResponseType.Receipt: + eftResponse = ParseReceiptResponse(msg); + break; + case IPClientResponseType.Logon: + eftResponse = ParseEFTLogonResponse(msg); + break; + case IPClientResponseType.Transaction: + eftResponse = ParseEFTTransactionResponse(msg); + break; + case IPClientResponseType.SetDialog: + eftResponse = ParseSetDialogResponse(msg); + break; + case IPClientResponseType.GetLastTransaction: + eftResponse = ParseEFTGetLastTransactionResponse(msg); + break; + case IPClientResponseType.DuplicateReceipt: + eftResponse = ParseEFTReprintReceiptResponse(msg); + break; + case IPClientResponseType.ControlPanel: + eftResponse = ParseControlPanelResponse(msg); + break; + case IPClientResponseType.Settlement: + eftResponse = ParseEFTSettlementResponse(msg); + break; + case IPClientResponseType.Status: + eftResponse = ParseEFTStatusResponse(msg); + break; + case IPClientResponseType.ChequeAuth: + eftResponse = ParseChequeAuthResponse(msg); + break; + case IPClientResponseType.QueryCard: + eftResponse = ParseQueryCardResponse(msg); + break; + case IPClientResponseType.GenericPOSCommand: + eftResponse = ParseGenericPOSCommandResponse(msg); + break; + case IPClientResponseType.Configure: + eftResponse = ParseConfigMerchantResponse(msg); + break; + case IPClientResponseType.CloudLogon: + eftResponse = ParseCloudLogonResponse(msg); + break; + case IPClientResponseType.ClientList: + eftResponse = ParseClientListResponse(msg); + break; + + default: + throw new ArgumentException($"Unknown message type: {msg}", nameof(msg)); + } + + return eftResponse; + } + + + T TryParse(string input, int length, ref int index) + { + return TryParse(input, length, ref index, ""); + } + T TryParse(string input, int length, ref int index, string format) + { + T result = default(T); + + if (input.Length - index >= length) + { + if (result is bool && length == 1) + { + result = (T)Convert.ChangeType((input[index] == '1' || input[index] == 'Y'), typeof(T)); + index += length; + } + else + { + object data = input.Substring(index, length); + try + { + if (result is Enum && length == 1) + result = (T)Enum.ToObject(typeof(T), ((string)data)[0]); + else if (result is DateTime && format.Length > 1) + result = (T)(object)DateTime.ParseExact((string)data, format, null); + else + result = (T)Convert.ChangeType(data, typeof(T)); + } + catch + { + var idx = index; + //Log(LogLevel.Error, tr => tr.Set($"Unable to parse field. Input={input}, Index={idx}, Length={length}")); + } + finally + { + index += length; + } + } + } + else + index = length; + + return result; + } + + EFTResponse ParseEFTTransactionResponse(string msg) + { + var index = 1; + + var r = new EFTTransactionResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + r.Merchant = TryParse(msg, 2, ref index); + r.TxnType = TryParse(msg, 1, ref index); + r.CardAccountType = r.CardAccountType.FromString(TryParse(msg, 7, ref index)); + r.AmtCash = TryParse(msg, 9, ref index) / 100; + r.AmtPurchase = TryParse(msg, 9, ref index) / 100; + r.AmtTip = TryParse(msg, 9, ref index) / 100; + r.AuthCode = TryParse(msg, 6, ref index); + r.TxnRef = TryParse(msg, 16, ref index); + r.Stan = TryParse(msg, 6, ref index); + r.Caid = TryParse(msg, 15, ref index); + r.Catid = TryParse(msg, 8, ref index); + r.DateExpiry = TryParse(msg, 4, ref index); + r.SettlementDate = TryParse(msg, 4, ref index, "ddMM"); + r.Date = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); + r.CardType = TryParse(msg, 20, ref index); + r.Pan = TryParse(msg, 20, ref index); + r.Track2 = TryParse(msg, 40, ref index); + r.RRN = TryParse(msg, 12, ref index); + r.CardName = TryParse(msg, 2, ref index); + r.TxnFlags = new TxnFlags(TryParse(msg, 8, ref index).ToCharArray()); + r.BalanceReceived = TryParse(msg, 1, ref index); + r.AvailableBalance = TryParse(msg, 9, ref index) / 100; + r.ClearedFundsBalance = TryParse(msg, 9, ref index) / 100; + r.PurchaseAnalysisData = new PadField(TryParse(msg, msg.Length - index, ref index)); + + return r; + } + EFTResponse ParseEFTGetLastTransactionResponse(string msg) + { + var index = 1; + + var r = new EFTGetLastTransactionResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.LastTransactionSuccess = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + r.Merchant = TryParse(msg, 2, ref index); + + if (char.IsLower(msg[index])) + { + r.IsTrainingMode = true; + msg = msg.Substring(0, index) + char.ToUpper(msg[index]) + msg.Substring(index + 1); + } + r.TxnType = TryParse(msg, 1, ref index); + string accountType = TryParse(msg, 7, ref index); + if (accountType == "Credit ") r.CardAccountType = AccountType.Credit; + else if (accountType == "Savings") r.CardAccountType = AccountType.Savings; + else if (accountType == "Cheque ") r.CardAccountType = AccountType.Cheque; + else r.CardAccountType = AccountType.Default; + r.AmtCash = TryParse(msg, 9, ref index) / 100; + r.AmtPurchase = TryParse(msg, 9, ref index) / 100; + r.AmtTip = TryParse(msg, 9, ref index) / 100; + r.AuthCode = TryParse(msg, 6, ref index); + r.TxnRef = TryParse(msg, 16, ref index); + r.Stan = TryParse(msg, 6, ref index); + r.Caid = TryParse(msg, 15, ref index); + r.Catid = TryParse(msg, 8, ref index); + r.DateExpiry = TryParse(msg, 4, ref index); + r.SettlementDate = TryParse(msg, 4, ref index, "ddMM"); + r.BankDateTime = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); + r.CardType = TryParse(msg, 20, ref index); + r.Pan = TryParse(msg, 20, ref index); + r.Track2 = TryParse(msg, 40, ref index); + r.RRN = TryParse(msg, 12, ref index); + r.CardName = TryParse(msg, 2, ref index); + string txnFlags = TryParse(msg, 8, ref index); + r.TxnFlags = new TxnFlags(txnFlags.ToCharArray()); + r.BalanceReceived = TryParse(msg, 1, ref index); + r.AvailableBalance = TryParse(msg, 9, ref index) / 100; + int padLength = TryParse(msg, 3, ref index); + r.PurchaseAnalysisData = new PadField(TryParse(msg, padLength, ref index)); + + return r; + } + EFTResponse ParseSetDialogResponse(string msg) + { + var index = 1; + + var r = new SetDialogResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + + return r; + } + EFTResponse ParseEFTLogonResponse(string msg) + { + var index = 1; + + var r = new EFTLogonResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + if (msg.Length > 25) + { + r.Catid = TryParse(msg, 8, ref index); + r.Caid = TryParse(msg, 15, ref index); + r.Date = TryParse(msg, 12, ref index, "ddMMyyHHmmss"); + r.Stan = TryParse(msg, 6, ref index); + r.PinPadVersion = TryParse(msg, 16, ref index); + r.PurchaseAnalysisData = new PadField(TryParse(msg, msg.Length - index, ref index)); + } + return r; + } + EFTResponse ParseDisplayResponse(string msg) + { + int index = 1; + + var r = new EFTDisplayResponse(); + index++; // Skip sub code. + r.NumberOfLines = TryParse(msg, 2, ref index); + r.LineLength = TryParse(msg, 2, ref index); + for (int i = 0; i < r.NumberOfLines; i++) + r.DisplayText[i] = TryParse(msg, r.LineLength, ref index); + r.CancelKeyFlag = TryParse(msg, 1, ref index); + r.AcceptYesKeyFlag = TryParse(msg, 1, ref index); + r.DeclineNoKeyFlag = TryParse(msg, 1, ref index); + r.AuthoriseKeyFlag = TryParse(msg, 1, ref index); + r.InputType = TryParse(msg, 1, ref index); + r.OKKeyFlag = TryParse(msg, 1, ref index); + index += 2; + r.GraphicCode = TryParse(msg, 1, ref index); + int padLength = TryParse(msg, 3, ref index); + r.PurchaseAnalysisData = new PadField(TryParse(msg, padLength, ref index)); + + return r; + } + EFTResponse ParseReceiptResponse(string msg) + { + int index = 1; + + var r = new EFTReceiptResponse + { + Type = TryParse(msg, 1, ref index) + }; + + if (r.Type != ReceiptType.ReceiptText) + { + lastReceiptType = r.Type; + r.IsPrePrint = true; + } + else + { + List receiptLines = new List(); + bool done = false; + while (!done) + { + int lineLength = msg.Substring(index).IndexOf("\r\n"); + if (lineLength > 0) + { + receiptLines.Add(msg.Substring(index, lineLength)); + index += lineLength + 2; + if (index >= msg.Length) + done = true; + } + else + done = true; + } + + r.ReceiptText = receiptLines.ToArray(); + r.Type = lastReceiptType; + } + + return r; + } + EFTResponse ParseControlPanelResponse(string msg) + { + int index = 1; + + var r = new EFTControlPanelResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + return r; + } + EFTResponse ParseEFTReprintReceiptResponse(string msg) + { + int index = 1; + + var r = new EFTReprintReceiptResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + List receiptLines = new List(); + bool done = false; + while (!done) + { + int lineLength = msg.Substring(index).IndexOf("\r\n"); + if (lineLength > 0) + { + receiptLines.Add(msg.Substring(index, lineLength)); + index += lineLength + 2; + if (index >= msg.Length) + done = true; + } + else + done = true; + } + + r.ReceiptText = receiptLines.ToArray(); + + return r; + } + EFTResponse ParseEFTSettlementResponse(string msg) + { + var index = 1; + + var r = new EFTSettlementResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + if (msg.Length > 25) + { + r.SettlementData = msg.Substring(index); + + //int cardCount = int.Parse( response.Substring( index, 9 ) ); index += 9; + //for( int i = 0; i < cardCount; i++ ) + //{ + // int cardTotalsDataLength = int.Parse( response.Substring( index, 3 ) ); index += 3; + // if( cardTotalsDataLength >= 69 ) + // { + // SettlementCardTotals cardTotals = new SettlementCardTotals(); + // cardTotals.CardName = response.Substring( index, 20 ); index += 20; + // try { cardTotals.PurchaseAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { cardTotals.PurchaseAmount = 0; } + // finally { index += 9; } + // try { cardTotals.PurchaseCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { cardTotals.PurchaseCount = 0; } + // finally { index += 3; } + // try { cardTotals.CashOutAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { cardTotals.CashOutAmount = 0; } + // finally { index += 9; } + // try { cardTotals.CashOutCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { cardTotals.CashOutCount = 0; } + // finally { index += 3; } + // try { cardTotals.RefundAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { cardTotals.RefundAmount = 0; } + // finally { index += 9; } + // try { cardTotals.RefundCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { cardTotals.RefundCount = 0; } + // finally { index += 3; } + // try { cardTotals.TotalAmount = decimal.Parse( response.Substring( index, 10 ), System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite ) / 100; } + // catch { cardTotals.TotalAmount = 0; } + // finally { index += 9; } + // try { cardTotals.TotalCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { cardTotals.TotalCount = 0; } + // finally { index += 3; } + // displayResponse.SettlementCardData.Add( cardTotals ); + // index += cardTotalsDataLength - 69; + // } + //} + + //int totalsDataLength = int.Parse( response.Substring( index, 3 ) ); index += 3; + //if( totalsDataLength >= 69 ) + //{ + // SettlementTotals settleTotals = new SettlementTotals(); + // settleTotals.TotalsDescription = response.Substring( index, 20 ); index += 20; + // try { settleTotals.PurchaseAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { settleTotals.PurchaseAmount = 0; } + // finally { index += 9; } + // try { settleTotals.PurchaseCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { settleTotals.PurchaseCount = 0; } + // finally { index += 3; } + // try { settleTotals.CashOutAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { settleTotals.CashOutAmount = 0; } + // finally { index += 9; } + // try { settleTotals.CashOutCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { settleTotals.CashOutCount = 0; } + // finally { index += 3; } + // try { settleTotals.RefundAmount = decimal.Parse( response.Substring( index, 9 ) ) / 100; } + // catch { settleTotals.RefundAmount = 0; } + // finally { index += 9; } + // try { settleTotals.RefundCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { settleTotals.RefundCount = 0; } + // finally { index += 3; } + // try { settleTotals.TotalAmount = decimal.Parse( response.Substring( index, 10 ), System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite ) / 100; } + // catch { settleTotals.TotalAmount = 0; } + // finally { index += 9; } + // try { settleTotals.TotalCount = int.Parse( response.Substring( index, 3 ) ); } + // catch { settleTotals.TotalCount = 0; } + // finally { index += 3; } + // displayResponse.TotalsData = settleTotals; + // index += totalsDataLength - 69; + //} + + //int padLength; + //try + //{ + // padLength = int.Parse( response.Substring( index, 3 ) ); + // displayResponse.PurchaseAnalysisData = response.Substring( index, padLength ); index += padLength; + //} + //catch { padLength = 0; } + //finally { index += 3; } + } + + return r; + } + EFTResponse ParseQueryCardResponse(string msg) + { + int index = 1; + + var r = new EFTQueryCardResponse + { + AccountType = (AccountType)msg[index++], + Success = TryParse(msg, 1, ref index), + ResponseCode = TryParse(msg, 2, ref index), + ResponseText = TryParse(msg, 20, ref index) + }; + + if (msg.Length > 25) + { + r.Track2 = msg.Substring(index, 40); index += 40; + string track1or3 = msg.Substring(index, 80); index += 80; + char trackFlag = msg[index++]; + switch (trackFlag) + { + case '1': + r.TrackFlags = TrackFlags.Track1; + r.Track1 = track1or3; + break; + case '2': + r.TrackFlags = TrackFlags.Track2; + break; + case '3': + r.TrackFlags = TrackFlags.Track1 | TrackFlags.Track2; + r.Track1 = track1or3; + break; + case '4': + r.TrackFlags = TrackFlags.Track3; + r.Track3 = track1or3; + break; + case '6': + r.TrackFlags = TrackFlags.Track2 | TrackFlags.Track3; + r.Track3 = track1or3; + break; + } + + r.CardName = int.Parse(msg.Substring(index, 2)); index += 2; + + int padLength; + try + { + padLength = int.Parse(msg.Substring(index, 3)); + r.PurchaseAnalysisData = new PadField(msg.Substring(index, padLength)); + index += padLength; + } + catch { padLength = 0; } + finally { index += 3; } + } + + return r; + } + EFTResponse ParseClientListResponse(string msg) + { + var r = new EFTClientListResponse(); + // Get rid of the junk at the beginning, will look like #Q000001 + string trimmedMsg = msg.Substring(msg.IndexOf('{')); + // Each client coming in will be in format {CLIENTNAME,IPADDRESS,PORT,STATUS}|{CLIENTNAME,IPADDRESS,PORT,STATUS} So we split on the | character + if (trimmedMsg.IndexOf('|') > -1) + { + string[] clients = trimmedMsg.Split('|'); + foreach (string client in clients) + { + EFTClientListResponse.EFTClient newClient = new EFTClientListResponse.EFTClient(); + // Each client 'string' will be surrounded by {} so we get rid of them + string trimmedClient = client.TrimEnd('}').TrimStart('{'); + // That leaves us with the CLIENTNAME,IPADDRESS,PORT,STATUS and we split on ',' to get individual properties + string[] newClientProps = trimmedClient.Split(','); + newClient.Name = newClientProps[0]; + newClient.IPAddress = newClientProps[1]; + newClient.Port = Convert.ToInt32(newClientProps[2]); + // EFTClientListResponse.Client.State is an enum, so we need to check the string and parse into the enum + if (newClientProps[3].Equals("AVAILABLE")) + { + newClient.State = EFTClientListResponse.EFTClientState.Available; + } + else + newClient.State = EFTClientListResponse.EFTClientState.Unavailable; + // Add the new client to the response list + r.EFTClients.Add(newClient); + } + } + else if (msg.IndexOf('|') == -1) + { + EFTClientListResponse.EFTClient newClient = new EFTClientListResponse.EFTClient(); + // Each client 'string' will be surrounded by {} so we get rid of them + string trimmedClient = trimmedMsg.TrimEnd('}').TrimStart('{'); + // That leaves us with the CLIENTNAME,IPADDRESS,PORT,STATUS and we split on ',' to get individual properties + string[] newClientProps = trimmedMsg.Split(','); + newClient.Name = newClientProps[0]; + newClient.IPAddress = newClientProps[1]; + newClient.Port = Convert.ToInt32(newClientProps[2]); + // EFTClientListResponse.Client.State is an enum, so we need to check the string and parse into the enum + if (newClientProps[3].Equals("AVAILABLE")) + { + newClient.State = EFTClientListResponse.EFTClientState.Available; + } + else + newClient.State = EFTClientListResponse.EFTClientState.Unavailable; + // Add the new client to the response list + r.EFTClients.Add(newClient); + } + return r; + } + EFTResponse ParseEFTConfigureMerchantResponse(string msg) + { + int index = 1; + + var r = new EFTConfigureMerchantResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + return r; + } + EFTResponse ParseEFTStatusResponse(string msg) + { + int index = 1; + + var r = new EFTStatusResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + if (index >= msg.Length) return r; + r.Merchant = TryParse(msg, 2, ref index); + r.AIIC = TryParse(msg, 11, ref index); + r.NII = TryParse(msg, 3, ref index); + r.Caid = TryParse(msg, 15, ref index); + r.Catid = TryParse(msg, 8, ref index); + r.Timeout = TryParse(msg, 3, ref index); + r.LoggedOn = TryParse(msg, 1, ref index); + r.PinPadSerialNumber = TryParse(msg, 16, ref index); + r.PinPadVersion = TryParse(msg, 16, ref index); + r.BankDescription = TryParse(msg, 32, ref index); + int padLength = TryParse(msg, 3, ref index); + if (msg.Length - index < padLength) + return r; + r.SAFCount = TryParse(msg, 4, ref index); + r.NetworkType = TryParse(msg, 1, ref index); + r.HardwareSerial = TryParse(msg, 16, ref index); + r.RetailerName = TryParse(msg, 40, ref index); + r.OptionsFlags = ParseStatusOptionFlags(msg.Substring(index, 32).ToCharArray()); index += 32; + r.SAFCreditLimit = TryParse(msg, 9, ref index) / 100; + r.SAFDebitLimit = TryParse(msg, 9, ref index) / 100; + r.MaxSAF = TryParse(msg, 3, ref index); + r.KeyHandlingScheme = ParseKeyHandlingType(msg[index++]); + r.CashoutLimit = TryParse(msg, 9, ref index) / 100; + r.RefundLimit = TryParse(msg, 9, ref index) / 100; + r.CPATVersion = TryParse(msg, 6, ref index); + r.NameTableVersion = TryParse(msg, 6, ref index); + r.TerminalCommsType = ParseTerminalCommsType(msg[index++]); + r.CardMisreadCount = TryParse(msg, 6, ref index); + r.TotalMemoryInTerminal = TryParse(msg, 4, ref index); + r.FreeMemoryInTerminal = TryParse(msg, 4, ref index); + r.EFTTerminalType = ParseEFTTerminalType(msg.Substring(index, 4)); index += 4; + r.NumAppsInTerminal = TryParse(msg, 2, ref index); + r.NumLinesOnDisplay = TryParse(msg, 2, ref index); + r.HardwareInceptionDate = TryParse(msg, 6, ref index, "ddMMyy"); + + return r; + } + + TerminalCommsType ParseTerminalCommsType(char CommsType) + { + TerminalCommsType commsType = TerminalCommsType.Unknown; + + if (CommsType == '0') commsType = TerminalCommsType.Cable; + else if (CommsType == '1') commsType = TerminalCommsType.Infrared; + + return commsType; + } + KeyHandlingType ParseKeyHandlingType(char KeyHandlingScheme) + { + KeyHandlingType keyHandlingType = KeyHandlingType.Unknown; + + if (KeyHandlingScheme == '0') keyHandlingType = KeyHandlingType.SingleDES; + else if (KeyHandlingScheme == '1') keyHandlingType = KeyHandlingType.TripleDES; + + return keyHandlingType; + } + EFTTerminalType ParseEFTTerminalType(string TerminalType) + { + EFTTerminalType terminalType = EFTTerminalType.Unknown; + + if (TerminalType == "0062") terminalType = EFTTerminalType.IngenicoNPT710; + else if (TerminalType == "0069") terminalType = EFTTerminalType.IngenicoPX328; + else if (TerminalType == "7010") terminalType = EFTTerminalType.Ingenicoi3070; + else if (TerminalType == "5110") terminalType = EFTTerminalType.Ingenicoi5110; + + return terminalType; + } + PINPadOptionFlags ParseStatusOptionFlags(char[] Flags) + { + PINPadOptionFlags flags = 0; + int index = 0; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Tipping; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.PreAuth; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Completions; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.CashOut; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Refund; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Balance; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Deposit; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Voucher; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.MOTO; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.AutoCompletion; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.EFB; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.EMV; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Training; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Withdrawal; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.Transfer; + if (Flags[index++] == '1') flags |= PINPadOptionFlags.StartCash; + return flags; + } + EFTResponse ParseChequeAuthResponse(string msg) + { + int index = 1; + + var r = new EFTChequeAuthResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + if (msg.Length > 25) + { + r.Merchant = TryParse(msg, 2, ref index); + try { r.Amount = decimal.Parse(msg.Substring(index, 9)) / 100; } + catch { r.Amount = 0; } + finally { index += 9; } + try { r.AuthNumber = int.Parse(msg.Substring(index, 6)); } + catch { r.AuthNumber = 0; } + finally { index += 6; } + r.ReferenceNumber = msg.Substring(index, 12); index += 12; + } + + return r; + } + EFTResponse ParseGenericPOSCommandResponse(string msg) + { + // Validate response length + if (string.IsNullOrEmpty(msg)) + { + return null; + } + + int index = 1; + + CommandType commandType = (CommandType)msg[index++]; + switch (commandType) + { + case CommandType.GetPassword: + var pwdResponse = new EFTGetPasswordResponse + { + ResponseCode = msg.Substring(index, 2) + }; + index += 2; + pwdResponse.Success = pwdResponse.ResponseCode == "00"; + pwdResponse.ResponseText = msg.Substring(index, 20); index += 20; + + if (msg.Length > 25) + { + int pwdLength = 0; + try { pwdLength = int.Parse(msg.Substring(index, 2)); } + finally { index += 2; } + pwdResponse.Password = msg.Substring(index, pwdLength); index += pwdLength; + } + return pwdResponse; + case CommandType.Slave: + var slaveResponse = new EFTSlaveResponse + { + ResponseCode = msg.Substring(index, 2) + }; + index += 2; + slaveResponse.Response = msg.Substring(index); + return slaveResponse; + + case CommandType.PayAtTable: + var patResponse = new EFTPayAtTableResponse(); + index = 22; + + var headerLength = msg.Substring(index, 6); index += 6; + int len = 0; + int.TryParse(headerLength, out len); + + patResponse.Header = msg.Substring(index, len); index += len; + patResponse.Content = msg.Substring(index, msg.Length - index); + + return patResponse; + + case CommandType.BasketData: + return ParseBasketDataResponse(msg); + } + + return null; + } + EFTResponse ParseConfigMerchantResponse(string msg) + { + int index = 1; + + var r = new EFTConfigureMerchantResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + return r; + } + EFTResponse ParseCloudLogonResponse(string msg) + { + int index = 1; + + var r = new EFTCloudLogonResponse(); + index++; // Skip sub code. + r.Success = TryParse(msg, 1, ref index); + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + return r; + } + + EFTResponse ParseBasketDataResponse(string msg) + { + int index = 1; // msg[0] is the command code + + var r = new EFTBasketDataResponse(); + index++; // Skip sub code. + r.ResponseCode = TryParse(msg, 2, ref index); + r.ResponseText = TryParse(msg, 20, ref index); + + r.Success = r.ResponseCode == "00"; + + return r; + } + + + /// + /// Convert a PC-EFTPOS message (e.g. #0010K0000) to a human readable debug string + /// + public static string MsgToDebugString(string msg) + { + if ((msg?.Length ?? 0) < 2) + { + return $"Unable to parse msg{Environment.NewLine}ContentLength={msg?.Length ?? 0}{Environment.NewLine}Content={msg}"; + } + + // Remove the header if one exists + if (msg[0] == '#') + { + if (msg.Length < 7) + return $"Unable to parse msg{Environment.NewLine}ContentLength={msg?.Length ?? 0}{Environment.NewLine}Content={msg}"; + + msg = msg.Substring(5); + } + + var messageParser = new DefaultMessageParser(); + try + { + var eftResponse = messageParser.StringToEFTResponse(msg); + return PrintProperties(eftResponse); + } + catch (ArgumentException) + { + // Try StringToEFTRequest() + return $"Unable to parse msg{Environment.NewLine}ContentLength={msg.Length}{Environment.NewLine}Content={msg}"; + } + } + + static string PrintProperties(object obj) + { + if (obj == null) + return "NULL"; + + var sb = new StringBuilder(); + + var objType = obj.GetType(); + var properties = objType.GetProperties(); + foreach (var p in properties) + { + sb.AppendFormat("{1}: {2}", p.Name, p.ToString()); + } + + return sb.ToString(); + } + #endregion + + #region EFTResponseToString + + public string EFTRequestToString(EFTRequest eftRequest) + { + // Build the request string. + var request = BuildRequest(eftRequest); + var len = request.Length + 5; + request.Insert(0, '#'); + request.Insert(1, len.PadLeft(4)); + return request.ToString(); + } + + StringBuilder BuildRequest(EFTRequest eftRequest) + { + if (eftRequest is EFTLogonRequest) + { + return BuildEFTLogonRequest((EFTLogonRequest)eftRequest); + } + + if (eftRequest is EFTTransactionRequest) + { + return BuildEFTTransactionRequest((EFTTransactionRequest)eftRequest); + } + + if (eftRequest is EFTGetLastTransactionRequest) + { + return BuildEFTGetLastTransactionRequest((EFTGetLastTransactionRequest)eftRequest); + } + + if (eftRequest is EFTReprintReceiptRequest) + { + return BuildEFTReprintReceiptRequest((EFTReprintReceiptRequest)eftRequest); + } + + if (eftRequest is SetDialogRequest) + { + return BuildSetDialogRequest((SetDialogRequest)eftRequest); + } + + if (eftRequest is ControlPanelRequest) + { + return BuildControlPanelRequest((ControlPanelRequest)eftRequest); + } + + if (eftRequest is EFTSettlementRequest) + { + return BuildSettlementRequest((EFTSettlementRequest)eftRequest); + } + + if (eftRequest is EFTStatusRequest) + { + return BuildStatusRequest((EFTStatusRequest)eftRequest); + } + + if (eftRequest is EFTChequeAuthRequest) + { + return BuildChequeAuthRequest((EFTChequeAuthRequest)eftRequest); + } + + if (eftRequest is QueryCardRequest) + { + return BuildQueryCardRequest((QueryCardRequest)eftRequest); + } + + if (eftRequest is EFTGetPasswordRequest) + { + return BuildGetPasswordRequest((EFTGetPasswordRequest)eftRequest); + } + + if (eftRequest is EFTSlaveRequest) + { + return BuildSlaveRequest((EFTSlaveRequest)eftRequest); + } + + if (eftRequest is EFTConfigureMerchantRequest) + { + return BuildConfigMerchantRequest((EFTConfigureMerchantRequest)eftRequest); + } + + if (eftRequest is EFTCloudLogonRequest) + { + return BuildCloudLogonRequest((EFTCloudLogonRequest)eftRequest); + } + + if (eftRequest is EFTClientListRequest) + { + return BuildGetClientListRequest((EFTClientListRequest)eftRequest); + } + + if (eftRequest is EFTSendKeyRequest) + { + return BuildSendKeyRequest((EFTSendKeyRequest)eftRequest); + } + + if (eftRequest is EFTReceiptRequest) + { + return BuildReceiptRequest((EFTReceiptRequest)eftRequest); + } + + if (eftRequest is EFTPayAtTableRequest) + { + return BuildPayAtTableRequest((EFTPayAtTableRequest)eftRequest); + } + + if (eftRequest is EFTBasketDataRequest) + { + return BuildBasketDataRequest((EFTBasketDataRequest)eftRequest); + } + + throw new Exception("Unknown EFTRequest type."); + } + StringBuilder BuildEFTTransactionRequest(EFTTransactionRequest v) + { + var r = new StringBuilder(); + r.Append("M"); + r.Append("0"); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append((char)v.TxnType); + r.Append(v.TrainingMode ? '1' : '0'); + r.Append(v.EnableTip ? '1' : '0'); + r.Append(v.AmtCash.PadLeftAsInt(9)); + r.Append(v.AmtPurchase.PadLeftAsInt(9)); + r.Append(v.AuthCode.PadLeft(6)); + r.Append(v.TxnRef.PadRightAndCut(16)); + r.Append((char)v.ReceiptPrintMode); + r.Append((char)v.ReceiptCutMode); + r.Append((char)v.PanSource); + r.Append(v.Pan.PadRightAndCut(20)); + r.Append(v.DateExpiry.PadRightAndCut(4)); + r.Append(v.Track2.PadRightAndCut(40)); + r.Append((char)v.CardAccountType); + r.Append(v.Application.ToApplicationString()); + r.Append(v.RRN.PadRightAndCut(12)); + r.Append(v.CurrencyCode.PadRightAndCut(3)); + r.Append((char)v.OriginalTxnType); + r.Append(v.Date != null ? v.Date.Value.ToString("ddMMyy") : " "); + r.Append(v.Time != null ? v.Time.Value.ToString("HHmmss") : " "); + r.Append(" ".PadRightAndCut(8)); // Reserved + r.Append(v.PurchaseAnalysisData.GetAsString(true)); + + return r; + } + StringBuilder BuildEFTLogonRequest(EFTLogonRequest v) + { + var r = new StringBuilder(); + r.Append("G"); + r.Append((char)v.LogonType); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append((char)v.ReceiptPrintMode); + r.Append((char)v.ReceiptCutMode); + r.Append(v.Application.ToApplicationString()); + r.Append(v.PurchaseAnalysisData.GetAsString(true)); + return r; + } + StringBuilder BuildEFTReprintReceiptRequest(EFTReprintReceiptRequest v) + { + var r = new StringBuilder(); + r.Append("C"); + r.Append((char)v.ReprintType); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append((char)v.ReceiptCutMode); + r.Append((char)v.ReceiptPrintMode); + r.Append(v.Application.ToApplicationString()); + return r; + } + StringBuilder BuildEFTGetLastTransactionRequest(EFTGetLastTransactionRequest v) + { + var r = new StringBuilder(); + r.Append("N"); + r.Append("0"); + r.Append(v.Application.ToApplicationString()); + return r; + } + StringBuilder BuildSetDialogRequest(SetDialogRequest v) + { + var r = new StringBuilder(); + r.Append("2"); + r.Append(v.DisableDisplayEvents ? '5' : ' '); + r.Append((char)v.DialogType); + r.Append(v.DialogX.PadLeft(4)); + r.Append(v.DialogY.PadLeft(4)); + r.Append(v.DialogPosition.ToString().PadRightAndCut(12)); + r.Append(v.EnableTopmost ? '1' : '0'); + r.Append(v.DialogTitle.PadRightAndCut(32)); + return r; + } + StringBuilder BuildControlPanelRequest(ControlPanelRequest v) + { + var r = new StringBuilder(); + r.Append("5"); // ControlPanel + r.Append((char)v.ControlPanelType); + r.Append((char)v.ReceiptPrintMode); + r.Append((char)v.ReceiptCutMode); + r.Append((char)v.ReturnType); + return r; + } + StringBuilder BuildSettlementRequest(EFTSettlementRequest v) + { + var r = new StringBuilder(); + r.Append("P"); + r.Append((char)v.SettlementType); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append((char)v.ReceiptPrintMode); + r.Append((char)v.ReceiptCutMode); + r.Append(v.ResetTotals ? '1' : '0'); + r.Append(v.Application.ToApplicationString()); + r.Append(v.PurchaseAnalysisData.GetAsString(true)); + return r; + } + StringBuilder BuildQueryCardRequest(QueryCardRequest v) + { + var r = new StringBuilder(); + r.Append("J"); + r.Append((char)v.QueryCardType); + r.Append(v.Application.ToApplicationString()); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append(v.PurchaseAnalysisData.GetAsString(true)); + return r; + } + StringBuilder BuildConfigMerchantRequest(EFTConfigureMerchantRequest v) + { + var r = new StringBuilder(); + r.Append("10"); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append(v.AIIC.PadLeft(11)); + r.Append(v.NII.PadLeft(3)); + r.Append(v.Caid.PadRightAndCut(15)); + r.Append(v.Catid.PadRightAndCut(8)); + r.Append(v.Timeout.PadLeft(3)); + r.Append(v.Application.ToApplicationString()); + return r; + } + StringBuilder BuildStatusRequest(EFTStatusRequest v) + { + var r = new StringBuilder(); + r.Append("K"); + r.Append((char)v.StatusType); + r.Append(v.Merchant.PadRightAndCut(2)); + r.Append(v.Application.ToApplicationString()); + return r; + } + StringBuilder BuildChequeAuthRequest(EFTChequeAuthRequest v) + { + var r = new StringBuilder(); + r.Append("H0"); + r.Append(v.Application.ToApplicationString()); + r.Append(' '); + r.Append(v.BranchCode.PadRightAndCut(6)); + r.Append(v.AccountNumber.PadRightAndCut(14)); + r.Append(v.SerialNumber.PadRightAndCut(14)); + r.Append(v.Amount.PadLeftAsInt(9)); + r.Append((char)v.ChequeType); + r.Append(v.ReferenceNumber.PadRightAndCut(12)); + + return r; + } + + StringBuilder BuildGetPasswordRequest(EFTGetPasswordRequest v) + { + var r = new StringBuilder(); + r.Append("X"); + r.Append((char)CommandType.GetPassword); + r.Append(v.MinPasswordLength.PadLeft(2)); + r.Append(v.MaxPassworkLength.PadLeft(2)); + r.Append(v.Timeout.PadLeft(3)); + r.Append("0" + (char)v.PasswordDisplay); + return r; + } + + StringBuilder BuildSlaveRequest(EFTSlaveRequest v) + { + var r = new StringBuilder(); + r.Append("X"); + r.Append((char)CommandType.Slave); + r.Append(v.RawCommand); + + return r; + } + + StringBuilder BuildGetClientListRequest(EFTClientListRequest v) + { + var r = new StringBuilder(); + r.Append("Q0"); + return r; + } + + StringBuilder BuildCloudLogonRequest(EFTCloudLogonRequest v) + { + var r = new StringBuilder(); + r.Append("A "); + r.Append(v.ClientID.PadRightAndCut(16)); + r.Append(v.Password.PadRightAndCut(16)); + r.Append(v.PairingCode.PadRightAndCut(16)); + return r; + } + + StringBuilder BuildSendKeyRequest(EFTSendKeyRequest v) + { + var r = new StringBuilder(); + r.Append("Y0"); + r.Append((char)v.Key); + if (v.Key == EFTPOSKey.Authorise && v.Data != null) + { + r.Append(v.Data.PadRightAndCut(20)); + } + + return r; + } + + StringBuilder BuildReceiptRequest(EFTReceiptRequest v) + { + return new StringBuilder("3 "); + } + + StringBuilder BuildPayAtTableRequest(EFTPayAtTableRequest request) + { + var r = new StringBuilder(); + r.Append("X"); + r.Append((char)CommandType.PayAtTable); + r.Append(request.Header); + r.Append(request.Content); + + return r; + } + + StringBuilder BuildBasketDataRequest(EFTBasketDataRequest request) + { + var jsonContent = "{}"; + + switch (request.Command) + { + case EFTBasketDataCommandCreate c: + // Serializer the Basket object to JSON + using (var ms = new System.IO.MemoryStream()) + { + // Would be better to use use Newtonsoft.Json here, but we don't want the dependency, so we are stuck with DataContractJsonSerializer + // We need to add the possible known types from c.Basket.Items to the "known types" of the serializer otherwise it throws an exception + var serializerSettings = new DataContractJsonSerializerSettings() + { + EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Never, + IgnoreExtensionDataObject = false, + SerializeReadOnlyTypes = true, + KnownTypes = (c?.Basket?.Items?.Count > 0) ? c.Basket.Items.Select(bi => bi.GetType()).Distinct() : new List() { typeof(EFTBasketItem) } + }; + var serializer = new DataContractJsonSerializer((c?.Basket != null) ? c.Basket.GetType() : typeof(EFTBasket), serializerSettings); + serializer.WriteObject(ms, c.Basket); + var json = ms.ToArray(); + ms.Close(); + jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); + } + break; + + case EFTBasketDataCommandAdd c: + // TODO: We can only have one item in an "add" command. Should we validate this request?? + + // Serializer the Basket object to JSON + using (var ms = new System.IO.MemoryStream()) + { + // Would be better to use use Newtonsoft.Json here, but we don't want the dependency, so we are stuck with DataContractJsonSerializer + // We need to add the possible known types from c.Basket.Items to the "known types" of the serializer otherwise it throws an exception + var serializerSettings = new DataContractJsonSerializerSettings() + { + EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Never, + IgnoreExtensionDataObject = false, + SerializeReadOnlyTypes = true, + KnownTypes = (c?.Basket?.Items?.Count > 0) ? c.Basket.Items.Select(bi => bi.GetType()).Distinct() : new List() { typeof(EFTBasketItem) } + }; + var serializer = new DataContractJsonSerializer((c?.Basket != null) ? c.Basket.GetType() : typeof(EFTBasket), serializerSettings); + serializer.WriteObject(ms, c.Basket); + var json = ms.ToArray(); + ms.Close(); + jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); + } + break; + + case EFTBasketDataCommandDelete c: + // Build our fake basket + var b = new EFTBasket() + { + Id = c.BasketId, + Items = new List() + { + new EFTBasketItem() + { + Id = c.BasketItemId + } + } + }; + + // Serializer the Basket object to JSON + using (var ms = new System.IO.MemoryStream()) + { + var serializer = new DataContractJsonSerializer(typeof(EFTBasket), new DataContractJsonSerializerSettings() { IgnoreExtensionDataObject = false, SerializeReadOnlyTypes = true }); + serializer.WriteObject(ms, b); + var json = ms.ToArray(); + ms.Close(); + jsonContent = System.Text.Encoding.UTF8.GetString(json, 0, json.Length); + } + break; + + case EFTBasketDataCommandRaw c: + jsonContent = c.BasketContent; + break; + } + + var r = new StringBuilder(); + r.Append("X"); + r.Append((char)CommandType.BasketData); + r.Append(jsonContent.Length.PadLeft(6)); + r.Append(jsonContent); + return r; + } + + + #endregion + + public EFTResponse XMLStringToEFTResponse(string msg) + { + EFTPosAsPinpadResponse response = null; + try + { + response = XMLSerializer.Deserialize(msg); + } + catch (Exception) + { + } + + return response; + } + + public string EFTRequestToXMLString(EFTRequest eftRequest) + { + switch (eftRequest) + { + case EFTPosAsPinpadRequest r: return EFTRequestToXMLString(r); + default: return string.Empty; + } + } + + private string EFTRequestToXMLString(EFTPosAsPinpadRequest eftRequest) + { + try + { + var response = XMLSerializer.Serialize(eftRequest); + return response.Insert(0, $"&{(response.Length + 7).ToString("000000")}"); + } + catch (Exception) + { + return string.Empty; + } + } + } + } diff --git a/README.md b/README.md index 8c0ce47..7d83f24 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,9 @@ class EFTClientIPDemoAsync ``` ## Release notes - +## 1.4.0.0 (2018-04-30) +* Added IDialogUIHandler for easier handling of POS custom dialogs. +* Updated MessageParser to allow for custom parsing. ### 1.3.5.0 (2018-02-16) * Added support for .NET Standard 2.0