added functionality to commit transactions

This commit is contained in:
yurii 2025-08-30 19:42:07 +01:00
parent 8e268c5e60
commit b4dcc35a8b
2 changed files with 183 additions and 65 deletions

View File

@ -246,11 +246,11 @@ namespace Creditcall.ChipDna.Client
if (response.GetValue(ParameterKeys.Errors, out errors) && !string.IsNullOrEmpty(errors)) if (response.GetValue(ParameterKeys.Errors, out errors) && !string.IsNullOrEmpty(errors))
{ {
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;
} }
} }

View File

@ -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 try
{ {
var serializer = new XmlSerializer(typeof(TransactionPayload));
TransactionPayload payload = (TransactionPayload)serializer.Deserialize(request.InputStream);
Console.WriteLine($"Received transaction request: Amount={payload.amount}, Type={payload.transactionType}");
client.TransactionCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
client.PerformStartTransaction(payload.amount, payload.transactionType);
// Wait synchronously for result
Dictionary<string, string> transactionResult = client.TransactionCompletionSource.Task.Result;
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 handling request: " + ex.Message);
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
context.Response.Close();
} }
finally catch { }
{
// 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();
}
}
else
{
context.Response.StatusCode = 405;
context.Response.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}");
client.TransactionCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
// start transaction (non-blocking)
client.PerformStartTransaction(payload.amount, payload.transactionType);
// Wait for completion with timeout
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));
context.Response.ContentType = "application/xml";
var wrappedResult = new SerializableKeyValueList(transactionResult);
responseSerializer.Serialize(context.Response.OutputStream, wrappedResult);
}
catch (Exception ex)
{
Console.WriteLine("Error in start-transaction: " + ex.Message);
context.Response.StatusCode = 500;
}
finally
{
context.Response.OutputStream.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; }
} }
} }