Compare commits

...

5 Commits

4 changed files with 265 additions and 87 deletions

View File

@ -218,6 +218,7 @@
<Reference Include="System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
@ -247,6 +248,7 @@
<Compile Include="Client.cs" />
<Compile Include="ClientApp.cs" />
<Compile Include="ConfigFileParser.cs" />
<Compile Include="ErrorLogger.cs" />
<Compile Include="ExtraParameter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Serialization;
namespace Creditcall.ChipDna.Client
{
@ -249,7 +250,6 @@ namespace Creditcall.ChipDna.Client
{
{ ParameterKeys.Errors, errors },
{ ParameterKeys.Reference, reference },
{ ParameterKeys.TransactionResult, "ERROR" }
};
if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted)
@ -318,13 +318,13 @@ namespace Creditcall.ChipDna.Client
PerformStartTransaction("250", "");
break;
case "C":
PerformConfirmTransaction("", out TransactionConfirmation confirmation);
PerformConfirmTransaction("", "");
break;
case "V":
PerformVoidTransaction();
PerformVoidTransaction("");
break;
case "I":
PerformGetTransactionInformation();
PerformGetTransactionInformation("");
break;
case "K":
PerformReleaseCard();
@ -471,14 +471,14 @@ namespace Creditcall.ChipDna.Client
}
}
private void GetStatus()
public Dictionary<string, string> GetStatus()
{
Console.WriteLine(ParameterKeys.VersionInformation + "\n" + ParameterKeys.ChipDnaStatus + "\n" + ParameterKeys.PaymentDeviceStatus + "\n" + ParameterKeys.RequestQueueStatus + "\n" + ParameterKeys.TmsStatus + "\n" + ParameterKeys.PaymentPlatformStatus);
var transactionResult = new Dictionary<string, string>();
var parameterSet = new ParameterSet();
var responseParameter = new List<Parameter>();
parameterSet.Add(GetExtraParams("GetStatus"));
Response response = chipDnaClientLib.GetStatus(GetRequestedParameterSet(parameterSet));
Response response = chipDnaClientLib.GetStatus(parameterSet);
string errors;
var serverStatus = (ChipDnaStatus) null;
@ -495,6 +495,13 @@ namespace Creditcall.ChipDna.Client
{
Console.WriteLine(serverStatus);
}
responseParameter = response.ToList();
responseParameter.ForEach(parameter =>
{
transactionResult.Add(parameter.Key, parameter.Value);
});
return transactionResult;
}
private void GetMerchantData()
@ -635,19 +642,23 @@ namespace Creditcall.ChipDna.Client
}
}
public void PerformConfirmTransaction(string reference, out TransactionConfirmation confirmation)
public Dictionary<string, string> PerformConfirmTransaction(string reference, string amount)
{
confirmation = new TransactionConfirmation();
var confirmationResult = new Dictionary<string, string>();
var parameters = new ParameterSet();
parameters.Add(ParameterKeys.Reference, reference);
parameters.Add(ParameterKeys.Amount, amount);
parameters.Add(GetExtraParams("ConfirmTransaction"));
var response = chipDnaClientLib.ConfirmTransaction(parameters);
var responseParameter = response.ToList();
responseParameter.ForEach(parameter =>
{
confirmationResult.Add(parameter.Key, parameter.Value);
});
string receipt, result;
if (response.GetValue(ParameterKeys.TransactionResult, out result))
{
Console.WriteLine("Confirmed Transaction Result: {0}", result);
confirmation.Result = result;
}
if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
{
@ -656,21 +667,19 @@ namespace Creditcall.ChipDna.Client
if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
{
PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
confirmation.ReceiptDataCardholder = receipt;
}
string errors;
if (response.GetValue(ParameterKeys.Errors, out errors))
{
Console.WriteLine(ErrorsString("ConfirmTransaction Errors", errors));
confirmation.Errors = errors;
}
string errorDescription;
if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
{
Console.WriteLine("ErrorDescription: {0}", errorDescription);
confirmation.ErrorDescription = errorDescription;
}
return confirmationResult;
}
private void DoUpdateTransaction()
@ -739,18 +748,13 @@ namespace Creditcall.ChipDna.Client
}
}
private void PerformVoidTransaction()
public Dictionary<string, string> PerformVoidTransaction(string referenceValue)
{
var reference = GetReference(true, "");
var voidReason = GetUserInput("\r\nPlease Enter Void Reason (Optional)");
var transactionResult = new Dictionary<string, string>();
var parameters = new ParameterSet();
if (!string.IsNullOrEmpty(reference))
if (!string.IsNullOrEmpty(referenceValue))
{
parameters.Add(ParameterKeys.Reference, reference);
}
if (!String.IsNullOrEmpty(voidReason))
{
parameters.Add(ParameterKeys.VoidReason, voidReason);
parameters.Add(ParameterKeys.Reference, referenceValue);
}
parameters.Add(GetExtraParams("VoidTransaction"));
@ -778,12 +782,18 @@ namespace Creditcall.ChipDna.Client
{
Console.WriteLine("ErrorDescription: {0}", errorDescription);
}
var responseParameter = response.ToList();
responseParameter.ForEach(parameter =>
{
transactionResult.Add(parameter.Key, parameter.Value);
});
return transactionResult;
}
private void PerformGetTransactionInformation()
public Dictionary<string, string> PerformGetTransactionInformation(string referenceValue)
{
var referenceValue = GetReference(true, "");
var result = new Dictionary<string, string>();
var parameters = new ParameterSet();
if (!string.IsNullOrEmpty(referenceValue))
{
@ -794,18 +804,11 @@ namespace Creditcall.ChipDna.Client
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);
}
result.Add(parameter.Key, parameter.Value);
});
return result;
}
private static string GetUserInput(string prompt)
@ -1458,6 +1461,7 @@ namespace Creditcall.ChipDna.Client
private void ChipDnaClientLibOnPaymentDeviceAvailabilityChangeEvent(object sender, EventParameters connectionEventArgs)
{
var transactionResult = new Dictionary<string, string>();
var stringBuilder = new StringBuilder();
connectionEventArgs.ToList().ForEach(param =>
{
@ -1475,7 +1479,9 @@ namespace Creditcall.ChipDna.Client
{
stringBuilder.Append(string.Format("[{0}]", param.ToString()));
}
transactionResult.Add(param.Key, param.Value);
});
_ = ErrorLogger.SendLogErrorAsync(transactionResult);
Console.WriteLine("{0}: {1}", "Payment Device Availability Change Event", stringBuilder);
}
@ -1501,6 +1507,11 @@ namespace Creditcall.ChipDna.Client
private void ChipDnaClientLibOnErrorEvent(object sender, ClientHelperErrorEventArgs errorEventArgs)
{
var result = new Dictionary<string, string>
{
{ ParameterKeys.Errors, errorEventArgs.RaisedException.Message}
};
_ = ErrorLogger.SendLogErrorAsync(result);
PrintErrorEvent(errorEventArgs.RaisedException);
}

View File

@ -19,6 +19,7 @@ namespace Creditcall.ChipDna.Client
private const int DefaultPort = 1869;
private const string DefaultIpAddress = "127.0.0.1";
private const bool DefaultSaveReceipt = false;
private const int transactionTimeoutSeconds = 300;
private static Client client;
@ -72,10 +73,18 @@ namespace Creditcall.ChipDna.Client
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://127.0.0.1:18181/start-transaction/");
listener.Prefixes.Add("http://127.0.0.1:18181/confirm-transaction/");
listener.Prefixes.Add("http://127.0.0.1:18181/transaction-information/");
listener.Prefixes.Add("http://127.0.0.1:18181/void-transaction/");
listener.Prefixes.Add("http://127.0.0.1:18181/chipdna-status/");
listener.Start();
Console.WriteLine("Listening on:");
Console.WriteLine(" http://127.0.0.1:18181/start-transaction/");
Console.WriteLine(" http://127.0.0.1:18181/confirm-transaction/");
Console.WriteLine(" http://127.0.0.1:18181/transaction-information/");
Console.WriteLine(" http://127.0.0.1:18181/void-transaction/");
Console.WriteLine(" http://127.0.0.1:18181/chipdna-status/");
while (true)
{
@ -95,6 +104,22 @@ namespace Creditcall.ChipDna.Client
if (rawUrl.StartsWith("/start-transaction", StringComparison.OrdinalIgnoreCase))
{
HandleStartTransaction(context);
} else if (rawUrl.StartsWith("/confirm-transaction", StringComparison.OrdinalIgnoreCase))
{
HandleConfirmTransaction(context);
}
else if (rawUrl.StartsWith("/transaction-information", StringComparison.OrdinalIgnoreCase))
{
HandleTransactionInformation(context);
}
else if (rawUrl.StartsWith("/void-transaction", StringComparison.OrdinalIgnoreCase))
{
HandleVoidTransaction(context);
}
else if (rawUrl.StartsWith("/chipdna-status", StringComparison.OrdinalIgnoreCase))
{
HandleGetStatus(context);
}
else
{
@ -115,21 +140,127 @@ namespace Creditcall.ChipDna.Client
}
}
private static void HandleGetStatus(HttpListenerContext context)
{
try
{
var statusResult = client.GetStatus();
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(statusResult);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
catch (Exception ex)
{
Console.WriteLine("Error in -status: " + ex.Message);
context.Response.StatusCode = 500;
}
finally
{
context.Response.OutputStream.Close();
}
}
private static void HandleTransactionInformation(HttpListenerContext context)
{
try
{
var serializer = new XmlSerializer(typeof(TransactionReferenceRequest));
TransactionReferenceRequest req = (TransactionReferenceRequest)serializer.Deserialize(context.Request.InputStream);
var transactionResult = client.PerformGetTransactionInformation(req.TransactionReference);
// Return the final key/value list
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(transactionResult);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
catch (Exception ex)
{
Console.WriteLine("Error in transaction-information: " + ex.Message);
context.Response.StatusCode = 500;
}
finally
{
context.Response.OutputStream.Close();
}
}
private static void HandleVoidTransaction(HttpListenerContext context)
{
try
{
var serializer = new XmlSerializer(typeof(TransactionReferenceRequest));
TransactionReferenceRequest req = (TransactionReferenceRequest)serializer.Deserialize(context.Request.InputStream);
var transactionResult = client.PerformVoidTransaction(req.TransactionReference);
// Return the final key/value list
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(transactionResult);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
catch (Exception ex)
{
Console.WriteLine("Error in transaction-information: " + ex.Message);
context.Response.StatusCode = 500;
}
finally
{
context.Response.OutputStream.Close();
}
}
private static void HandleConfirmTransaction(HttpListenerContext context)
{
try
{
var serializer = new XmlSerializer(typeof(ConfirmTransactionRequest));
ConfirmTransactionRequest payload = (ConfirmTransactionRequest)serializer.Deserialize(context.Request.InputStream);
Console.WriteLine($"Received confirm transaction: Amount={payload.Amount}, TransactionReference={payload.TransactionReference}");
var confirmationResult = client.PerformConfirmTransaction(payload.TransactionReference, payload.Amount);
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(confirmationResult);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
catch (Exception ex)
{
var errorMap = new Dictionary<string, string>
{
{ ParameterKeys.TransactionResult, "ERROR" },
{ ParameterKeys.Errors, $"ConfirmTransaction failed: {ex.Message}" }
};
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(errorMap);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
finally
{
context.Response.OutputStream.Close();
}
}
private static void HandleStartTransaction(HttpListenerContext context)
{
const int transactionTimeoutSeconds = 300;
try
{
var serializer = new XmlSerializer(typeof(TransactionPayload));
TransactionPayload payload = (TransactionPayload)serializer.Deserialize(context.Request.InputStream);
Console.WriteLine($"Received start transaction: Amount={payload.amount}, Type={payload.transactionType}");
Console.WriteLine($"Received transaction request: Amount={payload.Amount}, Type={payload.TransactionType}");
client.TransactionCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
// start transaction (non-blocking)
client.PerformStartTransaction(payload.amount, payload.transactionType);
client.PerformStartTransaction(payload.Amount, payload.TransactionType);
// Wait for completion with timeout
var task = client.TransactionCompletionSource.Task;
@ -168,45 +299,6 @@ namespace Creditcall.ChipDna.Client
return;
}
// If it's a SALE and approved, call ConfirmTransaction and attach its XML
transactionResult.TryGetValue(ParameterKeys.TransactionType, out string txType);
transactionResult.TryGetValue(ParameterKeys.TransactionResult, out string txResult);
transactionResult.TryGetValue(ParameterKeys.Reference, out string txReference);
if (!string.IsNullOrEmpty(txType) && txType.Equals("SALE", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(txResult) && txResult.Equals("APPROVED", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(txReference))
{
try
{
TransactionConfirmation confirmation;
client.PerformConfirmTransaction(txReference, out confirmation);
// Keep original fields, but also add confirmation-prefixed fields
transactionResult["CONFIRM_RESULT"] = confirmation?.Result ?? "";
transactionResult["CONFIRM_ERRORS"] = confirmation?.Errors ?? "";
if (!string.IsNullOrEmpty(confirmation.ErrorDescription))
{
transactionResult["CONFIRM_ERRORS"] = $"{confirmation.Errors} {confirmation.ErrorDescription}";
}
if (!string.IsNullOrEmpty(confirmation?.ReceiptDataCardholder))
{
transactionResult[ParameterKeys.ReceiptDataCardholder] = confirmation.ReceiptDataCardholder;
}
// XML (omit declaration so it can be embedded)
var xml = SerializeToXml(confirmation, omitXmlDeclaration: true);
transactionResult["TRANSACTION_CONFIRMATION"] = xml;
}
catch (Exception ex)
{
// preserve original transaction result but add an error key
transactionResult["CONFIRM_RESULT"] = "ERROR";
transactionResult["CONFIRM_ERRORS"] = $"ConfirmTransaction failed: {ex.Message}";
}
}
// Return the final key/value list as before
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(transactionResult);
@ -221,6 +313,7 @@ namespace Creditcall.ChipDna.Client
{
context.Response.OutputStream.Close();
}
}
private static string GetAbsolutePath(string fileName)
@ -308,11 +401,8 @@ namespace Creditcall.ChipDna.Client
[XmlRoot("TransactionPayload")]
public class TransactionPayload
{
public string amount { get; set; }
public string transactionType { get; set; }
// For confirm transaction
public string transactionReference { get; set; }
public string Amount { get; set; }
public string TransactionType { get; set; }
}
[XmlRoot("TransactionConfirmation")]
@ -322,6 +412,20 @@ namespace Creditcall.ChipDna.Client
public string Errors { get; set; }
public string ErrorDescription { get; set; }
public string ReceiptDataCardholder { get; set; }
}
[XmlRoot("TransactionReferenceRequest")]
public class TransactionReferenceRequest
{
[XmlElement("TransactionReference")]
public string TransactionReference { get; set; }
}
[XmlRoot("ConfirmTransactionRequest")]
public class ConfirmTransactionRequest
{
public string Amount { get; set; }
public string TransactionReference { get; set; }
}
}

61
ErrorLogger.cs Normal file
View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
namespace Creditcall.ChipDna.Client
{
public static class ErrorLogger
{
private static readonly HttpClient httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
public static async Task SendLogErrorAsync(Dictionary<string, string> statusResult)
{
try
{
if (statusResult == null || statusResult.Count == 0)
return;
var url = "http://127.0.0.1:9091/logerror";
var wrappedResult = new SerializableKeyValueList(statusResult);
var serializer = new XmlSerializer(typeof(SerializableKeyValueList));
var ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty);
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = false,
Indent = false,
Encoding = new UTF8Encoding(false)
};
using (var ms = new MemoryStream())
{
using (var xw = XmlWriter.Create(ms, settings))
{
serializer.Serialize(xw, wrappedResult, ns);
}
var content = new ByteArrayContent(ms.ToArray());
content.Headers.ContentType =
new MediaTypeHeaderValue("application/xml");
await httpClient.PostAsync(url, content);
}
}
catch (Exception ex)
{
Console.WriteLine("SendLogErrorAsync failed: " + ex.Message);
}
}
}
}