mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
Work on Transaction started. Pre-checks complete.
This commit is contained in:
parent
4722585e17
commit
82b8d1820e
@ -207,18 +207,18 @@ namespace QRBee.Core.Client
|
|||||||
|
|
||||||
/// <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 InsertTransactionAsync(PaymentRequest body)
|
public virtual System.Threading.Tasks.Task<PaymentResponse> PayAsync(PaymentRequest body)
|
||||||
{
|
{
|
||||||
return InsertTransactionAsync(body, System.Threading.CancellationToken.None);
|
return PayAsync(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>
|
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||||
/// <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 async System.Threading.Tasks.Task InsertTransactionAsync(PaymentRequest body, System.Threading.CancellationToken cancellationToken)
|
public virtual async System.Threading.Tasks.Task<PaymentResponse> PayAsync(PaymentRequest body, System.Threading.CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var urlBuilder_ = new System.Text.StringBuilder();
|
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 client_ = _httpClient;
|
||||||
var disposeClient_ = false;
|
var disposeClient_ = false;
|
||||||
@ -230,6 +230,7 @@ namespace QRBee.Core.Client
|
|||||||
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
|
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
|
||||||
request_.Content = content_;
|
request_.Content = content_;
|
||||||
request_.Method = new System.Net.Http.HttpMethod("POST");
|
request_.Method = new System.Net.Http.HttpMethod("POST");
|
||||||
|
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
|
||||||
|
|
||||||
PrepareRequest(client_, request_, urlBuilder_);
|
PrepareRequest(client_, request_, urlBuilder_);
|
||||||
|
|
||||||
@ -254,7 +255,12 @@ namespace QRBee.Core.Client
|
|||||||
var status_ = (int)response_.StatusCode;
|
var status_ = (int)response_.StatusCode;
|
||||||
if (status_ == 200)
|
if (status_ == 200)
|
||||||
{
|
{
|
||||||
return;
|
var objectResponse_ = await ReadObjectResponseAsync<PaymentResponse>(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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -15,7 +15,36 @@
|
|||||||
/// WARNING: this should always be encrypted and never transmitted in clear text form.
|
/// WARNING: this should always be encrypted and never transmitted in clear text form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Converted string</returns>
|
/// <returns>Converted string</returns>
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,16 +30,10 @@ namespace QRBee.Droid
|
|||||||
LoadApplication(new App(AddServices));
|
LoadApplication(new App(AddServices));
|
||||||
ZXing.Mobile.MobileBarcodeScanner.Initialize(Application);
|
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)
|
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
|
||||||
|
|||||||
@ -48,8 +48,9 @@ namespace QRBee.ViewModels
|
|||||||
//QrCode = null;
|
//QrCode = null;
|
||||||
IsVisible = false;
|
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");
|
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@ -35,11 +35,11 @@ namespace QRBee.Api.Controllers
|
|||||||
return _service.Update(clientId,value);
|
return _service.Update(clientId,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("InsertTransaction")]
|
[HttpPost("Pay")]
|
||||||
public Task InsertTransaction([FromBody] PaymentRequest value)
|
public Task<PaymentResponse> Pay([FromBody] PaymentRequest value)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
|
_logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}");
|
||||||
return _service.InsertTransaction(value);
|
return _service.Pay(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,13 @@
|
|||||||
/// <param name="info">Information to be inserted</param>
|
/// <param name="info">Information to be inserted</param>
|
||||||
Task PutTransactionInfo(TransactionInfo info);
|
Task PutTransactionInfo(TransactionInfo info);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve transaction information from database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Identifier by which transaction information will be retrieved</param>
|
||||||
|
/// <returns>Transaction information</returns>
|
||||||
|
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inserts CertificateInfo into database
|
/// Inserts CertificateInfo into database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,6 +50,13 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">Identifier by which certificate information will be retrieved</param>
|
/// <param name="id">Identifier by which certificate information will be retrieved</param>
|
||||||
/// <returns>Certificate information</returns>
|
/// <returns>Certificate information</returns>
|
||||||
Task<CertificateInfo> GetCertificateInfo(string id);
|
Task<CertificateInfo> GetCertificateInfoByCertificateId(string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve certificate information from database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Identifier by which certificate information will be retrieved</param>
|
||||||
|
/// <returns>Certificate information</returns>
|
||||||
|
Task<CertificateInfo> GetCertificateInfoByUserId(string clientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,6 +102,12 @@ namespace QRBee.Api.Services.Database
|
|||||||
return cursor.Current.FirstOrDefault();
|
return cursor.Current.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<TransactionInfo> 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)
|
public async Task InsertCertificate(CertificateInfo info)
|
||||||
{
|
{
|
||||||
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
||||||
@ -131,7 +137,7 @@ namespace QRBee.Api.Services.Database
|
|||||||
/// <returns>null if certificate doesn't exist or CertificateInfo</returns>
|
/// <returns>null if certificate doesn't exist or CertificateInfo</returns>
|
||||||
private async Task<CertificateInfo?> TryGetCertificateInfo(string id)
|
private async Task<CertificateInfo?> TryGetCertificateInfo(string id)
|
||||||
{
|
{
|
||||||
var collection = _database.GetCollection<CertificateInfo>("Transactions");
|
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
||||||
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
|
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
|
||||||
if (!await cursor.MoveNextAsync())
|
if (!await cursor.MoveNextAsync())
|
||||||
{
|
{
|
||||||
@ -141,10 +147,23 @@ namespace QRBee.Api.Services.Database
|
|||||||
return cursor.Current.FirstOrDefault();
|
return cursor.Current.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CertificateInfo> GetCertificateInfo(string id)
|
public async Task<CertificateInfo> GetCertificateInfoByCertificateId(string id)
|
||||||
{
|
{
|
||||||
var certificate = await TryGetCertificateInfo(id);
|
var certificate = await TryGetCertificateInfo(id);
|
||||||
return certificate ?? throw new ApplicationException($"Certificate with Id: {id} not found.");
|
return certificate ?? throw new ApplicationException($"Certificate with Id: {id} not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<CertificateInfo> GetCertificateInfoByUserId(string clientId)
|
||||||
|
{
|
||||||
|
var collection = _database.GetCollection<CertificateInfo>("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.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,5 +34,12 @@ namespace QRBee.Api.Services.Database
|
|||||||
|
|
||||||
public PaymentRequest Request { get; set; }
|
public PaymentRequest Request { get; set; }
|
||||||
|
|
||||||
|
public enum TransactionStatus
|
||||||
|
{
|
||||||
|
Pending = 0,
|
||||||
|
Rejected = 1,
|
||||||
|
Succeeded = 2,
|
||||||
|
}
|
||||||
|
public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ namespace QRBee.Api.Services
|
|||||||
/// Handles InsertTransaction request
|
/// Handles InsertTransaction request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">Payment request</param>
|
/// <param name="value">Payment request</param>
|
||||||
Task InsertTransaction(PaymentRequest value);
|
Task<PaymentResponse> Pay(PaymentRequest value);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ namespace QRBee.Api.Services
|
|||||||
public async Task<RegistrationResponse> Register(RegistrationRequest request)
|
public async Task<RegistrationResponse> Register(RegistrationRequest request)
|
||||||
{
|
{
|
||||||
|
|
||||||
Validate(request);
|
ValidateRegistration(request);
|
||||||
|
|
||||||
var info = Convert(request);
|
var info = Convert(request);
|
||||||
|
|
||||||
@ -69,18 +69,11 @@ namespace QRBee.Api.Services
|
|||||||
|
|
||||||
public Task Update(string clientId, RegistrationRequest request)
|
public Task Update(string clientId, RegistrationRequest request)
|
||||||
{
|
{
|
||||||
Validate(request);
|
ValidateRegistration(request);
|
||||||
var info = Convert(request);
|
var info = Convert(request);
|
||||||
return _storage.UpdateUser(info);
|
return _storage.UpdateUser(info);
|
||||||
}
|
}
|
||||||
|
private void ValidateRegistration(RegistrationRequest request)
|
||||||
public Task InsertTransaction(PaymentRequest value)
|
|
||||||
{
|
|
||||||
var info = Convert(value);
|
|
||||||
return _storage.PutTransactionInfo(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Validate(RegistrationRequest request)
|
|
||||||
{
|
{
|
||||||
if (request == null)
|
if (request == null)
|
||||||
{
|
{
|
||||||
@ -92,14 +85,14 @@ namespace QRBee.Api.Services
|
|||||||
var dateOfBirth = request.DateOfBirth;
|
var dateOfBirth = request.DateOfBirth;
|
||||||
var certificateRequest = request.CertificateRequest;
|
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");
|
throw new ApplicationException($"Name \"{name}\" isn't valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
var freq = Regex.Matches(email, @"[^@]+@[^@]+").Count;
|
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");
|
throw new ApplicationException($"Email \"{email}\" isn't valid");
|
||||||
}
|
}
|
||||||
@ -130,6 +123,105 @@ namespace QRBee.Api.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PaymentResponse> 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)
|
private static RSA LoadRsaPublicKey(StringRSAParameters stringParameters)
|
||||||
{
|
{
|
||||||
var rsaParameters = new RSAParameters
|
var rsaParameters = new RSAParameters
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user