mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
Transaction functionality complete.
This commit is contained in:
parent
82b8d1820e
commit
1a89deb394
@ -2,35 +2,15 @@
|
|||||||
{
|
{
|
||||||
public record ClientToMerchantResponse
|
public record ClientToMerchantResponse
|
||||||
{
|
{
|
||||||
public MerchantToClientRequest MerchantRequest
|
public MerchantToClientRequest MerchantRequest { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ClientId
|
public string ClientId { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime TimeStampUTC
|
public DateTime TimeStampUTC { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ClientSignature
|
public string ClientSignature { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string EncryptedClientCardData
|
public string EncryptedClientCardData { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature)
|
/// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature)
|
||||||
|
|||||||
@ -4,41 +4,17 @@ namespace QRBee.Core.Data
|
|||||||
{
|
{
|
||||||
public record MerchantToClientRequest
|
public record MerchantToClientRequest
|
||||||
{
|
{
|
||||||
public string MerchantId
|
public string MerchantId { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MerchantTransactionId
|
public string MerchantTransactionId { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
public string Name { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public decimal Amount
|
public decimal Amount { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime TimeStampUTC
|
public DateTime TimeStampUTC { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MerchantSignature
|
public string MerchantSignature { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature)
|
/// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature)
|
||||||
|
|||||||
@ -2,23 +2,22 @@
|
|||||||
{
|
{
|
||||||
public record PaymentResponse
|
public record PaymentResponse
|
||||||
{
|
{
|
||||||
|
public string ServerTransactionId { get; set; }
|
||||||
|
|
||||||
public string ServerTransactionId
|
public PaymentRequest PaymentRequest { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentRequest PaymentRequest
|
public DateTime ServerTimeStampUTC { get; set; }
|
||||||
{
|
|
||||||
get;
|
public bool Success { get; set; }
|
||||||
set;
|
|
||||||
}
|
public string RejectReason { get; set; }
|
||||||
|
|
||||||
|
public string ServerSignature { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert PaymentResponse to string to be encrypted and transmitted back to merchant
|
/// Convert PaymentResponse to string to be encrypted and transmitted back to merchant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Converted string</returns>
|
/// <returns>Converted string</returns>
|
||||||
public string AsString() => $"{ServerTransactionId}|{PaymentRequest.AsString()}";
|
public string AsDataForSignature() => $"{ServerTransactionId}|{PaymentRequest.AsString()}|{ServerTimeStampUTC:O}|{Success}|{RejectReason}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,35 +4,15 @@ namespace QRBee.Core.Data
|
|||||||
{
|
{
|
||||||
public record RegistrationRequest
|
public record RegistrationRequest
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Email
|
public string Email { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DateOfBirth
|
public string DateOfBirth { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadableCertificateRequest CertificateRequest
|
public ReadableCertificateRequest CertificateRequest { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RegisterAsMerchant
|
public bool RegisterAsMerchant { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,23 +6,11 @@ namespace QRBee.Core.Data
|
|||||||
{
|
{
|
||||||
public record RegistrationResponse
|
public record RegistrationResponse
|
||||||
{
|
{
|
||||||
public string ClientId
|
public string ClientId { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ClientCertificate
|
public string ClientCertificate { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string APIServerCertificate
|
public string APIServerCertificate { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,8 +50,29 @@ namespace QRBee.ViewModels
|
|||||||
|
|
||||||
var response = await service.PayAsync(paymentRequest);
|
var response = await service.PayAsync(paymentRequest);
|
||||||
|
|
||||||
//TODO handle response
|
if (response.Success)
|
||||||
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
|
{
|
||||||
|
var check = _securityService.Verify(
|
||||||
|
Encoding.UTF8.GetBytes(response.AsDataForSignature()),
|
||||||
|
Convert.FromBase64String(response.ServerSignature),
|
||||||
|
_securityService.APIServerCertificate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (check)
|
||||||
|
{
|
||||||
|
await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Application.Current.MainPage.DisplayAlert("Failure", "Invalid server signature", "Ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Application.Current.MainPage.DisplayAlert("Failure", $"The transaction failed: {response.RejectReason}", "Ok");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -31,6 +31,7 @@ builder.Services
|
|||||||
.AddSingleton<IMongoClient>( cfg => new MongoClient(cfg.GetRequiredService<IOptions<DatabaseSettings>>().Value.ToMongoDbSettings()))
|
.AddSingleton<IMongoClient>( cfg => new MongoClient(cfg.GetRequiredService<IOptions<DatabaseSettings>>().Value.ToMongoDbSettings()))
|
||||||
.AddSingleton<IPrivateKeyHandler, ServerPrivateKeyHandler>()
|
.AddSingleton<IPrivateKeyHandler, ServerPrivateKeyHandler>()
|
||||||
.AddSingleton<ISecurityService, SecurityService>()
|
.AddSingleton<ISecurityService, SecurityService>()
|
||||||
|
.AddSingleton<IPaymentGateway, PaymentGateway>()
|
||||||
;
|
;
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
@ -38,6 +38,12 @@
|
|||||||
/// <returns>Transaction information</returns>
|
/// <returns>Transaction information</returns>
|
||||||
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update transaction after execution
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">Transaction to be updated</param>
|
||||||
|
Task UpdateTransaction(TransactionInfo info);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inserts CertificateInfo into database
|
/// Inserts CertificateInfo into database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -93,7 +93,7 @@ namespace QRBee.Api.Services.Database
|
|||||||
private async Task<TransactionInfo?> TryGetTransactionInfo(string id)
|
private async Task<TransactionInfo?> TryGetTransactionInfo(string id)
|
||||||
{
|
{
|
||||||
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
||||||
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
|
using var cursor = await collection.FindAsync($"{{ _id: \"{id}\" }}");
|
||||||
if (!await cursor.MoveNextAsync())
|
if (!await cursor.MoveNextAsync())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@ -108,6 +108,12 @@ namespace QRBee.Api.Services.Database
|
|||||||
return transaction ?? throw new ApplicationException($"Transaction with Id: {id} not found.");
|
return transaction ?? throw new ApplicationException($"Transaction with Id: {id} not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateTransaction(TransactionInfo info)
|
||||||
|
{
|
||||||
|
var collection = _database.GetCollection<TransactionInfo>("Transactions");
|
||||||
|
await collection.ReplaceOneAsync($"{{ _id: \"{info.Id}\" }}", info, new ReplaceOptions() { IsUpsert = false });
|
||||||
|
}
|
||||||
|
|
||||||
public async Task InsertCertificate(CertificateInfo info)
|
public async Task InsertCertificate(CertificateInfo info)
|
||||||
{
|
{
|
||||||
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
||||||
|
|||||||
@ -41,5 +41,7 @@ namespace QRBee.Api.Services.Database
|
|||||||
Succeeded = 2,
|
Succeeded = 2,
|
||||||
}
|
}
|
||||||
public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
|
public TransactionStatus Status { get; set; } = TransactionStatus.Pending;
|
||||||
|
|
||||||
|
public string? RejectReason { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
QRBeeApi/Services/IPaymentGateway.cs
Normal file
17
QRBeeApi/Services/IPaymentGateway.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using QRBee.Api.Services.Database;
|
||||||
|
using QRBee.Core.Data;
|
||||||
|
|
||||||
|
namespace QRBee.Api.Services
|
||||||
|
{
|
||||||
|
|
||||||
|
public class GatewayResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; init; }
|
||||||
|
public string? ErrorMessage { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPaymentGateway
|
||||||
|
{
|
||||||
|
Task<GatewayResponse> Payment(TransactionInfo info, ClientCardData clientCardData);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
QRBeeApi/Services/PaymentGateway.cs
Normal file
32
QRBeeApi/Services/PaymentGateway.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using QRBee.Api.Services.Database;
|
||||||
|
using QRBee.Core.Data;
|
||||||
|
|
||||||
|
namespace QRBee.Api.Services;
|
||||||
|
|
||||||
|
internal class PaymentGateway : IPaymentGateway
|
||||||
|
{
|
||||||
|
private readonly ILogger<Storage> _logger;
|
||||||
|
|
||||||
|
public PaymentGateway(ILogger<Storage> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<GatewayResponse> Payment(TransactionInfo info, ClientCardData clientCardData)
|
||||||
|
{
|
||||||
|
if (info.Request.ClientResponse.MerchantRequest.Amount < 10)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Transaction with id: {info.Id} failed");
|
||||||
|
return Task.FromResult(new GatewayResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "Amount is too low"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_logger.LogInformation($"Transaction with id: {info.Id} succeeded");
|
||||||
|
return Task.FromResult(new GatewayResponse
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -18,16 +19,18 @@ namespace QRBee.Api.Services
|
|||||||
private readonly IStorage _storage;
|
private readonly IStorage _storage;
|
||||||
private readonly ISecurityService _securityService;
|
private readonly ISecurityService _securityService;
|
||||||
private readonly IPrivateKeyHandler _privateKeyHandler;
|
private readonly IPrivateKeyHandler _privateKeyHandler;
|
||||||
|
private readonly IPaymentGateway _paymentGateway;
|
||||||
private static readonly object _lock = new ();
|
private static readonly object _lock = new ();
|
||||||
|
|
||||||
private const int MaxNameLength = 512;
|
private const int MaxNameLength = 512;
|
||||||
private const int MaxEmailLength = 512;
|
private const int MaxEmailLength = 512;
|
||||||
|
|
||||||
public QRBeeAPIService(IStorage storage, ISecurityService securityService, IPrivateKeyHandler privateKeyHandler)
|
public QRBeeAPIService(IStorage storage, ISecurityService securityService, IPrivateKeyHandler privateKeyHandler, IPaymentGateway paymentGateway)
|
||||||
{
|
{
|
||||||
_storage = storage;
|
_storage = storage;
|
||||||
_securityService = securityService;
|
_securityService = securityService;
|
||||||
_privateKeyHandler = privateKeyHandler;
|
_privateKeyHandler = privateKeyHandler;
|
||||||
|
_paymentGateway = paymentGateway;
|
||||||
Init(_privateKeyHandler);
|
Init(_privateKeyHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,13 +153,42 @@ namespace QRBee.Api.Services
|
|||||||
var clientCardData = DecryptClientData(value.ClientResponse.EncryptedClientCardData);
|
var clientCardData = DecryptClientData(value.ClientResponse.EncryptedClientCardData);
|
||||||
|
|
||||||
//6. Check client card data for validity
|
//6. Check client card data for validity
|
||||||
|
await CheckClientCardData(clientCardData);
|
||||||
|
|
||||||
//7. Register preliminary transaction record with expiry of one minute
|
//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);
|
var info = Convert(value);
|
||||||
|
info.Status = TransactionInfo.TransactionStatus.Pending;
|
||||||
|
|
||||||
await _storage.PutTransactionInfo(info);
|
await _storage.PutTransactionInfo(info);
|
||||||
return new PaymentResponse();
|
|
||||||
|
//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 = new PaymentResponse
|
||||||
|
{
|
||||||
|
ServerTransactionId = info.TransactionId,
|
||||||
|
PaymentRequest = value,
|
||||||
|
ServerTimeStampUTC = DateTime.UtcNow,
|
||||||
|
Success = res.Success,
|
||||||
|
RejectReason = res.ErrorMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
var signature = _securityService.Sign(Encoding.UTF8.GetBytes(response.AsDataForSignature()));
|
||||||
|
response.ServerSignature = System.Convert.ToBase64String(signature);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateTransaction(PaymentRequest request)
|
private void ValidateTransaction(PaymentRequest request)
|
||||||
@ -222,6 +254,31 @@ namespace QRBee.Api.Services
|
|||||||
return ClientCardData.FromString(s);
|
return ClientCardData.FromString(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckClientCardData(ClientCardData data)
|
||||||
|
{
|
||||||
|
var transactionId = data.TransactionId;
|
||||||
|
var expirationDate = DateTime.Parse(data.ExpirationDateMMYY);
|
||||||
|
var validFrom = DateTime.Parse(data.ValidFrom);
|
||||||
|
var holderName = data.CardHolderName;
|
||||||
|
|
||||||
|
await CheckTransaction(transactionId);
|
||||||
|
|
||||||
|
if (expirationDate <= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"The expiration date: {expirationDate} is wrong");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFrom > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"The valid from date: {validFrom} is wrong");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holderName.Any(char.IsDigit))
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"The card holder name: {holderName} is wrong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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