Transaction mechanism implemented. UI improved.

This commit is contained in:
Andrey Shabarshov 2022-02-12 12:01:27 +00:00
parent ad183ea33f
commit 21f16b862c
14 changed files with 269 additions and 45 deletions

View File

@ -205,6 +205,77 @@ namespace QRBee.Core.Client
}
}
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public virtual System.Threading.Tasks.Task InsertTransactionAsync(PaymentRequest body)
{
return InsertTransactionAsync(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 InsertTransactionAsync(PaymentRequest body, System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee/InsertTransaction");
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>
{
public ObjectResponseResult(T responseObject, string responseText)

View File

@ -2,7 +2,7 @@
{
public record ClientToMerchantResponse
{
public MerchantToClientRequest Request
public MerchantToClientRequest MerchantRequest
{
get;
set;
@ -36,7 +36,7 @@
/// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature)
/// </summary>
/// <returns> Converted string</returns>
public string AsString() => $"{ClientId}|{TimeStampUTC:O}|{Request.AsString()}";
public string AsQRCodeString() => $"{ClientId}|{TimeStampUTC:O}|{MerchantRequest.AsQRCodeString()}";
/// <summary>
/// Convert from string
@ -47,14 +47,14 @@
public static ClientToMerchantResponse FromString(string input)
{
var s = input.Split('|');
if (s.Length != 3)
if (s.Length < 3)
{
throw new ApplicationException("Expected 3 elements");
throw new ApplicationException("Expected 3 or more elements");
}
var res = new ClientToMerchantResponse()
{
Request = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))),
MerchantRequest = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))),
ClientId = s[0],
TimeStampUTC = DateTime.ParseExact(s[1], "O", null)
};

View File

@ -4,7 +4,13 @@ namespace QRBee.Core.Data
{
public record MerchantToClientRequest
{
public string TransactionId
public string MerchantId
{
get;
set;
}
public string MerchantTransactionId
{
get;
set;
@ -38,7 +44,7 @@ namespace QRBee.Core.Data
/// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature)
/// </summary>
/// <returns>String conversion</returns>
public string AsString() => $"{TransactionId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}";
public string AsQRCodeString() => $"{MerchantTransactionId}|{MerchantId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}";
/// <summary>
/// Convert from string
@ -49,17 +55,18 @@ namespace QRBee.Core.Data
public static MerchantToClientRequest FromString(string input)
{
var s = input.Split('|');
if (s.Length != 4)
if (s.Length != 5)
{
throw new ApplicationException("Expected 4 elements");
throw new ApplicationException("Expected 5 elements");
}
var res = new MerchantToClientRequest
{
TransactionId = s[0],
Name = s[1],
Amount = Convert.ToDecimal(s[2], CultureInfo.InvariantCulture),
TimeStampUTC = DateTime.ParseExact(s[3],"O",null)
MerchantId = s[0],
MerchantTransactionId = s[1],
Name = s[2],
Amount = Convert.ToDecimal(s[3], CultureInfo.InvariantCulture),
TimeStampUTC = DateTime.ParseExact(s[4],"O",null)
};

View File

@ -3,7 +3,7 @@
public record PaymentRequest
{
public ClientToMerchantResponse Request
public ClientToMerchantResponse ClientResponse
{
get;
set;
@ -13,7 +13,18 @@
/// Convert PaymentRequest to string
/// </summary>
/// <returns>Converted string</returns>
public string AsString() => Request.AsString();
public string AsString() => ClientResponse.AsQRCodeString();
public static PaymentRequest FromString(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
throw new ApplicationException("The input is wrong!");
}
//doesn't work
var response = ClientToMerchantResponse.FromString(input);
return new PaymentRequest(){ClientResponse = response};
}
}
}

View File

@ -37,12 +37,12 @@ namespace QRBee.ViewModels
{
var scanner = DependencyService.Get<IQRScanner>();
var result = await scanner.ScanQR();
if (result != null)
{
_merchantToClientRequest = MerchantToClientRequest.FromString(result);
Amount = $"{_merchantToClientRequest.Amount:N2}";
IsVisible = true;
}
if (result == null)
return;
_merchantToClientRequest = MerchantToClientRequest.FromString(result);
Amount = $"{_merchantToClientRequest.Amount:N2}";
IsVisible = true;
}
catch (Exception)
{
@ -99,21 +99,22 @@ namespace QRBee.ViewModels
public async void OnGenerateQrClicked(object obj)
{
bool answer = await _clientPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No");
if (answer)
{
var response = new ClientToMerchantResponse
{
ClientId = Guid.NewGuid().ToString("D"),
TimeStampUTC = DateTime.UtcNow,
Request = _merchantToClientRequest
};
// TODO Create merchant signature.
QrCode = response.AsString();
}
var answer = await _clientPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No");
if (!answer)
return;
var response = new ClientToMerchantResponse
{
//TODO get client id from database
ClientId = Guid.NewGuid().ToString("D"),
TimeStampUTC = DateTime.UtcNow,
MerchantRequest = _merchantToClientRequest
};
// TODO Create merchant signature.
QrCode = response.AsQRCodeString();
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net.Http;
using QRBee.Core.Data;
using QRBee.Services;
using Xamarin.Forms;
@ -29,17 +30,43 @@ namespace QRBee.ViewModels
{
var scanner = DependencyService.Get<IQRScanner>();
var result = await scanner.ScanQR();
if (result != null)
{
if (result == null)
return;
}
var client = new HttpClient(GetInsecureHandler());
var localSettings = DependencyService.Resolve<ILocalSettings>();
var service = new Core.Client.Client(localSettings.QRBeeApiUrl, client);
var paymentRequest = PaymentRequest.FromString(result);
//QrCode = null;
IsVisible = false;
await service.InsertTransactionAsync(paymentRequest);
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
}
catch (Exception ex)
catch (Exception e)
{
throw;
//TODO: delete exception message in error message
await Application.Current.MainPage.DisplayAlert("Error", $"The Backend isn't working: {e.Message}", "Ok");
}
}
// This method must be in a class in a platform project, even if
// the HttpClient object is constructed in a shared project.
public HttpClientHandler GetInsecureHandler()
{
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
}
public string Name { get; }
public decimal Amount
@ -98,13 +125,15 @@ namespace QRBee.ViewModels
{
var trans = new MerchantToClientRequest
{
TransactionId = Guid.NewGuid().ToString("D"),
//TODO get merchant id from database
MerchantId = Guid.NewGuid().ToString("D"),
MerchantTransactionId = Guid.NewGuid().ToString("D"),
Name = Name,
Amount = Amount,
TimeStampUTC = DateTime.UtcNow
};
// TODO Create merchant signature.
QrCode = trans.AsString();
QrCode = trans.AsQRCodeString();
IsVisible = true;
}

View File

@ -94,7 +94,8 @@ namespace QRBee.ViewModels
private async void OnRegisterClicked(object obj)
{
using var client = new HttpClient(GetInsecureHandler());
//TODO when to dispose the client?
var client = new HttpClient(GetInsecureHandler());
var localSettings = DependencyService.Resolve<ILocalSettings>();
var service = new Core.Client.Client(localSettings.QRBeeApiUrl,client);

View File

@ -27,7 +27,8 @@
BarcodeFormat="QR_CODE"
BarcodeValue="{Binding QrCode}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
VerticalOptions="FillAndExpand"
IsVisible="{Binding IsVisible}">
<forms:ZXingBarcodeImageView.BarcodeOptions>
<common:EncodingOptions Width="300" Height="300" />
</forms:ZXingBarcodeImageView.BarcodeOptions>

View File

@ -34,5 +34,12 @@ namespace QRBee.Api.Controllers
_logger.LogInformation($"Trying to update user {value.Name}");
return _service.Update(clientId,value);
}
[HttpPost("InsertTransaction")]
public Task InsertTransaction([FromBody] PaymentRequest value)
{
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
return _service.InsertTransaction(value);
}
}
}

View File

@ -9,7 +9,6 @@
/// Insert userInfo into database
/// </summary>
/// <param name="info"> Information to be inserted</param>
/// <returns></returns>
Task<string> PutUserInfo(UserInfo info);
/// <summary>
@ -19,7 +18,17 @@
/// <returns>User information</returns>
Task<UserInfo> GetUserInfo(string email);
/// <summary>
/// Update user record
/// </summary>
/// <param name="info">New user record</param>
/// <returns></returns>
Task UpdateUser(UserInfo info);
/// <summary>
/// Insert transactionInfo into database
/// </summary>
/// <param name="info">Information to be inserted</param>
Task PutTransactionInfo(TransactionInfo info);
}
}

View File

@ -68,5 +68,37 @@ namespace QRBee.Api.Services.Database
await collection.ReplaceOneAsync($"{{ _id: \"{info.Id}\" }}",info, new ReplaceOptions(){IsUpsert = false});
}
public async Task PutTransactionInfo(TransactionInfo info)
{
var collection = _database.GetCollection<TransactionInfo>("Transactions");
var transaction = await TryGetTransactionInfo(info.Id);
if (transaction == null)
{
await collection.InsertOneAsync(info);
_logger.LogInformation($"Inserted new transaction with ID: {info.Id}");
return;
}
_logger.LogInformation($"Found transaction with ClientId: {info.Id}");
}
/// <summary>
/// Try to find if the Transaction already exists in the database
/// </summary>
/// <param name="id">parameter by which to find TransactionInfo</param>
/// <returns>null if transaction doesn't exist or TransactionInfo</returns>
private async Task<TransactionInfo?> TryGetTransactionInfo(string id)
{
var collection = _database.GetCollection<TransactionInfo>("Transactions");
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
if (!await cursor.MoveNextAsync())
{
return null;
}
return cursor.Current.FirstOrDefault();
}
}
}

View File

@ -0,0 +1,38 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using QRBee.Core.Data;
namespace QRBee.Api.Services.Database
{
public record TransactionInfo
{
#pragma warning disable CS8618
public TransactionInfo()
#pragma warning restore CS8618
{
}
public TransactionInfo(PaymentRequest request, DateTime serverTimeStamp)
{
//TODO This is a side effect. The original request will be modified.
request.ClientResponse.EncryptedClientCardData = null;
ServerTimeStamp = serverTimeStamp;
Request = request;
Id = $"{request.ClientResponse.MerchantRequest.MerchantId}-{request.ClientResponse.MerchantRequest.MerchantTransactionId}";
}
/// <summary>
/// Never use directly. Use <see cref="TransactionId"/> instead.
/// </summary>
[BsonId] public string Id { get; set; }
[BsonIgnore] public string? TransactionId => Id;
public DateTime ServerTimeStamp { get; set; }
public PaymentRequest Request { get; set; }
}
}

View File

@ -23,5 +23,11 @@ namespace QRBee.Api.Services
/// <param name="value">Update request</param>
Task Update(string clientId, RegistrationRequest value);
/// <summary>
/// Handles InsertTransaction request
/// </summary>
/// <param name="value">Payment request</param>
Task InsertTransaction(PaymentRequest value);
}
}

View File

@ -39,6 +39,12 @@ namespace QRBee.Api.Services
return _storage.UpdateUser(info);
}
public Task InsertTransaction(PaymentRequest value)
{
var info = Convert(value);
return _storage.PutTransactionInfo(info);
}
private static void Validate(RegistrationRequest request)
{
if (request == null)
@ -77,5 +83,10 @@ namespace QRBee.Api.Services
return new UserInfo(request.Name, request.Email, request.DateOfBirth);
}
private static TransactionInfo Convert(PaymentRequest request)
{
return new TransactionInfo(request, DateTime.UtcNow);
}
}
}