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 MerchantToClientRequest MerchantRequest
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public MerchantToClientRequest MerchantRequest { get; set; }
|
||||
|
||||
public string ClientId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public DateTime TimeStampUTC
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTime TimeStampUTC { get; set; }
|
||||
|
||||
public string ClientSignature
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ClientSignature { get; set; }
|
||||
|
||||
public string EncryptedClientCardData
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string EncryptedClientCardData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 string MerchantId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string MerchantId { get; set; }
|
||||
|
||||
public string MerchantTransactionId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string MerchantTransactionId { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
|
||||
public decimal Amount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public DateTime TimeStampUTC
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTime TimeStampUTC { get; set; }
|
||||
|
||||
public string MerchantSignature
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string MerchantSignature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature)
|
||||
|
||||
@ -2,23 +2,22 @@
|
||||
{
|
||||
public record PaymentResponse
|
||||
{
|
||||
public string ServerTransactionId { get; set; }
|
||||
|
||||
public string ServerTransactionId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public PaymentRequest PaymentRequest { get; set; }
|
||||
|
||||
public PaymentRequest PaymentRequest
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTime ServerTimeStampUTC { get; set; }
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string RejectReason { get; set; }
|
||||
|
||||
public string ServerSignature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert PaymentResponse to string to be encrypted and transmitted back to merchant
|
||||
/// </summary>
|
||||
/// <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 string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Email
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Email { get; set; }
|
||||
|
||||
public string DateOfBirth
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string DateOfBirth { get; set; }
|
||||
|
||||
public ReadableCertificateRequest CertificateRequest
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public ReadableCertificateRequest CertificateRequest { get; set; }
|
||||
|
||||
public bool RegisterAsMerchant
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public bool RegisterAsMerchant { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -6,23 +6,11 @@ namespace QRBee.Core.Data
|
||||
{
|
||||
public record RegistrationResponse
|
||||
{
|
||||
public string ClientId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public string ClientCertificate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ClientCertificate { get; set; }
|
||||
|
||||
public string APIServerCertificate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string APIServerCertificate { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,9 +50,30 @@ namespace QRBee.ViewModels
|
||||
|
||||
var response = await service.PayAsync(paymentRequest);
|
||||
|
||||
//TODO handle response
|
||||
if (response.Success)
|
||||
{
|
||||
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)
|
||||
{
|
||||
//TODO: delete exception message in error message
|
||||
|
||||
@ -31,6 +31,7 @@ builder.Services
|
||||
.AddSingleton<IMongoClient>( cfg => new MongoClient(cfg.GetRequiredService<IOptions<DatabaseSettings>>().Value.ToMongoDbSettings()))
|
||||
.AddSingleton<IPrivateKeyHandler, ServerPrivateKeyHandler>()
|
||||
.AddSingleton<ISecurityService, SecurityService>()
|
||||
.AddSingleton<IPaymentGateway, PaymentGateway>()
|
||||
;
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@ -38,6 +38,12 @@
|
||||
/// <returns>Transaction information</returns>
|
||||
Task<TransactionInfo> GetTransactionInfoByTransactionId(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Update transaction after execution
|
||||
/// </summary>
|
||||
/// <param name="info">Transaction to be updated</param>
|
||||
Task UpdateTransaction(TransactionInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts CertificateInfo into database
|
||||
/// </summary>
|
||||
|
||||
@ -93,7 +93,7 @@ namespace QRBee.Api.Services.Database
|
||||
private async Task<TransactionInfo?> TryGetTransactionInfo(string id)
|
||||
{
|
||||
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())
|
||||
{
|
||||
return null;
|
||||
@ -108,6 +108,12 @@ namespace QRBee.Api.Services.Database
|
||||
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)
|
||||
{
|
||||
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
||||
|
||||
@ -41,5 +41,7 @@ namespace QRBee.Api.Services.Database
|
||||
Succeeded = 2,
|
||||
}
|
||||
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.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
@ -18,16 +19,18 @@ namespace QRBee.Api.Services
|
||||
private readonly IStorage _storage;
|
||||
private readonly ISecurityService _securityService;
|
||||
private readonly IPrivateKeyHandler _privateKeyHandler;
|
||||
private readonly IPaymentGateway _paymentGateway;
|
||||
private static readonly object _lock = new ();
|
||||
|
||||
private const int MaxNameLength = 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;
|
||||
_securityService = securityService;
|
||||
_privateKeyHandler = privateKeyHandler;
|
||||
_paymentGateway = paymentGateway;
|
||||
Init(_privateKeyHandler);
|
||||
}
|
||||
|
||||
@ -150,13 +153,42 @@ namespace QRBee.Api.Services
|
||||
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
|
||||
//8. Send client card data to a payment gateway
|
||||
//9. Record transaction with result
|
||||
//10. Make response for merchant
|
||||
var info = Convert(value);
|
||||
info.Status = TransactionInfo.TransactionStatus.Pending;
|
||||
|
||||
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)
|
||||
@ -222,6 +254,31 @@ namespace QRBee.Api.Services
|
||||
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)
|
||||
{
|
||||
var rsaParameters = new RSAParameters
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user