using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Creditcall.ChipDna.Client { internal class Client { #region default messages private const string AuthCodeInput = "\r\nPlease Enter Auth Code (Default=12345)"; private const string VoiceReferralContinue = "\r\nVoice Referral Approved [True,False] (Default=True)"; private const string VoiceReferralError = "ContinueVoiceReferral Errors"; private const string VoiceReferralTelNumber = "\r\nVoice Referral Telephone Number: "; private const string VoiceReferralDefault = "True"; private const string AuthCodeDefault = "12345"; private const string DeferredAuthorizationContinue = "\r\nDeferred Authorization Approved [True,False] (Default=True)"; private const string DeferredAuthorizationError = "ContinueDeferredAuthorization Errors"; private const string DeferredAuthorizationDefault = "True"; #endregion public TaskCompletionSource> TransactionCompletionSource { get; set; } private volatile bool voiceReferralRequired; private volatile bool voiceReferralDone; private readonly bool saveReceipt; private volatile bool deferredAuthorizationRequested; private volatile bool deferredAuthorizationDone; private volatile bool transactionInProgress; private volatile bool getCardDetailsInProgress; private bool verboseLogEnabled; private readonly StringBuilder consoleCommands; private readonly ClientHelper chipDnaClientLib; private bool requestQueueRunCompletedEventSubscribed; private readonly ConfigFileParser settings; public Client(string identifier, string address, int port, string sslHost, bool receiptIsSaved, ConfigFileParser settings) { Console.OutputEncoding = Encoding.UTF8; this.settings = settings; saveReceipt = receiptIsSaved; consoleCommands = new StringBuilder(); consoleCommands.Append("S \t Current Status\r\n"); consoleCommands.Append("N \t Get Card Status\r\n"); consoleCommands.Append("W \t Get Merchant Data\r\n"); consoleCommands.Append("M \t Set Idle Message On Supported PED\r\n"); consoleCommands.Append("R \t Process Refund By Reference\r\n"); consoleCommands.Append("P \t Start Transaction\r\n"); consoleCommands.Append("C \t Confirm Transaction\r\n"); consoleCommands.Append("V \t Void Transaction\r\n"); consoleCommands.Append("T \t Terminate Transaction\r\n"); consoleCommands.Append("U \t Update Transaction\r\n"); consoleCommands.Append("K \t Release Card\r\n"); consoleCommands.Append("L \t Continue Transaction\r\n"); consoleCommands.Append("F \t Continue Voice Referral\r\n"); consoleCommands.Append("D \t Continue Deferred Authorization\r\n"); consoleCommands.Append("J \t Continue Signature Verification\r\n"); consoleCommands.Append("I \t Transaction Information\r\n"); consoleCommands.Append("+ \t Toggle Client Verbose Debug\r\n"); consoleCommands.Append("X \t Request TMS Update\r\n"); consoleCommands.Append("G \t Get Card Details\r\n"); consoleCommands.Append("Q \t Quit Client Application\r\n"); consoleCommands.Append("B \t Open Pass Thru Session\r\n"); consoleCommands.Append("E \t Close Pass Thru Session\r\n"); consoleCommands.Append("H \t Send Pass Thru Command\r\n"); consoleCommands.Append("O \t Connect And Configure\r\n"); consoleCommands.Append("A \t Custom Command\r\n"); consoleCommands.Append("Y \t Show/Hide the Request Queue Event response\r\n"); consoleCommands.Append("Z \t Run the Request Queue\r\n"); if (settings.ApiKey != null) { if (!string.IsNullOrEmpty(identifier)) { chipDnaClientLib = new ClientHelper(identifier, address, port, sslHost, settings.ApiKey); } else { chipDnaClientLib = new ClientHelper(address, port, sslHost, settings.ApiKey); } } else { chipDnaClientLib = new ClientHelper(identifier, address, port, sslHost); } chipDnaClientLib.VoiceReferralEvent += ChipDnaClientLibOnVoiceReferral; chipDnaClientLib.DeferredAuthorizationEvent += ChipDnaClientLibOnDeferredAuthorization; chipDnaClientLib.TransactionFinishedEvent += ChipDnaClientLibOnTransactionFinished; chipDnaClientLib.TransactionUpdateEvent += ChipDnaClientLibOnTransactionUpdate; chipDnaClientLib.CardNotificationEvent += ChipDnaClientLibOnCardNotification; chipDnaClientLib.SignatureVerificationRequestedEvent += ChipDnaClientLibOnSignatureVerificationRequested; chipDnaClientLib.CardDetailsEvent += ChipDnaClientLibOnCardDetails; chipDnaClientLib.TransactionPauseEvent += ChipDnaClientLibOnTransactionPause; chipDnaClientLib.PaymentDeviceAvailabilityChangeEvent += ChipDnaClientLibOnPaymentDeviceAvailabilityChangeEvent; chipDnaClientLib.TmsUpdateEvent += ChipDnaClientLibOnTmsUpdateEvent; chipDnaClientLib.ErrorEvent += ChipDnaClientLibOnErrorEvent; chipDnaClientLib.UpdateTransactionParametersFinishedEvent += ChipDnaClientLibOnUpdateTransactionParametersFinishedEvent; chipDnaClientLib.SendPassThruCommandResponseEvent += ChipDnaClientLibOnPassThruResponseEvent; chipDnaClientLib.OpenPassThruSessionResponseEvent += ChipDnaClientLibOnOpenPassThruSessionResponseEvent; chipDnaClientLib.ConnectAndConfigureEvent += ChipDnaClientLibOnConnectAndConfigureEvent; chipDnaClientLib.ConfigurationUpdateEvent += ChipDnaClientLibOnConfigurationUpdateEvent; chipDnaClientLib.DccRateInformationEvent += ChipDnaClientLibOnDccRateInformationEvent; } private ParameterSet GetExtraParams(string commandName) { List extraParams = this.settings.GetExtraParameters(commandName); ParameterSet returnSet = new ParameterSet(); foreach (ExtraParameter theParam in extraParams) { if (theParam.GetInput) { AddUserInputToParameters(returnSet, theParam.Key, theParam.Description, theParam.DefaultValue); } else if (theParam.DefaultValue != null && theParam.DefaultValue.Equals(":EMPTY:")) { returnSet.Add(theParam.Key, ""); } else { returnSet.Add(theParam.Key, theParam.DefaultValue); } } return returnSet; } private void PerformDeferredAuthorization() { var parameterSet = new ParameterSet(); var input = GetUserInput(DeferredAuthorizationContinue); if (string.IsNullOrEmpty(input)) input = DeferredAuthorizationDefault; parameterSet.Add(ParameterKeys.DeferredAuthorizationApproved, input); parameterSet.Add(GetExtraParams("ContinueDeferredAuthorization")); var response = chipDnaClientLib.ContinueDeferredAuthorization(parameterSet); string errors; response.GetValue(ParameterKeys.Errors, out errors); if (!string.IsNullOrEmpty(errors)) Console.WriteLine(ErrorsString(DeferredAuthorizationError, errors)); deferredAuthorizationDone = true; deferredAuthorizationRequested = false; } private void AddUserInputToParameters(ParameterSet set, string key, string description, string defaultVal) { StringBuilder message = new StringBuilder(); message.Append("\r\nPlease enter "); message.Append(description); message.Append(" ("); if (!string.IsNullOrEmpty(defaultVal)) { message.Append("default= "); message.Append(defaultVal); } else { message.Append("Optional"); } message.Append(")"); var input = GetUserInput(message.ToString()); input = string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(defaultVal) && !defaultVal.Equals(":EMPTY:", StringComparison.CurrentCulture)? defaultVal : input; if (!string.IsNullOrEmpty(input) || defaultVal != null && defaultVal.Equals(":EMPTY:")) { set.Add(key, input?? ""); } } public void PerformStartTransaction(string requestedAmount, string requestedTransactionType) { ResetTransaction(); transactionInProgress = true; var parameters = new ParameterSet(); var amount = requestedAmount; string amountType = string.Empty; var transactionType = GetType(requestedTransactionType, "Sale"); var reference = string.Format("ChipDNA-{0}-{1}", transactionType, DateTime.Now.ToString("yyyyMMddHHmmss")); string batchReference = string.Empty; string panKeyEntry = string.Empty; if (!string.IsNullOrEmpty(panKeyEntry) && !panKeyEntry.Equals("false", StringComparison.InvariantCultureIgnoreCase)) { parameters.Add(ParameterKeys.PanKeyEntry, panKeyEntry); if (!transactionType.Equals("Refund", StringComparison.InvariantCultureIgnoreCase)) //if the user chooses to perform a PanKeyEntry transaction and it's not a refund then prompt for address information { AddUserInputToParameters(parameters, ParameterKeys.CardholderAddress, "address information ", null); AddUserInputToParameters(parameters, ParameterKeys.CardholderZipcode, "Postcode/Zipcode", null); } } if (!string.IsNullOrEmpty(amount)) parameters.Add(ParameterKeys.Amount, amount); if (!string.IsNullOrEmpty(amountType)) parameters.Add(ParameterKeys.AmountType, amountType); parameters.Add(ParameterKeys.Reference, reference); parameters.Add(ParameterKeys.TransactionType, transactionType); if (transactionType.Equals("Sale", StringComparison.InvariantCultureIgnoreCase)) { bool isFirstStore = false; parameters.Add(ParameterKeys.CredentialOnFileFirstStore, isFirstStore.ToString()); if (isFirstStore) { parameters.Add(ParameterKeys.CredentialOnFileReason, GetFirstStoreReason()); } parameters.Add(ParameterKeys.TippingSupport, "Default"); } if (!string.IsNullOrEmpty(batchReference)) { parameters.Add(ParameterKeys.BatchReference, batchReference); } parameters.Add(GetExtraParams("StartTransaction")); Console.WriteLine(parameters.ToString()); Response response = chipDnaClientLib.StartTransaction(parameters); string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("StartTransaction Errors", errors)); } } public void Run() { while (true) { string userInput; PrintConsoleCommands(); userInput = Console.ReadLine(); if (string.IsNullOrEmpty(userInput)) continue; userInput = userInput.ToUpper().Trim(); if (userInput.Equals("Q")) { chipDnaClientLib.Dispose(); break; } ComputeInput(userInput); } } private void PrintConsoleCommands() { if (transactionInProgress || getCardDetailsInProgress || deferredAuthorizationDone || voiceReferralDone) return; } private void ResetTransaction() { transactionInProgress = false; getCardDetailsInProgress = false; voiceReferralRequired = false; voiceReferralDone = false; deferredAuthorizationRequested = false; deferredAuthorizationDone = false; } private void ComputeInput(String input) { try { switch (input) { case "M": SetIdleMessage(); break; case "R": PerformRefundTransactionByReference(); break; case "P": PerformStartTransaction("250", ""); break; case "C": PerformConfirmTransaction(); break; case "V": PerformVoidTransaction(); break; case "I": PerformGetTransactionInformation(); break; case "K": PerformReleaseCard(); break; case "L": PerformContinueTransaction(); break; case "U": DoUpdateTransaction(); break; case "T": PerformTerminateTransaction(); break; case "F": PerformVoiceReferral(); break; case "D": PerformDeferredAuthorization(); break; case "+": verboseLogEnabled = !verboseLogEnabled; Console.WriteLine("VerboseLogEnabled: {0}", verboseLogEnabled); break; case "S": GetStatus(); break; case "W": GetMerchantData(); break; case "N": GetCardStatus(); break; case "X": RequestTmsUpdate(); break; case "G": PerformGetCardDetails(); break; case "B": OpenPassThruSession(); break; case "E": ClosePassThruSession(); break; case "H": SendPassThruCommand(); break; case "O": ConnectAndConfigure(); break; case "A": PerformCustomCommand(); break; case "J": PerformContinueSignatureVerification(); break; case "Y": ToggleRequestQueueRunCompletedEvent(); break; case "Z": RunRequestQueue(); break; default: return; } } catch (ClientException ex) { PrintErrorEvent(ex); } } private void PrintErrorEvent(Exception exception) { if (exception == null) return; var builder = new StringBuilder(); builder.Append(exception.Message); var inner = exception.InnerException; while (inner != null && !string.IsNullOrEmpty(inner.Message)) { builder.Append(inner.Message + "\r\n"); inner = inner.InnerException; } if (verboseLogEnabled) { Console.WriteLine("ERROR: {0}:\r\n{1} ", builder, exception); } else { Console.WriteLine("ERROR: {0}", builder); } } private void RequestTmsUpdate() { var parameter = new ParameterSet(); var requestType = GetUserInput("\r\nTMS Request Type [TmsConfiguration] (Default=TmsConfiguration)"); if (!string.IsNullOrEmpty(requestType)) { parameter.Add(ParameterKeys.TmsRequestType, requestType); } var updateType = GetUserInput("\r\nConfiguration Update Type [Partial,Full] (Default=Partial)"); if (!string.IsNullOrEmpty(updateType)) { parameter.Add(ParameterKeys.TmsUpdateType, updateType); } var configUpdateSchedule = GetUserInput("\r\nConfiguration Update Schedule [Immediate,MaintenanceTime] (Default=Immediate)"); if (!string.IsNullOrEmpty(configUpdateSchedule)) { parameter.Add(ParameterKeys.ConfigurationUpdateSchedule, configUpdateSchedule); } parameter.Add(GetExtraParams("RequestTmsUpdate")); var response = chipDnaClientLib.RequestTmsUpdate(parameter); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("RequestTmsConfigUpdate Errors", errors)); } } private void SetIdleMessage() { var idleMessage = GetUserInput("\r\nPlease Enter Message For Idle Screen"); var parameter = new ParameterSet(); if (!string.IsNullOrEmpty(idleMessage)) { parameter.Add(ParameterKeys.IdleDisplayMessage, idleMessage); } parameter.Add(GetExtraParams("SetIdleMessage")); var response = chipDnaClientLib.SetIdleMessage(parameter); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("SetIdleMessage Errors", errors)); } } private void GetStatus() { Console.WriteLine(ParameterKeys.VersionInformation + "\n" + ParameterKeys.ChipDnaStatus + "\n" + ParameterKeys.PaymentDeviceStatus + "\n" + ParameterKeys.RequestQueueStatus + "\n" + ParameterKeys.TmsStatus + "\n" + ParameterKeys.PaymentPlatformStatus); var parameterSet = new ParameterSet(); parameterSet.Add(GetExtraParams("GetStatus")); Response response = chipDnaClientLib.GetStatus(GetRequestedParameterSet(parameterSet)); string errors; var serverStatus = (ChipDnaStatus) null; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("GetStatus Errors", errors)); } else { serverStatus = ChipDnaStatus.ParseFromResponse(response); } if (serverStatus != null) { Console.WriteLine(serverStatus); } } private void GetMerchantData() { Response response = chipDnaClientLib.GetMerchantData(new ParameterSet()); if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out var errors)) { Console.WriteLine(ErrorsString("GetMerchantData Errors", errors)); } var merchantData = MerchantData.ParseFromResponse(response); Console.WriteLine((merchantData.ToString())); } private void GetCardStatus() { var parameterSet = new ParameterSet(); var input = GetUserInput("\r\nEnter CardNotificationEvents [Transactions,Always] (Optional)"); if (!string.IsNullOrEmpty(input)) { parameterSet.Add(ParameterKeys.CardNotificationEvents, input); } parameterSet.Add(GetExtraParams("GetCardStatus")); var response = chipDnaClientLib.GetCardStatus(parameterSet); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("GetStatus Errors", errors)); } var cardStatus = new DeviceCardStatusDataObject(response); Console.WriteLine(cardStatus); } private void PerformVoiceReferral() { var parameterSet = new ParameterSet(); var input = GetUserInput(VoiceReferralContinue); if (String.IsNullOrEmpty(input)) input = VoiceReferralDefault; parameterSet.Add(ParameterKeys.VoiceReferralApproved, input); var authcode = GetUserInput(AuthCodeInput); if (string.IsNullOrEmpty(authcode)) authcode = AuthCodeDefault; parameterSet.Add(ParameterKeys.AuthCode, authcode); parameterSet.Add(GetExtraParams("ContinueVoiceReferral")); var response = chipDnaClientLib.ContinueVoiceReferral(parameterSet); string errors; response.GetValue(ParameterKeys.Errors, out errors); if (!string.IsNullOrEmpty(errors)) Console.WriteLine(ErrorsString(VoiceReferralError, errors)); voiceReferralDone = true; voiceReferralRequired = false; } private static string ErrorsString(string errorsTitle, string errors) { if (errorsTitle == null) errorsTitle = ""; if (errors == null) errors = ""; var errorStringBuilder = new StringBuilder(); errorStringBuilder.Append("\r\n"); errorStringBuilder.AppendFormat(null, "Errors: [{0}]\t{1}", errorsTitle, errors); return errorStringBuilder.ToString(); } private void PerformRefundTransactionByReference() { var defaultReference = string.Format("ChipDNA-{0}-{1}", "refundbyref", DateTime.Now.ToString("yyyyMMddHHmmss")); var amount = GetAmount(true); var reference = GetReference(true, defaultReference); var saleReference = GetUserInput("\r\nPlease Enter Sale Reference (Required)"); var saleDateTime = GetUserInput("\r\nPlease Enter Sale Date And Time (Optional)"); var parameterSet = new ParameterSet(); if (!string.IsNullOrEmpty(amount)) { parameterSet.Add(ParameterKeys.Amount, amount); } if (!string.IsNullOrEmpty(saleReference)) { parameterSet.Add(ParameterKeys.SaleReference, saleReference); } if (!string.IsNullOrEmpty(reference)) { parameterSet.Add(ParameterKeys.Reference, reference); } if (!string.IsNullOrEmpty(saleDateTime)) { parameterSet.Add(ParameterKeys.SaleDateTime, saleDateTime); } parameterSet.Add(GetExtraParams("LinkedRefundTransaction")); var response = chipDnaClientLib.LinkedRefundTransaction(parameterSet); string transactionResult; if (response.GetValue(ParameterKeys.TransactionResult, out transactionResult)) { Console.WriteLine("Refund By Reference Result: {0}", transactionResult); } string transactionId; if (response.GetValue(ParameterKeys.TransactionId, out transactionId)) { Console.WriteLine("Transaction ID: {0}", transactionId); } string receipt; if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("RefundTransactionByReference Errors", errors)); } string errorDescription; if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription)) { Console.WriteLine("ErrorDescription: {0}", errorDescription); } } private void PerformConfirmTransaction() { var reference = GetReference(true, ""); var amount = GetAmount(false); var gratuity = GetGratuity(false); var closeTransaction = GetCloseTransaction(false); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(reference)) { parameters.Add(ParameterKeys.Reference, reference); } if (!String.IsNullOrEmpty(amount)) { parameters.Add(ParameterKeys.Amount, amount); } if (!String.IsNullOrEmpty(gratuity)) { parameters.Add(ParameterKeys.Gratuity, gratuity); } if (!String.IsNullOrEmpty(closeTransaction)) { parameters.Add(ParameterKeys.CloseTransaction, closeTransaction); } parameters.Add(GetExtraParams("ConfirmTransaction")); var response = chipDnaClientLib.ConfirmTransaction(parameters); string receipt, result; if (response.GetValue(ParameterKeys.TransactionResult, out result)) { Console.WriteLine("Confirmed Transaction Result: {0}", result); } if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("ConfirmTransaction Errors", errors)); } string errorDescription; if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription)) { Console.WriteLine("ErrorDescription: {0}", errorDescription); } } private void DoUpdateTransaction() { var reference = GetReference(false, ""); var amount = GetAmount(false); var paramSet = new ParameterSet(); if (ShouldUpdateBatchReference()) { var batchReference = GetBatchReference(false); paramSet.Add(ParameterKeys.BatchReference, batchReference); } if (!string.IsNullOrEmpty(reference)) { paramSet.Add(ParameterKeys.Reference, reference); } if (!string.IsNullOrEmpty(amount)) { paramSet.Add(ParameterKeys.Amount, amount); } paramSet.Add(GetExtraParams("UpdateTransactionParameters")); var response = chipDnaClientLib.UpdateTransactionParameters(paramSet); string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("UpdateTransaction Errors", errors)); } } private void PerformReleaseCard() { var response = chipDnaClientLib.ReleaseCard(GetExtraParams("ReleaseCard")); string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("ReleaseCard Errors", errors)); } } private void PerformTerminateTransaction() { var terminateReason = GetUserInput("\r\nPlease Enter Terminate Reason (Optional)"); var displayMessage = GetUserInput("\r\nPlease Enter Display Message (Optional)"); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(displayMessage)) { parameters.Add(ParameterKeys.TerminateDisplayMessage, displayMessage); } if (!string.IsNullOrEmpty(terminateReason)) { parameters.Add(ParameterKeys.TerminateReason, terminateReason); } parameters.Add(GetExtraParams("TerminateTransaction")); var response = chipDnaClientLib.TerminateTransaction(parameters); string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("TerminateTransaction Errors", errors)); } } private void PerformVoidTransaction() { var reference = GetReference(true, ""); var voidReason = GetUserInput("\r\nPlease Enter Void Reason (Optional)"); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(reference)) { parameters.Add(ParameterKeys.Reference, reference); } if (!String.IsNullOrEmpty(voidReason)) { parameters.Add(ParameterKeys.VoidReason, voidReason); } parameters.Add(GetExtraParams("VoidTransaction")); var response = chipDnaClientLib.VoidTransaction(parameters); string receipt, result; if (response.GetValue(ParameterKeys.TransactionResult, out result)) { Console.WriteLine("Void Transaction Result: {0}", result); } if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt)) { PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt)); } string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("VoidTransaction Errors", errors)); } string errorDescription; if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription)) { Console.WriteLine("ErrorDescription: {0}", errorDescription); } } private void PerformGetTransactionInformation() { var referenceValue = GetReference(true, ""); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(referenceValue)) { parameters.Add(ParameterKeys.Reference, referenceValue); } parameters.Add(GetExtraParams("GetTransactionInformation")); var response = chipDnaClientLib.GetTransactionInformation(parameters); var responseParameter = response.ToList(); responseParameter.ForEach(parameter => { if (parameter.Key == ParameterKeys.Errors) { Console.WriteLine(ErrorsString("GetTransactionInformation Errors", parameter.Value)); } else { Console.WriteLine("{0} {1}", parameter.Key, parameter.Value); } }); } private static string GetUserInput(string prompt) { Console.Write("\r\n{0}:", prompt); string input = Console.ReadLine(); return input; } private void PerformContinueTransaction() { var forceDecline = GetUserInput("\r\nForce Decline [True,False] (Default=False)"); var continueParams = new ParameterSet(); if (!string.IsNullOrEmpty(forceDecline)) { continueParams.Add(ParameterKeys.ForceDecline, forceDecline); } var amount = GetAmount(false); var reference = GetReference(false, ""); if (ShouldUpdateBatchReference()) { var batchReference = GetBatchReference(false); continueParams.Add(ParameterKeys.BatchReference, batchReference); } if (!string.IsNullOrEmpty(amount)) { continueParams.Add(ParameterKeys.Amount, amount); } if (!string.IsNullOrEmpty(reference)) { continueParams.Add(ParameterKeys.Reference, reference); } continueParams.Add(GetExtraParams("ContinueTransaction")); Response response = chipDnaClientLib.ContinueTransaction(continueParams); string errors; response.GetValue(ParameterKeys.Errors, out errors); Console.WriteLine(!string.IsNullOrEmpty(errors) ? ErrorsString("ContinuePausedTransaction Errors", errors) : ""); } private void PerformContinueSignatureVerification() { var parameters = new ParameterSet(); parameters.Add(GetExtraParams("PerformContinueSignatureVerification")); var result = GetUserInput("\r\nVerify Signature? [True,False]"); if (!string.IsNullOrEmpty(result)) { parameters.Add(ParameterKeys.SignatureVerificationResult, result); } var response = chipDnaClientLib.ContinueSignatureVerification(parameters); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("ContinueSignatureVerification Errors", errors)); } } private void PerformGetCardDetails() { ResetTransaction(); getCardDetailsInProgress = true; var response = chipDnaClientLib.GetCardDetails(GetExtraParams("GetCardDetails")); string errors; if (response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("GetCardDetails Errors", errors)); } } private void PerformCustomCommand() { var parameters = new ParameterSet(); parameters.Add(GetExtraParams("CustomCommand")); var response = chipDnaClientLib.CustomCommand(parameters); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("CustomCommand Errors", errors)); } } private void ToggleRequestQueueRunCompletedEvent() { requestQueueRunCompletedEventSubscribed = !requestQueueRunCompletedEventSubscribed; Console.WriteLine("{0} the RequestQueueRunCompletedEvent.", requestQueueRunCompletedEventSubscribed ? "Subscribed to" : "Unsubscribed from"); if (requestQueueRunCompletedEventSubscribed) chipDnaClientLib.RequestQueueRunCompletedEvent += ChipDnaClientLibOnRequestQueueRunCompletedEvent; else chipDnaClientLib.RequestQueueRunCompletedEvent -= ChipDnaClientLibOnRequestQueueRunCompletedEvent; } private void RunRequestQueue() { var parameters = new ParameterSet(); var requestQueueType = GetUserInput("\r\nRun failed transactions (Pending/Failed/PendingAndFailed)? Default=[Pending]"); if (!string.IsNullOrEmpty(requestQueueType)) parameters.Add(ParameterKeys.RequestQueueType, requestQueueType); if (Enum.TryParse(requestQueueType, true, out RequestQueueTypes requestQueueTypeEnum)) { if (new[] { RequestQueueTypes.Failed, RequestQueueTypes.PendingAndFailed }.Contains(requestQueueTypeEnum)) { var reRunFailedTransactionsFromDate = GetUserInput("\r\nFrom when should the failed transactions be run (yyyyMMddHHmmss)?"); parameters.Add(ParameterKeys.RunFailedTransactionsFromDate, reRunFailedTransactionsFromDate); } } var response = chipDnaClientLib.RunRequestQueue(parameters); var responseParameter = response.ToList(); if (response.GetValue(ParameterKeys.Errors, out var errors)) { Console.WriteLine(ErrorsString("RunRequestQueue Errors", errors)); } } private void ConnectAndConfigure() { var response = chipDnaClientLib.ConnectAndConfigure(null); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("ConnectAndConfigure Errors", errors)); } } private void OpenPassThruSession() { var deviceId = GetUserInput("\r\nEnter payment device ID"); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(deviceId)) { parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId); } var response = chipDnaClientLib.OpenPassThruSession(parameters); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("OpenPassThruSession Errors", errors)); } } private void ClosePassThruSession() { var deviceId = GetUserInput("\r\nEnter payment device ID"); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(deviceId)) { parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId); } var response = chipDnaClientLib.ClosePassThruSession(parameters); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("ClosePassThruSession Errors", errors)); } } private void SendPassThruCommand() { var deviceId = GetUserInput("\r\nEnter payment device ID"); var parameters = new ParameterSet(); if (!string.IsNullOrEmpty(deviceId)) { parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId); } var commandData = GetUserInput("\r\nEnter command (hex string)"); if (!string.IsNullOrEmpty(commandData)) { parameters.Add(ParameterKeys.Data, commandData); } var response = chipDnaClientLib.SendPassThruCommand(parameters); string errors; if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors)) { Console.WriteLine(ErrorsString("SendPassThruCommand Errors", errors)); } } private string NewLine() { #if !WindowsCE return Environment.NewLine; #else return "\r\n"; #endif } private void PrintReceiptData(ReceiptData receiptData) { if (receiptData == null) return; var receiptDataBuilder = new StringBuilder(); receiptDataBuilder.Append("Receipt Data:"); var receiptDataForPrinting = new StringBuilder(); var entries = new List(receiptData.ReceiptEntries); entries.Sort((entry1, entry2) => { if (entry1.Priority > entry2.Priority) { return 1; } if (entry1.Priority < entry2.Priority) { return -1; } return 0; }); var recipientEntry = entries.FirstOrDefault(e => e.ReceiptEntryId.Equals("Recipient") && e.Value == "CARDHOLDER COPY"); bool isCustomerCopy = (recipientEntry != null) ? true : false; List printData = new List(entries.Count); foreach (var receiptEntry in entries) { if (isCustomerCopy && receiptEntry.ReceiptEntryId.Equals("SignatureLineLabel")) continue; /* * MerchantStreetAddress, MerchantCityStateZip and MerchantPhoneNumber * are not populated by ChipDNA and must be populated by the integration, * when they are present in ReceiptData. * The same also applies to MerchandiseDescription */ if (receiptEntry.ReceiptEntryId.Equals("MerchantStreetAddress") && !string.IsNullOrEmpty(settings.MerchantStreetAddress)) { //e.g. 123 Western Boulevard receiptEntry.Value = settings.MerchantStreetAddress; } else if (receiptEntry.ReceiptEntryId.Equals("MerchantCityStateZip") && !string.IsNullOrEmpty(settings.MerchantCityStateZip)) { //e.g. Chicago, IL, 60007 receiptEntry.Value = settings.MerchantCityStateZip; } else if (receiptEntry.ReceiptEntryId.Equals("MerchantPhoneNumber") && !string.IsNullOrEmpty(settings.MerchantPhoneNumber)) { //e.g. (555) 555-1234 receiptEntry.Value = settings.MerchantPhoneNumber; } else if (receiptEntry.ReceiptEntryId.Equals("MerchandiseDescription") && !string.IsNullOrEmpty(settings.MerchandiseDescription)) { //e.g. Food and drinks receiptEntry.Value = settings.MerchandiseDescription; } receiptDataBuilder.AppendFormat(null, "\t{0}=> {1}{2}\r\n", string.Format("[{0} - {1}]", receiptEntry.ReceiptEntryId, receiptEntry.ReceiptItemType).PadRight(40, '-'), string.IsNullOrEmpty(receiptEntry.Label) ? "" : String.Format("{0}: ", receiptEntry.Label), receiptEntry.Value); string label = string.IsNullOrEmpty(receiptEntry.Label) ? String.Empty : receiptEntry.Label + ": "; printData.Add(string.Format("{0}{1}", label, receiptEntry.Value)); if (receiptEntry.ReceiptEntryId.Equals("SignatureLineLabel", StringComparison.InvariantCultureIgnoreCase)) { printData.Add(string.Empty.PadRight(30, '_')); } } if (saveReceipt) { try { string currentPath = Directory.GetCurrentDirectory(); var receiptDir = Path.Combine(currentPath, @"Receipts\"); var receiptFilename = string.Format("ReceiptData.{0}.txt", DateTime.Now.ToString("yyyyMMddHHmmss")); if (!Directory.Exists(receiptDir)) { Directory.CreateDirectory(receiptDir); } var receiptFilePath = Path.Combine(receiptDir, Path.GetFileName(receiptFilename)); var str = string.Join(NewLine(), printData.ToArray()); str += Environment.NewLine; //To separate Merchant Copy from Cardholder Copy using (StreamWriter sw = new StreamWriter(receiptFilePath, append: true, Encoding.UTF8)) { sw.WriteLine(str, Encoding.UTF8); } Console.WriteLine("Receipt File: {0}", receiptFilePath); } catch (Exception ex) { PrintErrorEvent(new Exception("Error Writing Receipt to File.", ex)); } } Console.WriteLine("{0}{1}{2}{1}{0}", string.Empty.PadRight(80, '='), "\r\n", receiptDataBuilder); } private string GetAmount(bool isRequired) { return GetUserInput(string.Format("\r\nPlease Enter Amount [eg 250 => £2.50] ({0})", (isRequired) ? "Required" : "Optional")); } private bool GetFirstStore() { Boolean.TryParse(GetUserInput(string.Format("\r\nCredential On File First Store Transaction? [True,False] (Optional)")), out bool firstStore); return firstStore; } private string GetFirstStoreReason() { return GetUserInput(string.Format("\r\nCredential On File Transaction Reason? (Optional)")); } private ParameterSet GetRequestedParameterSet(ParameterSet parameterSet) { var input = GetUserInput(string.Format( "\r\nPlease Enter Requested Parameters (Comma Separated) [eg CHIPDNA_STATUS,TMS_STATUS] (Default = Returns all parameters)")); if (input != null) { string[] keys = input.Split(','); foreach (string par in keys) { if (!parameterSet.ContainsKey(par) && par != "") { parameterSet.Add(par, par); } } } return parameterSet; } private string GetAmountType(bool isRequired) { return GetUserInput(string.Format("\r\nActual or Estimated Amount? ({0}) [Actual]", isRequired ? "Required" : "Optional")); } private string GetGratuity(bool isRequired) { return GetUserInput(string.Format("\r\nPlease Enter Gratuity [eg 250 => £2.50] ({0} - End Of Day tipping)", (isRequired) ? "Required" : "Optional")); } private string GetCloseTransaction(bool isRequired) { return GetUserInput(string.Format("\r\nClose Transaction [True,False] (Default = True) ({0} - End Of Day tipping)", (isRequired) ? "Required" : "Optional")); } private string getDefaultInput(string defaultFormat) { if (string.IsNullOrEmpty(defaultFormat)) { return ""; } return string.Format("[{0}]", defaultFormat); } private String GetType(string requestedTransactionType, string defaultType) { if (string.IsNullOrEmpty(requestedTransactionType)) { if (string.IsNullOrEmpty(defaultType)) return ""; return defaultType; } return requestedTransactionType; } private String GetTippingMethod(bool isRequired, string defaultMethod) { var type = GetUserInput(string.Format("\r\nPlease Enter Tipping Method ({0}) {1}", (isRequired) ? "Required" : "Optional", getDefaultInput(defaultMethod))); if (string.IsNullOrEmpty(type)) { if (string.IsNullOrEmpty(defaultMethod)) return ""; return defaultMethod; } return type; } private String GetReference(bool isRequired, string defaultReference) { var reference = GetUserInput(string.Format("\r\nPlease Enter Reference ({0}) {1}", (isRequired) ? "Required" : "Optional", getDefaultInput(defaultReference))); if (string.IsNullOrEmpty(reference)) { if (!string.IsNullOrEmpty(defaultReference)) { reference = defaultReference; } else { return ""; } } return reference; } private static bool ShouldUpdateBatchReference() { string answer = GetUserInput("Update Batch Reference [True,False] (Default = False)"); if (string.IsNullOrEmpty(answer)) return false; return answer.Substring(0, 1).ToUpper().Equals("T", StringComparison.InvariantCultureIgnoreCase); } private static string GetBatchReference(bool displayOptional) { return GetUserInput(string.Format("\r\nPlease Enter Batch Reference ({0})", (displayOptional) ? "Optional" : "Required")); } private void ChipDnaClientLibOnVoiceReferral(object sender, EventParameters voiceReferralEventArgs) { // Console.WriteLine("Voice Referral Event Parameters: {0}", voiceReferralEventArgs); var processSource = ""; var telephoneNumber = ""; foreach (var parameter in voiceReferralEventArgs.ToList()) { if (ParameterKeys.VoiceReferralVerificationSource.Equals(parameter.Key)) { try { processSource = parameter.Value; } catch (Exception ex) { PrintErrorEvent(new Exception("Error Parsing Voice Referral Source.", ex)); } } if (ParameterKeys.VoiceReferralTelephoneNumber.Equals(parameter.Key)) { telephoneNumber = parameter.Value; } } if (processSource.Equals("Pos")) { Console.WriteLine("Voice Referral Event Parameters: {0}{1} *Waiting For Voice Referral Continue Command --> Press 'F' To Continue*", voiceReferralEventArgs, "\r\n"); voiceReferralRequired = true; } } private void ChipDnaClientLibOnDeferredAuthorization(object sender, EventParameters deferredAuthorizationEventArgs) { var pinBlockPresent = false; foreach (var parameter in deferredAuthorizationEventArgs.ToList()) { if (ParameterKeys.PinBlockPresent.Equals(parameter.Key)) { try { pinBlockPresent = bool.Parse(parameter.Value); } catch (Exception ex) { PrintErrorEvent(new Exception("Error Parsing Pin Block Present.", ex)); } } } Console.WriteLine("Deferred Authorization Event Parameters: {0}{1} *Waiting for Deferred Authorization Continue Command --> Press 'D' To Continue*", deferredAuthorizationEventArgs, "\r\n"); deferredAuthorizationRequested = true; } private void ChipDnaClientLibOnTransactionFinished(object sender, EventParameters transactionFinishedEventArgs) { var result = new Dictionary(); var transactionFinishedStringBuilder = new StringBuilder(); transactionFinishedStringBuilder.Append("Transaction Finished Event Parameters: "); string reference = null; bool signatureRequired = false; if (transactionFinishedEventArgs != null) { ExtractParametersForLogging(transactionFinishedEventArgs, out StringBuilder strBuilder, out ReceiptData receiptDataMerchant, out ReceiptData receiptDataCardholder); transactionFinishedStringBuilder.Append(strBuilder); if (transactionFinishedEventArgs.GetValue(ParameterKeys.SignatureVerificationRequired, out string signatureVerification)) { try { signatureRequired = Convert.ToBoolean(signatureVerification); } catch (FormatException) { Console.WriteLine("Formatting Error With Signature Required"); } } if (transactionFinishedEventArgs.GetValue(ParameterKeys.Reference, out string referenceValue)) { reference = referenceValue; } Console.WriteLine(transactionFinishedStringBuilder); if (signatureRequired) { Console.WriteLine("\n Signature Verification Required For [ref={0}]\n", reference); } if (transactionFinishedEventArgs.GetValue(ParameterKeys.ErrorDescription, out string errorDescription)) { Console.WriteLine("ErrorDescription: {0}", errorDescription); } PrintReceiptData(receiptDataMerchant); PrintReceiptData(receiptDataCardholder); // 🔥 SET THE RESULT FOR HTTP LISTENER foreach (var param in transactionFinishedEventArgs.ToList()) { string value = param.Key.Equals(ParameterKeys.CardHashCollection) ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value)) : param.Value; result[param.Key] = value; } if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted) { TransactionCompletionSource.SetResult(result); } } ResetTransaction(); PrintConsoleCommands(); } private void ChipDnaClientLibOnSignatureVerificationRequested(object sender, EventParameters signatureVerificationRequestedEventArgs) { var transactionFinishedStringBuilder = new StringBuilder(); transactionFinishedStringBuilder.Append("Signature Verification Requested Event Parameters: "); string reference = null; bool signatureRequired = false; if (signatureVerificationRequestedEventArgs != null) { ExtractParametersForLogging(signatureVerificationRequestedEventArgs, out StringBuilder strBuilder, out ReceiptData receiptDataMerchant, out ReceiptData receiptDataCardholder); if (strBuilder.Length == 0) { transactionFinishedStringBuilder.Append("[There are no set parameters for this event]"); } else { transactionFinishedStringBuilder.Append(strBuilder); } string signatureVerification; if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.SignatureVerificationRequired, out signatureVerification)) { try { signatureRequired = Convert.ToBoolean(signatureVerification); } catch (FormatException) { Console.WriteLine("Formating Error With Signature Required"); } } string referenceValue; if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.Reference, out referenceValue)) { reference = referenceValue; } Console.WriteLine(transactionFinishedStringBuilder); if (signatureRequired) { Console.WriteLine("\n Signature Verification Required For [ref={0}]\n", reference); } string errorDescription; if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.ErrorDescription, out errorDescription)) { Console.WriteLine("ErrorDescription: {0}", errorDescription); } PrintReceiptData(receiptDataMerchant); PrintReceiptData(receiptDataCardholder); Console.WriteLine("*Waiting for Continue Signature Verification Command --> Press 'J' To Continue*"); } } private void ChipDnaClientLibOnTransactionPause(object sender, EventParameters transactionPauseEventArgs) { Console.WriteLine("Transaction Pause Event Parameters: {0}{1} *Waiting For Continue Command --> Press 'L' To Continue*", transactionPauseEventArgs, "\n"); } private void ChipDnaClientLibOnTransactionUpdate(object sender, EventParameters transactionUpdateEventArgs) { Console.WriteLine("Transaction Update Event Parameters: {0}", transactionUpdateEventArgs); } private void ChipDnaClientLibOnCardNotification(object sender, EventParameters cardNotificationEventArgs) { Console.WriteLine("Card Notification Event Parameters: {0}", cardNotificationEventArgs); } private void ChipDnaClientLibOnCardDetails(object sender, EventParameters cardDetailsEventArgs) { var stringBuilder = new StringBuilder(); foreach (var param in cardDetailsEventArgs.ToList()) { var value = (param.Key.Equals(ParameterKeys.CardHashCollection)) ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value)) : param.Value; stringBuilder.Append(string.Format("[{0}, {1}]", param.Key, value)); } Console.WriteLine("Card Details Event Parameters: {0}", stringBuilder); getCardDetailsInProgress = false; PrintConsoleCommands(); } private void ChipDnaClientLibOnUpdateTransactionParametersFinishedEvent(object sender, EventParameters updateTransactionUpdateFinishedEvent) { Console.WriteLine("Update Transaction Parameters Finished Event: {0}", updateTransactionUpdateFinishedEvent); } private void ChipDnaClientLibOnPaymentDeviceAvailabilityChangeEvent(object sender, EventParameters connectionEventArgs) { var stringBuilder = new StringBuilder(); connectionEventArgs.ToList().ForEach(param => { if (param.Key.Equals(ParameterKeys.AvailabilityErrorInformation, StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(param.Value)) { var paymentErrorList = ChipDnaStatus.ParseFromAvailabilityErrorInformation(param.Value); stringBuilder.Append(string.Format("[{0}, ", param.Key)); foreach (var error in paymentErrorList) { stringBuilder.Append(error); } stringBuilder.Append("]"); } else { stringBuilder.Append(string.Format("[{0}]", param.ToString())); } }); Console.WriteLine("{0}: {1}", "Payment Device Availability Change Event", stringBuilder); } private void ChipDnaClientLibOnTmsUpdateEvent(object sender, EventParameters tmsUpdateEventParameters) { Console.WriteLine("Tms Update Event Parameters: {0}", tmsUpdateEventParameters); } private void ChipDnaClientLibOnPassThruResponseEvent(object sender, EventParameters passThruResponseEventParameters) { Console.WriteLine("Pass Thru Response Event Parameters: {0}", passThruResponseEventParameters); } private void ChipDnaClientLibOnOpenPassThruSessionResponseEvent(object sender, EventParameters openPassThruSessionResponseEventParameters) { Console.WriteLine("Open Pass Thru Session Response Event Parameters: {0}", openPassThruSessionResponseEventParameters); } private void ChipDnaClientLibOnDccRateInformationEvent(object sender, EventParameters dccRateInformationEventParameters) { Console.WriteLine("Dcc Rate Information Event Parameters: {0}", dccRateInformationEventParameters); } private void ChipDnaClientLibOnErrorEvent(object sender, ClientHelperErrorEventArgs errorEventArgs) { PrintErrorEvent(errorEventArgs.RaisedException); } private void ChipDnaClientLibOnConnectAndConfigureEvent(object sender, EventParameters connectAndConfigureEventParameters) { Console.WriteLine("Connect And Configure Event Parameters: {0}", connectAndConfigureEventParameters); } private void ChipDnaClientLibOnConfigurationUpdateEvent(object sender, EventParameters configurationUpdateEventParameters) { Console.WriteLine("Configuration Update Event Parameters: {0}", configurationUpdateEventParameters); } private void ChipDnaClientLibOnRequestQueueRunCompletedEvent(object sender, EventParameters configurationUpdateEventParameters) { var requestQueueRunCompletedObject = RequestQueueRunCompletedObject.ParseFromResponse(configurationUpdateEventParameters); Console.WriteLine(requestQueueRunCompletedObject.ToString()); } private string FormatCardHashCollection(List cardHashes) { var stringBuilder = new StringBuilder(); cardHashes.ForEach(hash => stringBuilder.Append(hash)); return stringBuilder.ToString(); } private void ExtractParametersForLogging(EventParameters eventParameter, out StringBuilder strBuilder, out ReceiptData receiptDataMerchant, out ReceiptData receiptDataCardholder) { strBuilder = new StringBuilder(); receiptDataMerchant = null; receiptDataCardholder = null; foreach (Parameter param in eventParameter.ToList()) { if (param.Key.Equals(ParameterKeys.ReceiptDataMerchant)) { receiptDataMerchant = ReceiptData.GetReceiptDataFromXml(param.Value); } else if (param.Key.Equals(ParameterKeys.ReceiptDataCardholder)) { receiptDataCardholder = ReceiptData.GetReceiptDataFromXml(param.Value); } else { var value = (param.Key.Equals(ParameterKeys.CardHashCollection)) ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value)) : param.Value; strBuilder.AppendFormat(null, " [{0}={1}] ", param.Key, value); } } } } }