From 5c041da318206d6cbb34ae826db00fadfe8c850e Mon Sep 17 00:00:00 2001 From: Andrey Shabarshov Date: Sat, 26 Mar 2022 13:31:17 +0000 Subject: [PATCH] Client response QR code size reduced. Cloud provider support. --- QRBee.Core/Data/ClientToMerchantResponse.cs | 11 +- QRBee.Core/Data/MerchantToClientRequest.cs | 4 +- QRBee.Core/Data/PaymentRequest.cs | 14 +- .../Resources/Resource.designer.cs | 2 +- QRBee/QRBee.Android/Services/LocalSettings.cs | 6 + .../QRBee/ViewModels/MerchantPageViewModel.cs | 28 ++-- QRBee/QRBee/ViewModels/RegisterViewModel.cs | 2 +- QRBeeApi/Services/Database/CertificateInfo.cs | 7 +- QRBeeApi/Services/Database/Storage.cs | 24 +++- QRBeeApi/Services/QRBeeAPIService.cs | 125 ++++++++++-------- 10 files changed, 132 insertions(+), 91 deletions(-) diff --git a/QRBee.Core/Data/ClientToMerchantResponse.cs b/QRBee.Core/Data/ClientToMerchantResponse.cs index 8a41558..53d0126 100644 --- a/QRBee.Core/Data/ClientToMerchantResponse.cs +++ b/QRBee.Core/Data/ClientToMerchantResponse.cs @@ -16,7 +16,7 @@ /// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature) /// /// Converted string - public string AsQRCodeString() => $"{AsDataForSignature()}|{ClientSignature}"; + public string AsQRCodeString() => $"{ClientId}|{TimeStampUTC:O}|{ClientSignature}"; public string AsDataForSignature() => $"{ClientId}|{TimeStampUTC:O}|{MerchantRequest.AsQRCodeString()}"; @@ -31,15 +31,14 @@ var s = input.Split('|'); if (s.Length < 3) { - throw new ApplicationException("Expected 3 or more elements"); + throw new ApplicationException($"Expected 3 or more elements but got {s.Length}"); } var res = new ClientToMerchantResponse() { - MerchantRequest = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))), - ClientId = s[0], - TimeStampUTC = DateTime.ParseExact(s[1], "O", null), - ClientSignature = s[3] + ClientId = s[0], + TimeStampUTC = DateTime.ParseExact(s[1], "O", null), + ClientSignature = s[2] }; return res; diff --git a/QRBee.Core/Data/MerchantToClientRequest.cs b/QRBee.Core/Data/MerchantToClientRequest.cs index ae8dfaf..68ce3d8 100644 --- a/QRBee.Core/Data/MerchantToClientRequest.cs +++ b/QRBee.Core/Data/MerchantToClientRequest.cs @@ -33,9 +33,9 @@ namespace QRBee.Core.Data public static MerchantToClientRequest FromString(string input) { var s = input.Split('|'); - if (s.Length != 6) + if (s.Length < 6) { - throw new ApplicationException("Expected 6 elements"); + throw new ApplicationException($"Expected 6 elements but got {s.Length}"); } var res = new MerchantToClientRequest diff --git a/QRBee.Core/Data/PaymentRequest.cs b/QRBee.Core/Data/PaymentRequest.cs index 1b5adda..1549817 100644 --- a/QRBee.Core/Data/PaymentRequest.cs +++ b/QRBee.Core/Data/PaymentRequest.cs @@ -13,18 +13,6 @@ /// Convert PaymentRequest to string /// /// Converted string - 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}; - } + public string AsString() => $"{ClientResponse.AsDataForSignature()}|{ClientResponse.ClientSignature}"; } } diff --git a/QRBee/QRBee.Android/Resources/Resource.designer.cs b/QRBee/QRBee.Android/Resources/Resource.designer.cs index b3b03d1..97c8062 100644 --- a/QRBee/QRBee.Android/Resources/Resource.designer.cs +++ b/QRBee/QRBee.Android/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace QRBee.Droid { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.0.155")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.0.11")] public partial class Resource { diff --git a/QRBee/QRBee.Android/Services/LocalSettings.cs b/QRBee/QRBee.Android/Services/LocalSettings.cs index dbf55aa..e43199b 100644 --- a/QRBee/QRBee.Android/Services/LocalSettings.cs +++ b/QRBee/QRBee.Android/Services/LocalSettings.cs @@ -9,7 +9,13 @@ namespace QRBee.Droid.Services { internal class LocalSettings : ILocalSettings { + // Use https://10.0.2.2:7000 if you are running in emulator and API server on localhost +#if false public string QRBeeApiUrl => "https://10.0.2.2:7000"; +#else + public string QRBeeApiUrl => "https://qrbee-api.azurewebsites.net/"; +#endif + public async Task SaveSettings(Settings settings) { diff --git a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs index 7372de6..a9f32f5 100644 --- a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs +++ b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs @@ -17,6 +17,8 @@ namespace QRBee.ViewModels private decimal _amount; private string _qrCode; + private MerchantToClientRequest _lastRequest; + public Command GenerateQrCommand { get; } public Command ScanCommand{ get; } @@ -42,7 +44,13 @@ namespace QRBee.ViewModels var client = new HttpClient(GetInsecureHandler()); var service = new Core.Client.Client(_settings.QRBeeApiUrl, client); - var paymentRequest = PaymentRequest.FromString(result); + var clientResponse = ClientToMerchantResponse.FromString(result); + + clientResponse.MerchantRequest = _lastRequest; + var paymentRequest = new PaymentRequest + { + ClientResponse = clientResponse + }; //QrCode = null; IsVisible = false; @@ -152,19 +160,21 @@ namespace QRBee.ViewModels } else { - var trans = new MerchantToClientRequest + var request = new MerchantToClientRequest { - MerchantId = _settings.LoadSettings().ClientId, + MerchantId = _settings.LoadSettings().ClientId, MerchantTransactionId = Guid.NewGuid().ToString("D"), - Name = Name, - Amount = Amount, - TimeStampUTC = DateTime.UtcNow + Name = Name, + Amount = Amount, + TimeStampUTC = DateTime.UtcNow }; - var merchantSignature = _securityService.Sign(Encoding.UTF8.GetBytes(trans.AsDataForSignature())); - trans.MerchantSignature = Convert.ToBase64String(merchantSignature); + var merchantSignature = _securityService.Sign(Encoding.UTF8.GetBytes(request.AsDataForSignature())); + request.MerchantSignature = Convert.ToBase64String(merchantSignature); - QrCode = trans.AsQRCodeString(); + _lastRequest = request; + + QrCode = request.AsQRCodeString(); IsVisible = true; } diff --git a/QRBee/QRBee/ViewModels/RegisterViewModel.cs b/QRBee/QRBee/ViewModels/RegisterViewModel.cs index c0d75e7..d85194c 100644 --- a/QRBee/QRBee/ViewModels/RegisterViewModel.cs +++ b/QRBee/QRBee/ViewModels/RegisterViewModel.cs @@ -124,7 +124,7 @@ namespace QRBee.ViewModels await _settings.SaveSettings(settings); - //if (!_privateKeyHandler.Exists()) + if (!_privateKeyHandler.Exists()) { _privateKeyHandler.GeneratePrivateKey(settings.Name); } diff --git a/QRBeeApi/Services/Database/CertificateInfo.cs b/QRBeeApi/Services/Database/CertificateInfo.cs index 9d8dd0b..60aa70d 100644 --- a/QRBeeApi/Services/Database/CertificateInfo.cs +++ b/QRBeeApi/Services/Database/CertificateInfo.cs @@ -5,9 +5,10 @@ namespace QRBee.Api.Services.Database public class CertificateInfo { - [BsonId] public string? Id { get; set; } - public string? ClientId { get; set; } - public string? Certificate { get; set; } + [BsonId] public string? Id { get; set; } + public string? ClientId { get; set; } + public string? Email { get; set; } + public string? Certificate { get; set; } public DateTime ServerTimeStamp { get; set; } } } diff --git a/QRBeeApi/Services/Database/Storage.cs b/QRBeeApi/Services/Database/Storage.cs index c9276e0..04fa2d3 100644 --- a/QRBeeApi/Services/Database/Storage.cs +++ b/QRBeeApi/Services/Database/Storage.cs @@ -121,7 +121,7 @@ namespace QRBee.Api.Services.Database throw new ApplicationException("Info Id is null."); } - var certificate = await TryGetCertificateInfo(info.Id); + var certificate = await TryGetCertificateInfoByEmail(info.Email ?? throw new ApplicationException("Email is not set")); if (certificate == null) { @@ -130,7 +130,10 @@ namespace QRBee.Api.Services.Database return; } - _logger.LogInformation($"Found certificate with ID: {info.Id}"); + await collection.DeleteOneAsync($"{{ _id: \"{certificate.Id}\" }}"); + await collection.ReplaceOneAsync($"{{ _id: \"{info.Id}\" }}", info, new ReplaceOptions() { IsUpsert = true }); + + _logger.LogInformation($"Found certificate with old ID: {certificate.Id} and replaced with new ID: {info.Id}"); } @@ -151,6 +154,23 @@ namespace QRBee.Api.Services.Database return cursor.Current.FirstOrDefault(); } + /// + /// Try to find if the Certificate already exists in the database + /// + /// parameter by which to find CertificateInfo + /// null if certificate doesn't exist or CertificateInfo + private async Task TryGetCertificateInfoByEmail(string email) + { + var collection = _database.GetCollection("Certificates"); + using var cursor = await collection.FindAsync($"{{ Email: \"{email}\" }}"); + if (!await cursor.MoveNextAsync()) + { + return null; + } + + return cursor.Current.FirstOrDefault(); + } + public async Task GetCertificateInfoByCertificateId(string id) { var certificate = await TryGetCertificateInfo(id); diff --git a/QRBeeApi/Services/QRBeeAPIService.cs b/QRBeeApi/Services/QRBeeAPIService.cs index f47841d..0cffffe 100644 --- a/QRBeeApi/Services/QRBeeAPIService.cs +++ b/QRBeeApi/Services/QRBeeAPIService.cs @@ -57,7 +57,7 @@ namespace QRBee.Api.Services var clientCertificate = _securityService.CreateCertificate(clientId,bytes); - var convertedClientCertificate = Convert(clientCertificate, clientId); + var convertedClientCertificate = Convert(clientCertificate, clientId,request.Email); await _storage.InsertCertificate(convertedClientCertificate); return new RegistrationResponse @@ -126,62 +126,78 @@ namespace QRBee.Api.Services public async Task Pay(PaymentRequest value) { - //1. Check payment request parameters for validity - ValidateTransaction(value); - - //2. Check client signature - var t2 = CheckSignature( - value.ClientResponse.AsDataForSignature(), - value.ClientResponse.ClientSignature, - value.ClientResponse.ClientId); - - //3. Check merchant signature - var t3 = CheckSignature( - value.ClientResponse.MerchantRequest.AsDataForSignature(), - value.ClientResponse.MerchantRequest.MerchantSignature, - value.ClientResponse.MerchantRequest.MerchantId); - - //4. Check if transaction was already processed - var t4 = CheckTransaction(value.ClientResponse.MerchantRequest.MerchantTransactionId); - - //Parallel task execution - await Task.WhenAll(t2, t3, t4); - - //5. Decrypt client card data - var clientCardData = DecryptClientData(value.ClientResponse.EncryptedClientCardData); - - //6. Check client card data for validity - await CheckClientCardData(clientCardData); - - //7. Register preliminary transaction record with expiry of one minute - var info = Convert(value); - info.Status = TransactionInfo.TransactionStatus.Pending; - - await _storage.PutTransactionInfo(info); - - //8. Send client card data to a payment gateway - var res = await _paymentGateway.Payment(info, clientCardData); - - //9. Record transaction with result - if (res.Success) + try { - info.Status=TransactionInfo.TransactionStatus.Succeeded; - } - else - { - info.Status = TransactionInfo.TransactionStatus.Rejected; - info.RejectReason = res.ErrorMessage; - } - await _storage.UpdateTransaction(info); + //1. Check payment request parameters for validity + ValidateTransaction(value); - //10. Make response for merchant + //2. Check client signature + var t2 = CheckSignature( + value.ClientResponse.AsDataForSignature(), + value.ClientResponse.ClientSignature, + value.ClientResponse.ClientId); + + //3. Check merchant signature + var t3 = CheckSignature( + value.ClientResponse.MerchantRequest.AsDataForSignature(), + value.ClientResponse.MerchantRequest.MerchantSignature, + value.ClientResponse.MerchantRequest.MerchantId); + + //4. Check if transaction was already processed + var t4 = CheckTransaction(value.ClientResponse.MerchantRequest.MerchantTransactionId); + + //Parallel task execution + await Task.WhenAll(t2, t3, t4); + + //5. Decrypt client card data + var clientCardData = DecryptClientData(value.ClientResponse.EncryptedClientCardData); + + //6. Check client card data for validity + await CheckClientCardData(clientCardData); + + //7. Register preliminary transaction record with expiry of one minute + var info = Convert(value); + info.Status = TransactionInfo.TransactionStatus.Pending; + + await _storage.PutTransactionInfo(info); + + //8. Send client card data to a payment gateway + var res = await _paymentGateway.Payment(info, clientCardData); + + //9. Record transaction with result + if (res.Success) + { + info.Status=TransactionInfo.TransactionStatus.Succeeded; + } + else + { + info.Status = TransactionInfo.TransactionStatus.Rejected; + info.RejectReason = res.ErrorMessage; + } + await _storage.UpdateTransaction(info); + + //10. Make response for merchant + var response = MakePaymentResponse(value, info.TransactionId ?? "", info.Status==TransactionInfo.TransactionStatus.Succeeded, info.RejectReason); + return response; + } + catch (Exception e) + { + var response = MakePaymentResponse(value, "", false, e.Message); + return response; + } + + } + + private PaymentResponse MakePaymentResponse(PaymentRequest value, string transactionId, bool result = true, string? errorMessage = null) + { + var response = new PaymentResponse { - ServerTransactionId = info.TransactionId, + ServerTransactionId = transactionId, PaymentRequest = value, ServerTimeStampUTC = DateTime.UtcNow, - Success = res.Success, - RejectReason = res.ErrorMessage, + Success = result, + RejectReason = errorMessage, }; var signature = _securityService.Sign(Encoding.UTF8.GetBytes(response.AsDataForSignature())); @@ -224,7 +240,7 @@ namespace QRBee.Api.Services if (!check) { - throw new ApplicationException($"Signature is incorrect for Id: {id}."); + throw new ApplicationException($"Signature is incorrect for Id: {id}. Data: {data}"); } } @@ -309,13 +325,14 @@ namespace QRBee.Api.Services return new TransactionInfo(request, DateTime.UtcNow); } - private CertificateInfo Convert(X509Certificate2 certificate, string clientId) + private CertificateInfo Convert(X509Certificate2 certificate, string clientId, string email) { var convertedCertificate = _securityService.Serialize(certificate); return new CertificateInfo { Id = certificate.SerialNumber, - ClientId = clientId, + ClientId = clientId, + Email = email, Certificate = convertedCertificate, ServerTimeStamp = DateTime.UtcNow };