diff --git a/QRBee.Core/Client/Client.cs b/QRBee.Core/Client/Client.cs
index 8e70d31..75ca88b 100644
--- a/QRBee.Core/Client/Client.cs
+++ b/QRBee.Core/Client/Client.cs
@@ -207,18 +207,18 @@ namespace QRBee.Core.Client
/// Success
/// A server side error occurred.
- public virtual System.Threading.Tasks.Task InsertTransactionAsync(PaymentRequest body)
+ public virtual System.Threading.Tasks.Task PayAsync(PaymentRequest body)
{
- return InsertTransactionAsync(body, System.Threading.CancellationToken.None);
+ return PayAsync(body, System.Threading.CancellationToken.None);
}
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// Success
/// A server side error occurred.
- public virtual async System.Threading.Tasks.Task InsertTransactionAsync(PaymentRequest body, System.Threading.CancellationToken cancellationToken)
+ public virtual async System.Threading.Tasks.Task PayAsync(PaymentRequest body, System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
- urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee/InsertTransaction");
+ urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee/Pay");
var client_ = _httpClient;
var disposeClient_ = false;
@@ -230,6 +230,7 @@ namespace QRBee.Core.Client
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
PrepareRequest(client_, request_, urlBuilder_);
@@ -254,7 +255,12 @@ namespace QRBee.Core.Client
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
- return;
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
}
else
{
diff --git a/QRBee.Core/Data/ClientCardData.cs b/QRBee.Core/Data/ClientCardData.cs
index a407e50..f2a0d41 100644
--- a/QRBee.Core/Data/ClientCardData.cs
+++ b/QRBee.Core/Data/ClientCardData.cs
@@ -15,7 +15,36 @@
/// WARNING: this should always be encrypted and never transmitted in clear text form.
///
/// Converted string
- public string AsString() => $"{TransactionId}|{CardNumber}|{ExpirationDateMMYY}|{ValidFrom}|{CardHolderName}|{CVC}|{IssueNo}";
+ public string AsString() => $"{TransactionId}|{CardNumber}|{ExpirationDateMMYY}|{ValidFrom}|{CardHolderName}|{CVC}|{IssueNo ?? 0}";
+
+ public static ClientCardData FromString(string input)
+ {
+ var s = input.Split('|');
+ if (s.Length < 7)
+ {
+ throw new ApplicationException("Expected 7 or more elements");
+ }
+
+ var res = new ClientCardData()
+ {
+ TransactionId = s[0],
+ CardNumber = s[1],
+ ExpirationDateMMYY = s[2],
+ ValidFrom = s[3],
+ CardHolderName = s[4],
+ CVC = s[5]
+ };
+
+ if (!string.IsNullOrWhiteSpace(s[6]))
+ res.IssueNo = Convert.ToInt32(s[6]);
+
+ if (res.IssueNo <= 0)
+ {
+ res.IssueNo = null;
+ }
+
+ return res;
+ }
}
}
diff --git a/QRBee/QRBee.Android/MainActivity.cs b/QRBee/QRBee.Android/MainActivity.cs
index 8005002..fb3d8fd 100644
--- a/QRBee/QRBee.Android/MainActivity.cs
+++ b/QRBee/QRBee.Android/MainActivity.cs
@@ -30,16 +30,10 @@ namespace QRBee.Droid
LoadApplication(new App(AddServices));
ZXing.Mobile.MobileBarcodeScanner.Initialize(Application);
- if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == (int) Permission.Granted)
+ if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) != (int) Permission.Granted)
{
-
+ ActivityCompat.RequestPermissions(this, new String[] {Manifest.Permission.Camera}, 0);
}
- else
- {
- ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.Camera }, 0);
- // ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.UseFingerprint }, 0);
- }
-
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
diff --git a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
index fcaba51..56b7639 100644
--- a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
+++ b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
@@ -48,8 +48,9 @@ namespace QRBee.ViewModels
//QrCode = null;
IsVisible = false;
- await service.InsertTransactionAsync(paymentRequest);
+ var response = await service.PayAsync(paymentRequest);
+ //TODO handle response
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
}
catch (Exception e)
diff --git a/QRBeeApi/Controllers/QRBeeController.cs b/QRBeeApi/Controllers/QRBeeController.cs
index 1fd8bc6..a278377 100644
--- a/QRBeeApi/Controllers/QRBeeController.cs
+++ b/QRBeeApi/Controllers/QRBeeController.cs
@@ -35,11 +35,11 @@ namespace QRBee.Api.Controllers
return _service.Update(clientId,value);
}
- [HttpPost("InsertTransaction")]
- public Task InsertTransaction([FromBody] PaymentRequest value)
+ [HttpPost("Pay")]
+ public Task Pay([FromBody] PaymentRequest value)
{
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
- return _service.InsertTransaction(value);
+ return _service.Pay(value);
}
}
}
diff --git a/QRBeeApi/Services/Database/IStorage.cs b/QRBeeApi/Services/Database/IStorage.cs
index 62ca07f..732932b 100644
--- a/QRBeeApi/Services/Database/IStorage.cs
+++ b/QRBeeApi/Services/Database/IStorage.cs
@@ -31,6 +31,13 @@
/// Information to be inserted
Task PutTransactionInfo(TransactionInfo info);
+ ///
+ /// Retrieve transaction information from database
+ ///
+ /// Identifier by which transaction information will be retrieved
+ /// Transaction information
+ Task GetTransactionInfoByTransactionId(string id);
+
///
/// Inserts CertificateInfo into database
///
@@ -43,6 +50,13 @@
///
/// Identifier by which certificate information will be retrieved
/// Certificate information
- Task GetCertificateInfo(string id);
+ Task GetCertificateInfoByCertificateId(string id);
+
+ ///
+ /// Retrieve certificate information from database
+ ///
+ /// Identifier by which certificate information will be retrieved
+ /// Certificate information
+ Task GetCertificateInfoByUserId(string clientId);
}
}
diff --git a/QRBeeApi/Services/Database/Storage.cs b/QRBeeApi/Services/Database/Storage.cs
index fa4a1df..dbbc0bf 100644
--- a/QRBeeApi/Services/Database/Storage.cs
+++ b/QRBeeApi/Services/Database/Storage.cs
@@ -102,6 +102,12 @@ namespace QRBee.Api.Services.Database
return cursor.Current.FirstOrDefault();
}
+ public async Task GetTransactionInfoByTransactionId(string id)
+ {
+ var transaction = await TryGetTransactionInfo(id);
+ return transaction ?? throw new ApplicationException($"Transaction with Id: {id} not found.");
+ }
+
public async Task InsertCertificate(CertificateInfo info)
{
var collection = _database.GetCollection("Certificates");
@@ -131,7 +137,7 @@ namespace QRBee.Api.Services.Database
/// null if certificate doesn't exist or CertificateInfo
private async Task TryGetCertificateInfo(string id)
{
- var collection = _database.GetCollection("Transactions");
+ var collection = _database.GetCollection("Certificates");
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
if (!await cursor.MoveNextAsync())
{
@@ -141,10 +147,23 @@ namespace QRBee.Api.Services.Database
return cursor.Current.FirstOrDefault();
}
- public async Task GetCertificateInfo(string id)
+ public async Task GetCertificateInfoByCertificateId(string id)
{
var certificate = await TryGetCertificateInfo(id);
return certificate ?? throw new ApplicationException($"Certificate with Id: {id} not found.");
}
+
+ public async Task GetCertificateInfoByUserId(string clientId)
+ {
+ var collection = _database.GetCollection("Certificates");
+ using var cursor = await collection.FindAsync($"{{ ClientId: \"{clientId}\" }}");
+ if (!await cursor.MoveNextAsync())
+ {
+ throw new ApplicationException($"Certificate with ClientId: {clientId} not found.");
+ }
+
+ return cursor.Current.FirstOrDefault() ?? throw new ApplicationException($"Certificate with ClientId: {clientId} not found.");
+ }
+
}
}
diff --git a/QRBeeApi/Services/Database/TransactionInfo.cs b/QRBeeApi/Services/Database/TransactionInfo.cs
index e892bd9..be05600 100644
--- a/QRBeeApi/Services/Database/TransactionInfo.cs
+++ b/QRBeeApi/Services/Database/TransactionInfo.cs
@@ -34,5 +34,12 @@ namespace QRBee.Api.Services.Database
public PaymentRequest Request { get; set; }
+ public enum TransactionStatus
+ {
+ Pending = 0,
+ Rejected = 1,
+ Succeeded = 2,
+ }
+ public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
}
}
diff --git a/QRBeeApi/Services/IQRBeeAPI.cs b/QRBeeApi/Services/IQRBeeAPI.cs
index ad4c504..e3c6dc3 100644
--- a/QRBeeApi/Services/IQRBeeAPI.cs
+++ b/QRBeeApi/Services/IQRBeeAPI.cs
@@ -28,7 +28,7 @@ namespace QRBee.Api.Services
/// Handles InsertTransaction request
///
/// Payment request
- Task InsertTransaction(PaymentRequest value);
+ Task Pay(PaymentRequest value);
}
}
diff --git a/QRBeeApi/Services/QRBeeAPIService.cs b/QRBeeApi/Services/QRBeeAPIService.cs
index 57814de..2c0a1bb 100644
--- a/QRBeeApi/Services/QRBeeAPIService.cs
+++ b/QRBeeApi/Services/QRBeeAPIService.cs
@@ -45,7 +45,7 @@ namespace QRBee.Api.Services
public async Task Register(RegistrationRequest request)
{
- Validate(request);
+ ValidateRegistration(request);
var info = Convert(request);
@@ -69,18 +69,11 @@ namespace QRBee.Api.Services
public Task Update(string clientId, RegistrationRequest request)
{
- Validate(request);
+ ValidateRegistration(request);
var info = Convert(request);
return _storage.UpdateUser(info);
}
-
- public Task InsertTransaction(PaymentRequest value)
- {
- var info = Convert(value);
- return _storage.PutTransactionInfo(info);
- }
-
- private void Validate(RegistrationRequest request)
+ private void ValidateRegistration(RegistrationRequest request)
{
if (request == null)
{
@@ -92,14 +85,14 @@ namespace QRBee.Api.Services
var dateOfBirth = request.DateOfBirth;
var certificateRequest = request.CertificateRequest;
- if (string.IsNullOrEmpty(name) || name.All(char.IsLetter)==false || name.Length>=MaxNameLength)
+ if (string.IsNullOrEmpty(name) || name.All(char.IsLetter) == false || name.Length >= MaxNameLength)
{
throw new ApplicationException($"Name \"{name}\" isn't valid");
}
var freq = Regex.Matches(email, @"[^@]+@[^@]+").Count;
- if (string.IsNullOrEmpty(email) || email.IndexOf('@')<0 || freq>=2 || email.Length >= MaxEmailLength)
+ if (string.IsNullOrEmpty(email) || email.IndexOf('@') < 0 || freq >= 2 || email.Length >= MaxEmailLength)
{
throw new ApplicationException($"Email \"{email}\" isn't valid");
}
@@ -120,9 +113,9 @@ namespace QRBee.Api.Services
var verified = rsa.VerifyData(
data,
signature,
- HashAlgorithmName.SHA256,
+ HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1
- );
+ );
if (!verified)
{
@@ -130,6 +123,105 @@ 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
+ //7. Register preliminary transaction record with expiry of one minute
+ //8. Send client card data to a payment gateway
+ //9. Record transaction with result
+ //10. Make response for merchant
+ var info = Convert(value);
+ await _storage.PutTransactionInfo(info);
+ return new PaymentResponse();
+ }
+
+ private void ValidateTransaction(PaymentRequest request)
+ {
+ if (request == null)
+ {
+ throw new NullReferenceException();
+ }
+
+ var clientId = request.ClientResponse.ClientId;
+ var merchantId = request.ClientResponse.MerchantRequest.MerchantId;
+ var transactionId = request.ClientResponse.MerchantRequest.MerchantTransactionId;
+ var amount = request.ClientResponse.MerchantRequest.Amount;
+
+ if (clientId == null || merchantId == null || transactionId == null)
+ {
+ throw new ApplicationException("Id isn't valid");
+ }
+
+ if (amount is <= 0 or >= 10000)
+ {
+ throw new ApplicationException($"Amount \"{amount}\" isn't valid");
+ }
+ }
+
+ private async Task CheckSignature(string data,string signature, string id)
+ {
+ var info = await _storage.GetCertificateInfoByUserId(id);
+ var certificate = _securityService.Deserialize(info.Certificate);
+
+ var check = _securityService.Verify(
+ Encoding.UTF8.GetBytes(data),
+ System.Convert.FromBase64String(signature),
+ certificate);
+
+ if (!check)
+ {
+ throw new ApplicationException($"Signature is incorrect for Id: {id}.");
+ }
+ }
+
+ private async Task CheckTransaction(string transactionId)
+ {
+ var info = await _storage.GetTransactionInfoByTransactionId(transactionId);
+ switch (info.Status)
+ {
+ case TransactionInfo.TransactionStatus.Succeeded:
+ throw new ApplicationException($"Transaction with Id: {transactionId} was already made.");
+ case TransactionInfo.TransactionStatus.Rejected:
+ throw new ApplicationException($"Transaction with Id: {transactionId} is not valid.");
+ case TransactionInfo.TransactionStatus.Pending:
+ throw new ApplicationException($"Transaction with Id: {transactionId} is already in progress.");
+ default:
+ return;
+ }
+ }
+
+ private ClientCardData DecryptClientData(string encryptedClientCardData)
+ {
+ var info = System.Convert.FromBase64String(encryptedClientCardData);
+ var bytes = _securityService.Decrypt(info);
+ var s = Encoding.UTF8.GetString(bytes);
+ return ClientCardData.FromString(s);
+ }
+
private static RSA LoadRsaPublicKey(StringRSAParameters stringParameters)
{
var rsaParameters = new RSAParameters