mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
Transaction monitoring implemented. Merchant confirmation implemented.
This commit is contained in:
parent
400a41760b
commit
5597fff1ab
@ -53,6 +53,142 @@ namespace QRBee.Core.Client
|
|||||||
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
|
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
|
||||||
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
|
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
|
||||||
|
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual System.Threading.Tasks.Task AnonymousAsync()
|
||||||
|
{
|
||||||
|
return AnonymousAsync(System.Threading.CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual async System.Threading.Tasks.Task AnonymousAsync(System.Threading.CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var urlBuilder_ = new System.Text.StringBuilder();
|
||||||
|
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/");
|
||||||
|
|
||||||
|
var client_ = _httpClient;
|
||||||
|
var disposeClient_ = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var request_ = new System.Net.Http.HttpRequestMessage())
|
||||||
|
{
|
||||||
|
request_.Method = new System.Net.Http.HttpMethod("GET");
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, urlBuilder_);
|
||||||
|
|
||||||
|
var url_ = urlBuilder_.ToString();
|
||||||
|
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, url_);
|
||||||
|
|
||||||
|
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
var disposeResponse_ = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
|
||||||
|
if (response_.Content != null && response_.Content.Headers != null)
|
||||||
|
{
|
||||||
|
foreach (var item_ in response_.Content.Headers)
|
||||||
|
headers_[item_.Key] = item_.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResponse(client_, response_);
|
||||||
|
|
||||||
|
var status_ = (int)response_.StatusCode;
|
||||||
|
if (status_ == 200)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeResponse_)
|
||||||
|
response_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeClient_)
|
||||||
|
client_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual System.Threading.Tasks.Task QRBeeAsync()
|
||||||
|
{
|
||||||
|
return QRBeeAsync(System.Threading.CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual async System.Threading.Tasks.Task QRBeeAsync(System.Threading.CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var urlBuilder_ = new System.Text.StringBuilder();
|
||||||
|
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee");
|
||||||
|
|
||||||
|
var client_ = _httpClient;
|
||||||
|
var disposeClient_ = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var request_ = new System.Net.Http.HttpRequestMessage())
|
||||||
|
{
|
||||||
|
request_.Method = new System.Net.Http.HttpMethod("GET");
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, urlBuilder_);
|
||||||
|
|
||||||
|
var url_ = urlBuilder_.ToString();
|
||||||
|
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, url_);
|
||||||
|
|
||||||
|
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
var disposeResponse_ = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
|
||||||
|
if (response_.Content != null && response_.Content.Headers != null)
|
||||||
|
{
|
||||||
|
foreach (var item_ in response_.Content.Headers)
|
||||||
|
headers_[item_.Key] = item_.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResponse(client_, response_);
|
||||||
|
|
||||||
|
var status_ = (int)response_.StatusCode;
|
||||||
|
if (status_ == 200)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeResponse_)
|
||||||
|
response_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeClient_)
|
||||||
|
client_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <returns>Success</returns>
|
/// <returns>Success</returns>
|
||||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
public virtual System.Threading.Tasks.Task<RegistrationResponse> RegisterAsync(RegistrationRequest body)
|
public virtual System.Threading.Tasks.Task<RegistrationResponse> RegisterAsync(RegistrationRequest body)
|
||||||
@ -282,6 +418,77 @@ namespace QRBee.Core.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual System.Threading.Tasks.Task ConfirmPayAsync(PaymentConfirmation body)
|
||||||
|
{
|
||||||
|
return ConfirmPayAsync(body, System.Threading.CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||||
|
/// <returns>Success</returns>
|
||||||
|
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||||
|
public virtual async System.Threading.Tasks.Task ConfirmPayAsync(PaymentConfirmation body, System.Threading.CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var urlBuilder_ = new System.Text.StringBuilder();
|
||||||
|
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee/ConfirmPay");
|
||||||
|
|
||||||
|
var client_ = _httpClient;
|
||||||
|
var disposeClient_ = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var request_ = new System.Net.Http.HttpRequestMessage())
|
||||||
|
{
|
||||||
|
var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
|
||||||
|
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
|
||||||
|
request_.Content = content_;
|
||||||
|
request_.Method = new System.Net.Http.HttpMethod("POST");
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, urlBuilder_);
|
||||||
|
|
||||||
|
var url_ = urlBuilder_.ToString();
|
||||||
|
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
|
||||||
|
|
||||||
|
PrepareRequest(client_, request_, url_);
|
||||||
|
|
||||||
|
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
var disposeResponse_ = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
|
||||||
|
if (response_.Content != null && response_.Content.Headers != null)
|
||||||
|
{
|
||||||
|
foreach (var item_ in response_.Content.Headers)
|
||||||
|
headers_[item_.Key] = item_.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResponse(client_, response_);
|
||||||
|
|
||||||
|
var status_ = (int)response_.StatusCode;
|
||||||
|
if (status_ == 200)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeResponse_)
|
||||||
|
response_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeClient_)
|
||||||
|
client_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected struct ObjectResponseResult<T>
|
protected struct ObjectResponseResult<T>
|
||||||
{
|
{
|
||||||
public ObjectResponseResult(T responseObject, string responseText)
|
public ObjectResponseResult(T responseObject, string responseText)
|
||||||
|
|||||||
11
QRBee.Core/Data/PaymentConfirmation.cs
Normal file
11
QRBee.Core/Data/PaymentConfirmation.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace QRBee.Core.Data
|
||||||
|
{
|
||||||
|
public class PaymentConfirmation
|
||||||
|
{
|
||||||
|
public string MerchantId { get; set; }
|
||||||
|
|
||||||
|
public string MerchantTransactionId { get; set; }
|
||||||
|
|
||||||
|
public string GatewayTransactionId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,8 @@
|
|||||||
{
|
{
|
||||||
public string ServerTransactionId { get; set; }
|
public string ServerTransactionId { get; set; }
|
||||||
|
|
||||||
|
public string GatewayTransactionId { get; set; }
|
||||||
|
|
||||||
public PaymentRequest PaymentRequest { get; set; }
|
public PaymentRequest PaymentRequest { get; set; }
|
||||||
|
|
||||||
public DateTime ServerTimeStampUTC { get; set; }
|
public DateTime ServerTimeStampUTC { get; set; }
|
||||||
@ -18,6 +20,6 @@
|
|||||||
/// Convert PaymentResponse to string to be encrypted and transmitted back to merchant
|
/// Convert PaymentResponse to string to be encrypted and transmitted back to merchant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Converted string</returns>
|
/// <returns>Converted string</returns>
|
||||||
public string AsDataForSignature() => $"{ServerTransactionId}|{PaymentRequest.AsString()}|{ServerTimeStampUTC:yyyy-MM-dd:HH.mm.ss.ffff}|{Success}|{RejectReason}";
|
public string AsDataForSignature() => $"{ServerTransactionId}|{GatewayTransactionId}|{PaymentRequest.AsString()}|{ServerTimeStampUTC:yyyy-MM-dd:HH.mm.ss.ffff}|{Success}|{RejectReason}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,14 @@ namespace QRBee.ViewModels
|
|||||||
|
|
||||||
if (check)
|
if (check)
|
||||||
{
|
{
|
||||||
|
var paymentConfirmation = new PaymentConfirmation
|
||||||
|
{
|
||||||
|
MerchantId = _settings.LoadSettings().ClientId,
|
||||||
|
MerchantTransactionId = response.PaymentRequest.ClientResponse.MerchantRequest.MerchantTransactionId,
|
||||||
|
GatewayTransactionId = response.GatewayTransactionId
|
||||||
|
};
|
||||||
|
|
||||||
|
await apiService.ConfirmPayAsync(paymentConfirmation);
|
||||||
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
|
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -46,5 +46,12 @@ namespace QRBee.Api.Controllers
|
|||||||
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
|
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
|
||||||
return _service.Pay(value);
|
return _service.Pay(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("ConfirmPay")]
|
||||||
|
public Task ConfirmPay([FromBody] PaymentConfirmation value)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Trying to confirm transaction with gatewayTransactionId: {value.GatewayTransactionId}");
|
||||||
|
return _service.ConfirmPay(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ builder.Services
|
|||||||
.AddSingleton<IPrivateKeyHandler, ServerPrivateKeyHandler>()
|
.AddSingleton<IPrivateKeyHandler, ServerPrivateKeyHandler>()
|
||||||
.AddSingleton<ISecurityService, SecurityService>()
|
.AddSingleton<ISecurityService, SecurityService>()
|
||||||
.AddSingleton<IPaymentGateway, PaymentGateway>()
|
.AddSingleton<IPaymentGateway, PaymentGateway>()
|
||||||
|
.AddSingleton<TransactionMonitoring>()
|
||||||
;
|
;
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
@ -44,6 +44,8 @@
|
|||||||
/// <returns>Transaction information</returns>
|
/// <returns>Transaction information</returns>
|
||||||
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
||||||
|
|
||||||
|
Task<List<TransactionInfo>> GetTransactionsByStatus(TransactionInfo.TransactionStatus status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update transaction after execution
|
/// Update transaction after execution
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -70,5 +72,7 @@
|
|||||||
/// <param name="clientId">Identifier by which certificate information will be retrieved</param>
|
/// <param name="clientId">Identifier by which certificate information will be retrieved</param>
|
||||||
/// <returns>Certificate information</returns>
|
/// <returns>Certificate information</returns>
|
||||||
Task<CertificateInfo> GetCertificateInfoByUserId(string clientId);
|
Task<CertificateInfo> GetCertificateInfoByUserId(string clientId);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,20 @@ namespace QRBee.Api.Services.Database
|
|||||||
return transaction ?? throw new ApplicationException($"Transaction with Id: {id} not found.");
|
return transaction ?? throw new ApplicationException($"Transaction with Id: {id} not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<TransactionInfo>> GetTransactionsByStatus(TransactionInfo.TransactionStatus status)
|
||||||
|
{
|
||||||
|
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
||||||
|
using var cursor = await collection.FindAsync($"{{ Status: {(int)status} }}");
|
||||||
|
|
||||||
|
var result = new List<TransactionInfo>();
|
||||||
|
|
||||||
|
while (await cursor.MoveNextAsync())
|
||||||
|
{
|
||||||
|
result.AddRange(cursor.Current);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateTransaction(TransactionInfo info)
|
public async Task UpdateTransaction(TransactionInfo info)
|
||||||
{
|
{
|
||||||
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
||||||
|
|||||||
@ -29,6 +29,9 @@ namespace QRBee.Api.Services.Database
|
|||||||
[BsonId] public string Id { get; set; }
|
[BsonId] public string Id { get; set; }
|
||||||
|
|
||||||
[BsonIgnore] public string? TransactionId => Id;
|
[BsonIgnore] public string? TransactionId => Id;
|
||||||
|
|
||||||
|
public string? GatewayTransactionId { get; set; }
|
||||||
|
public string MerchantTransactionId => Request.ClientResponse.MerchantRequest.MerchantTransactionId;
|
||||||
public DateTime ServerTimeStamp { get; set; }
|
public DateTime ServerTimeStamp { get; set; }
|
||||||
|
|
||||||
public PaymentRequest Request { get; set; }
|
public PaymentRequest Request { get; set; }
|
||||||
@ -38,6 +41,9 @@ namespace QRBee.Api.Services.Database
|
|||||||
Pending = 0,
|
Pending = 0,
|
||||||
Rejected = 1,
|
Rejected = 1,
|
||||||
Succeeded = 2,
|
Succeeded = 2,
|
||||||
|
Confirmed = 3,
|
||||||
|
Cancelled = 4,
|
||||||
|
CancelFailed =5
|
||||||
}
|
}
|
||||||
public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
|
public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
|
||||||
|
|
||||||
|
|||||||
@ -8,10 +8,14 @@ namespace QRBee.Api.Services
|
|||||||
{
|
{
|
||||||
public bool Success { get; init; }
|
public bool Success { get; init; }
|
||||||
public string? ErrorMessage { get; init; }
|
public string? ErrorMessage { get; init; }
|
||||||
|
|
||||||
|
public string? GatewayTransactionId { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPaymentGateway
|
public interface IPaymentGateway
|
||||||
{
|
{
|
||||||
Task<GatewayResponse> Payment(TransactionInfo info, ClientCardData clientCardData);
|
Task<GatewayResponse> Payment(TransactionInfo info, ClientCardData clientCardData);
|
||||||
|
|
||||||
|
Task<GatewayResponse> CancelPayment(TransactionInfo info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,5 +27,7 @@ namespace QRBee.Api.Services
|
|||||||
/// <param name="value">Payment request</param>
|
/// <param name="value">Payment request</param>
|
||||||
Task<PaymentResponse> Pay(PaymentRequest value);
|
Task<PaymentResponse> Pay(PaymentRequest value);
|
||||||
|
|
||||||
|
Task ConfirmPay(PaymentConfirmation value);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,13 +20,36 @@ internal class PaymentGateway : IPaymentGateway
|
|||||||
return Task.FromResult(new GatewayResponse
|
return Task.FromResult(new GatewayResponse
|
||||||
{
|
{
|
||||||
Success = false,
|
Success = false,
|
||||||
ErrorMessage = "Amount is too low"
|
ErrorMessage = "Amount is too low",
|
||||||
|
GatewayTransactionId = Guid.NewGuid().ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_logger.LogInformation($"Transaction with id: {info.Id} succeeded");
|
_logger.LogInformation($"Transaction with id: {info.Id} succeeded");
|
||||||
return Task.FromResult(new GatewayResponse
|
return Task.FromResult(new GatewayResponse
|
||||||
{
|
{
|
||||||
Success = true
|
Success = true,
|
||||||
|
GatewayTransactionId = Guid.NewGuid().ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<GatewayResponse> CancelPayment(TransactionInfo info)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(info.GatewayTransactionId))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Transaction with id: {info.Id} was cancelled");
|
||||||
|
return Task.FromResult(new GatewayResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "Either payment gateway isn't working or the transaction is old",
|
||||||
|
GatewayTransactionId = info.GatewayTransactionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Transaction with id: {info.Id} succeeded");
|
||||||
|
return Task.FromResult(new GatewayResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
GatewayTransactionId = info.GatewayTransactionId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,18 +19,20 @@ namespace QRBee.Api.Services
|
|||||||
private readonly IPrivateKeyHandler _privateKeyHandler;
|
private readonly IPrivateKeyHandler _privateKeyHandler;
|
||||||
private readonly IPaymentGateway _paymentGateway;
|
private readonly IPaymentGateway _paymentGateway;
|
||||||
private readonly ILogger<QRBeeAPIService> _logger;
|
private readonly ILogger<QRBeeAPIService> _logger;
|
||||||
|
private readonly TransactionMonitoring _transactionMonitoring;
|
||||||
private static readonly object _lock = new ();
|
private static readonly object _lock = new ();
|
||||||
|
|
||||||
private const int MaxNameLength = 512;
|
private const int MaxNameLength = 512;
|
||||||
private const int MaxEmailLength = 512;
|
private const int MaxEmailLength = 512;
|
||||||
|
|
||||||
public QRBeeAPIService(IStorage storage, ISecurityService securityService, IPrivateKeyHandler privateKeyHandler, IPaymentGateway paymentGateway, ILogger<QRBeeAPIService> logger)
|
public QRBeeAPIService(IStorage storage, ISecurityService securityService, IPrivateKeyHandler privateKeyHandler, IPaymentGateway paymentGateway, ILogger<QRBeeAPIService> logger, TransactionMonitoring transactionMonitoring)
|
||||||
{
|
{
|
||||||
_storage = storage;
|
_storage = storage;
|
||||||
_securityService = securityService;
|
_securityService = securityService;
|
||||||
_privateKeyHandler = privateKeyHandler;
|
_privateKeyHandler = privateKeyHandler;
|
||||||
_paymentGateway = paymentGateway;
|
_paymentGateway = paymentGateway;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_transactionMonitoring = transactionMonitoring;
|
||||||
Init(_privateKeyHandler);
|
Init(_privateKeyHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,39 +182,41 @@ namespace QRBee.Api.Services
|
|||||||
_logger.LogInformation($"Transaction=\"{tid}\" initialized");
|
_logger.LogInformation($"Transaction=\"{tid}\" initialized");
|
||||||
|
|
||||||
//8. Send client card data to a payment gateway
|
//8. Send client card data to a payment gateway
|
||||||
var res = await _paymentGateway.Payment(info, clientCardData);
|
var gatewayResponse = await _paymentGateway.Payment(info, clientCardData);
|
||||||
|
|
||||||
//9. Record transaction with result
|
//9. Record transaction with result
|
||||||
if (res.Success)
|
if (gatewayResponse.Success)
|
||||||
{
|
{
|
||||||
info.Status=TransactionInfo.TransactionStatus.Succeeded;
|
info.Status=TransactionInfo.TransactionStatus.Succeeded;
|
||||||
|
info.GatewayTransactionId=gatewayResponse.GatewayTransactionId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info.Status = TransactionInfo.TransactionStatus.Rejected;
|
info.Status = TransactionInfo.TransactionStatus.Rejected;
|
||||||
info.RejectReason = res.ErrorMessage;
|
info.RejectReason = gatewayResponse.ErrorMessage;
|
||||||
}
|
}
|
||||||
await _storage.UpdateTransaction(info);
|
await _storage.UpdateTransaction(info);
|
||||||
_logger.LogInformation($"Transaction=\"{tid}\" complete Status=\"{info.Status}\"");
|
_logger.LogInformation($"Transaction=\"{tid}\" complete Status=\"{info.Status}\"");
|
||||||
|
|
||||||
//10. Make response for merchant
|
//10. Make response for merchant
|
||||||
var response = MakePaymentResponse(value, info.TransactionId ?? "", info.Status==TransactionInfo.TransactionStatus.Succeeded, info.RejectReason);
|
var response = MakePaymentResponse(value, info.TransactionId ?? "", gatewayResponse.GatewayTransactionId ?? "", info.Status==TransactionInfo.TransactionStatus.Succeeded, info.RejectReason);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var response = MakePaymentResponse(value, "", false, e.Message);
|
var response = MakePaymentResponse(value, "", "", false, e.Message);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PaymentResponse MakePaymentResponse(PaymentRequest value, string transactionId, bool result = true, string? errorMessage = null)
|
private PaymentResponse MakePaymentResponse(PaymentRequest value, string transactionId, string gatewayTransactionId, bool result = true, string? errorMessage = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
var response = new PaymentResponse
|
var response = new PaymentResponse
|
||||||
{
|
{
|
||||||
ServerTransactionId = transactionId,
|
ServerTransactionId = transactionId,
|
||||||
|
GatewayTransactionId = gatewayTransactionId,
|
||||||
PaymentRequest = value,
|
PaymentRequest = value,
|
||||||
ServerTimeStampUTC = DateTime.UtcNow,
|
ServerTimeStampUTC = DateTime.UtcNow,
|
||||||
Success = result,
|
Success = result,
|
||||||
@ -364,5 +368,21 @@ namespace QRBee.Api.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ConfirmPay(PaymentConfirmation value)
|
||||||
|
{
|
||||||
|
var id = $"{value.MerchantId}-{value.MerchantTransactionId}";
|
||||||
|
var trans = await _storage.GetTransactionInfoByTransactionId(id);
|
||||||
|
if (trans.GatewayTransactionId == value.GatewayTransactionId)
|
||||||
|
{
|
||||||
|
trans.Status = TransactionInfo.TransactionStatus.Confirmed;
|
||||||
|
await _storage.UpdateTransaction(trans);
|
||||||
|
_logger.LogInformation($"Transaction with MerchantTransactionId: {trans.MerchantTransactionId} confirmed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"Transaction with gatewayTransactionId:{value.GatewayTransactionId} failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
QRBeeApi/Services/TransactionMonitoring.cs
Normal file
80
QRBeeApi/Services/TransactionMonitoring.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using QRBee.Api.Services.Database;
|
||||||
|
|
||||||
|
namespace QRBee.Api.Services
|
||||||
|
{
|
||||||
|
public class TransactionMonitoring
|
||||||
|
{
|
||||||
|
private readonly IStorage _storage;
|
||||||
|
private readonly IPaymentGateway _paymentGateway;
|
||||||
|
private readonly ILogger<TransactionMonitoring> _logger;
|
||||||
|
private const double Minutes = 5;
|
||||||
|
|
||||||
|
private static bool _started;
|
||||||
|
private static object _syncObject = new();
|
||||||
|
|
||||||
|
public TransactionMonitoring(IStorage storage, IPaymentGateway paymentGateway, ILogger<TransactionMonitoring> logger)
|
||||||
|
{
|
||||||
|
_storage = storage;
|
||||||
|
_paymentGateway = paymentGateway;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
if (_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_syncObject)
|
||||||
|
{
|
||||||
|
if (_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task.Run(MonitoringLoop);
|
||||||
|
_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private async Task MonitoringLoop()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting monitoring loop");
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await CheckTransactions();
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(Minutes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckTransactions()
|
||||||
|
{
|
||||||
|
var list = await _storage.GetTransactionsByStatus(TransactionInfo.TransactionStatus.Succeeded);
|
||||||
|
_logger.LogDebug($"Found {list.Count} unconfirmed transactions");
|
||||||
|
|
||||||
|
foreach (var transaction in list)
|
||||||
|
{
|
||||||
|
if (transaction.ServerTimeStamp + TimeSpan.FromMinutes(Minutes) > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"Transaction: {transaction.MerchantTransactionId} should not be cancelled yet (ServerTimeStamp: {transaction.ServerTimeStamp:O}, Now: {DateTime.UtcNow:O})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug($"Cancelling transaction: {transaction.MerchantTransactionId}...");
|
||||||
|
await CancelTransaction(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CancelTransaction(TransactionInfo transaction)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _paymentGateway.CancelPayment(transaction);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, $"Transaction: {transaction.MerchantTransactionId} can't be cancelled: {e.Message}");
|
||||||
|
transaction.Status = TransactionInfo.TransactionStatus.CancelFailed;
|
||||||
|
await _storage.UpdateTransaction(transaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Status = TransactionInfo.TransactionStatus.Cancelled;
|
||||||
|
await _storage.UpdateTransaction(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user