added functionality to commit transactions
This commit is contained in:
parent
8e268c5e60
commit
b4dcc35a8b
35
Client.cs
35
Client.cs
@ -247,9 +247,9 @@ namespace Creditcall.ChipDna.Client
|
|||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>
|
var result = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "ERROR", errors },
|
{ ParameterKeys.Errors, errors },
|
||||||
{ "REFERENCE", reference },
|
{ ParameterKeys.Reference, reference },
|
||||||
{ "TRANSACTION_RESULT", "ERROR" }
|
{ ParameterKeys.TransactionResult, "ERROR" }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted)
|
if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted)
|
||||||
@ -318,7 +318,7 @@ namespace Creditcall.ChipDna.Client
|
|||||||
PerformStartTransaction("250", "");
|
PerformStartTransaction("250", "");
|
||||||
break;
|
break;
|
||||||
case "C":
|
case "C":
|
||||||
PerformConfirmTransaction();
|
PerformConfirmTransaction("", out TransactionConfirmation confirmation);
|
||||||
break;
|
break;
|
||||||
case "V":
|
case "V":
|
||||||
PerformVoidTransaction();
|
PerformVoidTransaction();
|
||||||
@ -635,37 +635,19 @@ namespace Creditcall.ChipDna.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PerformConfirmTransaction()
|
public void PerformConfirmTransaction(string reference, out TransactionConfirmation confirmation)
|
||||||
{
|
{
|
||||||
var reference = GetReference(true, "");
|
confirmation = new TransactionConfirmation();
|
||||||
var amount = GetAmount(false);
|
|
||||||
var gratuity = GetGratuity(false);
|
|
||||||
var closeTransaction = GetCloseTransaction(false);
|
|
||||||
|
|
||||||
var parameters = new ParameterSet();
|
var parameters = new ParameterSet();
|
||||||
if (!string.IsNullOrEmpty(reference))
|
|
||||||
{
|
|
||||||
parameters.Add(ParameterKeys.Reference, 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"));
|
parameters.Add(GetExtraParams("ConfirmTransaction"));
|
||||||
var response = chipDnaClientLib.ConfirmTransaction(parameters);
|
var response = chipDnaClientLib.ConfirmTransaction(parameters);
|
||||||
string receipt, result;
|
string receipt, result;
|
||||||
if (response.GetValue(ParameterKeys.TransactionResult, out result))
|
if (response.GetValue(ParameterKeys.TransactionResult, out result))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Confirmed Transaction Result: {0}", result);
|
Console.WriteLine("Confirmed Transaction Result: {0}", result);
|
||||||
|
confirmation.Result = result;
|
||||||
}
|
}
|
||||||
if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
|
if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
|
||||||
{
|
{
|
||||||
@ -674,17 +656,20 @@ namespace Creditcall.ChipDna.Client
|
|||||||
if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
|
if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
|
||||||
{
|
{
|
||||||
PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
|
PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
|
||||||
|
confirmation.ReceiptDataCardholder = receipt;
|
||||||
}
|
}
|
||||||
string errors;
|
string errors;
|
||||||
if (response.GetValue(ParameterKeys.Errors, out errors))
|
if (response.GetValue(ParameterKeys.Errors, out errors))
|
||||||
{
|
{
|
||||||
Console.WriteLine(ErrorsString("ConfirmTransaction Errors", errors));
|
Console.WriteLine(ErrorsString("ConfirmTransaction Errors", errors));
|
||||||
|
confirmation.Errors = errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
string errorDescription;
|
string errorDescription;
|
||||||
if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
|
if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
|
||||||
{
|
{
|
||||||
Console.WriteLine("ErrorDescription: {0}", errorDescription);
|
Console.WriteLine("ErrorDescription: {0}", errorDescription);
|
||||||
|
confirmation.ErrorDescription = errorDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
175
ClientApp.cs
175
ClientApp.cs
@ -5,7 +5,9 @@ using System.Net;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Creditcall.ChipDna.Client
|
namespace Creditcall.ChipDna.Client
|
||||||
{
|
{
|
||||||
@ -72,56 +74,154 @@ namespace Creditcall.ChipDna.Client
|
|||||||
listener.Prefixes.Add("http://127.0.0.1:18181/start-transaction/");
|
listener.Prefixes.Add("http://127.0.0.1:18181/start-transaction/");
|
||||||
listener.Start();
|
listener.Start();
|
||||||
|
|
||||||
Console.WriteLine("Listening on http://127.0.0.1:18181/start-transaction/");
|
Console.WriteLine("Listening on:");
|
||||||
|
Console.WriteLine(" http://127.0.0.1:18181/start-transaction/");
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
HttpListenerContext context = listener.GetContext();
|
HttpListenerContext context = listener.GetContext();
|
||||||
HttpListenerRequest request = context.Request;
|
HttpListenerRequest request = context.Request;
|
||||||
|
string rawUrl = request.Url.AbsolutePath;
|
||||||
|
|
||||||
if (request.HttpMethod == "POST")
|
try
|
||||||
{
|
{
|
||||||
|
if (request.HttpMethod != "POST")
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 405;
|
||||||
|
context.Response.Close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawUrl.StartsWith("/start-transaction", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
HandleStartTransaction(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 404;
|
||||||
|
context.Response.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unhandled server error: " + ex.Message);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
context.Response.Close();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleStartTransaction(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
const int transactionTimeoutSeconds = 300;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var serializer = new XmlSerializer(typeof(TransactionPayload));
|
var serializer = new XmlSerializer(typeof(TransactionPayload));
|
||||||
TransactionPayload payload = (TransactionPayload)serializer.Deserialize(request.InputStream);
|
TransactionPayload payload = (TransactionPayload)serializer.Deserialize(context.Request.InputStream);
|
||||||
|
|
||||||
Console.WriteLine($"Received transaction request: Amount={payload.amount}, Type={payload.transactionType}");
|
Console.WriteLine($"Received start transaction: Amount={payload.amount}, Type={payload.transactionType}");
|
||||||
|
|
||||||
client.TransactionCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
|
client.TransactionCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
|
||||||
|
|
||||||
|
// start transaction (non-blocking)
|
||||||
client.PerformStartTransaction(payload.amount, payload.transactionType);
|
client.PerformStartTransaction(payload.amount, payload.transactionType);
|
||||||
|
|
||||||
// Wait synchronously for result
|
// Wait for completion with timeout
|
||||||
Dictionary<string, string> transactionResult = client.TransactionCompletionSource.Task.Result;
|
var task = client.TransactionCompletionSource.Task;
|
||||||
|
if (!task.Wait(TimeSpan.FromSeconds(transactionTimeoutSeconds)))
|
||||||
|
{
|
||||||
|
// timeout
|
||||||
|
var errorMap = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ ParameterKeys.TransactionResult, "TIMEOUT" },
|
||||||
|
{ ParameterKeys.Errors, "Transaction timed out waiting for device response" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var wrappedTimeout = new SerializableKeyValueList(errorMap);
|
||||||
|
context.Response.ContentType = "application/xml";
|
||||||
|
new XmlSerializer(typeof(SerializableKeyValueList)).Serialize(context.Response.OutputStream, wrappedTimeout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task completed — handle possible exception
|
||||||
|
Dictionary<string, string> transactionResult;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
transactionResult = task.Result; // safe now, Wait returned
|
||||||
|
}
|
||||||
|
catch (AggregateException agg)
|
||||||
|
{
|
||||||
|
var first = agg.Flatten().InnerExceptions[0];
|
||||||
|
var errMap = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ ParameterKeys.TransactionResult, "ERROR" },
|
||||||
|
{ ParameterKeys.Errors, $"Transaction task faulted: {first.Message}" }
|
||||||
|
};
|
||||||
|
var wrapped = new SerializableKeyValueList(errMap);
|
||||||
|
context.Response.ContentType = "application/xml";
|
||||||
|
new XmlSerializer(typeof(SerializableKeyValueList)).Serialize(context.Response.OutputStream, wrapped);
|
||||||
|
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));
|
var responseSerializer = new XmlSerializer(typeof(SerializableKeyValueList));
|
||||||
context.Response.ContentType = "application/xml";
|
context.Response.ContentType = "application/xml";
|
||||||
|
|
||||||
var wrappedResult = new SerializableKeyValueList(transactionResult);
|
var wrappedResult = new SerializableKeyValueList(transactionResult);
|
||||||
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
|
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error handling request: " + ex.Message);
|
Console.WriteLine("Error in start-transaction: " + ex.Message);
|
||||||
context.Response.StatusCode = 500;
|
context.Response.StatusCode = 500;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Remove this line if you're already writing to the stream above
|
|
||||||
// Otherwise keep it if no other usage of OutputStream occurs
|
|
||||||
context.Response.OutputStream.Close();
|
context.Response.OutputStream.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 405;
|
|
||||||
context.Response.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetAbsolutePath(string fileName)
|
private static string GetAbsolutePath(string fileName)
|
||||||
{
|
{
|
||||||
@ -154,8 +254,29 @@ namespace Creditcall.ChipDna.Client
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// helper to serialize an object to an XML string (no namespaces)
|
||||||
|
private static string SerializeToXml<T>(T obj, bool omitXmlDeclaration = true)
|
||||||
|
{
|
||||||
|
if (obj == null) return string.Empty;
|
||||||
|
var xs = new XmlSerializer(typeof(T));
|
||||||
|
var ns = new XmlSerializerNamespaces();
|
||||||
|
ns.Add(string.Empty, string.Empty); // remove xsi/xsd namespaces
|
||||||
|
|
||||||
|
var settings = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
OmitXmlDeclaration = omitXmlDeclaration,
|
||||||
|
Indent = false,
|
||||||
|
Encoding = Encoding.UTF8
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var sw = new StringWriter())
|
||||||
|
using (var xw = XmlWriter.Create(sw, settings))
|
||||||
|
{
|
||||||
|
xs.Serialize(xw, obj, ns);
|
||||||
|
return sw.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[XmlRoot("TransactionResult")]
|
[XmlRoot("TransactionResult")]
|
||||||
public class SerializableKeyValueList
|
public class SerializableKeyValueList
|
||||||
@ -184,11 +305,23 @@ namespace Creditcall.ChipDna.Client
|
|||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[XmlRoot("TransactionPayload")]
|
[XmlRoot("TransactionPayload")]
|
||||||
public class TransactionPayload
|
public class TransactionPayload
|
||||||
{
|
{
|
||||||
public string amount { get; set; }
|
public string amount { get; set; }
|
||||||
public string transactionType { get; set; }
|
public string transactionType { get; set; }
|
||||||
|
|
||||||
|
// For confirm transaction
|
||||||
|
public string transactionReference { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot("TransactionConfirmation")]
|
||||||
|
public class TransactionConfirmation
|
||||||
|
{
|
||||||
|
public string Result { get; set; }
|
||||||
|
public string Errors { get; set; }
|
||||||
|
public string ErrorDescription { get; set; }
|
||||||
|
public string ReceiptDataCardholder { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user