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
};