diff --git a/QRBee.Core/Client/Client.cs b/QRBee.Core/Client/Client.cs index 9a1b912..8e70d31 100644 --- a/QRBee.Core/Client/Client.cs +++ b/QRBee.Core/Client/Client.cs @@ -205,6 +205,77 @@ namespace QRBee.Core.Client } } + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task InsertTransactionAsync(PaymentRequest body) + { + return InsertTransactionAsync(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) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/QRBee/InsertTransaction"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) diff --git a/QRBee.Core/Data/ClientToMerchantResponse.cs b/QRBee.Core/Data/ClientToMerchantResponse.cs index 07f2339..a54b65a 100644 --- a/QRBee.Core/Data/ClientToMerchantResponse.cs +++ b/QRBee.Core/Data/ClientToMerchantResponse.cs @@ -2,7 +2,7 @@ { public record ClientToMerchantResponse { - public MerchantToClientRequest Request + public MerchantToClientRequest MerchantRequest { get; set; @@ -36,7 +36,7 @@ /// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature) /// /// Converted string - public string AsString() => $"{ClientId}|{TimeStampUTC:O}|{Request.AsString()}"; + public string AsQRCodeString() => $"{ClientId}|{TimeStampUTC:O}|{MerchantRequest.AsQRCodeString()}"; /// /// Convert from string @@ -47,14 +47,14 @@ public static ClientToMerchantResponse FromString(string input) { var s = input.Split('|'); - if (s.Length != 3) + if (s.Length < 3) { - throw new ApplicationException("Expected 3 elements"); + throw new ApplicationException("Expected 3 or more elements"); } var res = new ClientToMerchantResponse() { - Request = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))), + MerchantRequest = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))), ClientId = s[0], TimeStampUTC = DateTime.ParseExact(s[1], "O", null) }; diff --git a/QRBee.Core/Data/MerchantToClientRequest.cs b/QRBee.Core/Data/MerchantToClientRequest.cs index ea09ed9..42e7a8b 100644 --- a/QRBee.Core/Data/MerchantToClientRequest.cs +++ b/QRBee.Core/Data/MerchantToClientRequest.cs @@ -4,7 +4,13 @@ namespace QRBee.Core.Data { public record MerchantToClientRequest { - public string TransactionId + public string MerchantId + { + get; + set; + } + + public string MerchantTransactionId { get; set; @@ -38,7 +44,7 @@ namespace QRBee.Core.Data /// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature) /// /// String conversion - public string AsString() => $"{TransactionId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}"; + public string AsQRCodeString() => $"{MerchantTransactionId}|{MerchantId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}"; /// /// Convert from string @@ -49,17 +55,18 @@ namespace QRBee.Core.Data public static MerchantToClientRequest FromString(string input) { var s = input.Split('|'); - if (s.Length != 4) + if (s.Length != 5) { - throw new ApplicationException("Expected 4 elements"); + throw new ApplicationException("Expected 5 elements"); } var res = new MerchantToClientRequest { - TransactionId = s[0], - Name = s[1], - Amount = Convert.ToDecimal(s[2], CultureInfo.InvariantCulture), - TimeStampUTC = DateTime.ParseExact(s[3],"O",null) + MerchantId = s[0], + MerchantTransactionId = s[1], + Name = s[2], + Amount = Convert.ToDecimal(s[3], CultureInfo.InvariantCulture), + TimeStampUTC = DateTime.ParseExact(s[4],"O",null) }; diff --git a/QRBee.Core/Data/PaymentRequest.cs b/QRBee.Core/Data/PaymentRequest.cs index a3fb9b4..1b5adda 100644 --- a/QRBee.Core/Data/PaymentRequest.cs +++ b/QRBee.Core/Data/PaymentRequest.cs @@ -3,7 +3,7 @@ public record PaymentRequest { - public ClientToMerchantResponse Request + public ClientToMerchantResponse ClientResponse { get; set; @@ -13,7 +13,18 @@ /// Convert PaymentRequest to string /// /// Converted string - public string AsString() => Request.AsString(); + 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}; + } } } diff --git a/QRBee/QRBee/ViewModels/ClientPageViewModel.cs b/QRBee/QRBee/ViewModels/ClientPageViewModel.cs index fe8bc09..fc1d2b8 100644 --- a/QRBee/QRBee/ViewModels/ClientPageViewModel.cs +++ b/QRBee/QRBee/ViewModels/ClientPageViewModel.cs @@ -37,12 +37,12 @@ namespace QRBee.ViewModels { var scanner = DependencyService.Get(); var result = await scanner.ScanQR(); - if (result != null) - { - _merchantToClientRequest = MerchantToClientRequest.FromString(result); - Amount = $"{_merchantToClientRequest.Amount:N2}"; - IsVisible = true; - } + if (result == null) + return; + + _merchantToClientRequest = MerchantToClientRequest.FromString(result); + Amount = $"{_merchantToClientRequest.Amount:N2}"; + IsVisible = true; } catch (Exception) { @@ -99,21 +99,22 @@ namespace QRBee.ViewModels public async void OnGenerateQrClicked(object obj) { - bool answer = await _clientPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No"); - if (answer) - { - var response = new ClientToMerchantResponse - { - ClientId = Guid.NewGuid().ToString("D"), - TimeStampUTC = DateTime.UtcNow, - Request = _merchantToClientRequest - - }; - // TODO Create merchant signature. - QrCode = response.AsString(); - } + var answer = await _clientPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No"); + if (!answer) + return; + + var response = new ClientToMerchantResponse + { + //TODO get client id from database + ClientId = Guid.NewGuid().ToString("D"), + TimeStampUTC = DateTime.UtcNow, + MerchantRequest = _merchantToClientRequest + + }; + // TODO Create merchant signature. + QrCode = response.AsQRCodeString(); + - } } diff --git a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs index 47942b5..2e1d864 100644 --- a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs +++ b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using QRBee.Core.Data; using QRBee.Services; using Xamarin.Forms; @@ -29,17 +30,43 @@ namespace QRBee.ViewModels { var scanner = DependencyService.Get(); var result = await scanner.ScanQR(); - if (result != null) - { + if (result == null) + return; - } + var client = new HttpClient(GetInsecureHandler()); + var localSettings = DependencyService.Resolve(); + + var service = new Core.Client.Client(localSettings.QRBeeApiUrl, client); + var paymentRequest = PaymentRequest.FromString(result); + + //QrCode = null; + IsVisible = false; + + await service.InsertTransactionAsync(paymentRequest); + + await Application.Current.MainPage.DisplayAlert("Success", "The transaction completed successfully ", "Ok"); } - catch (Exception ex) + catch (Exception e) { - throw; + //TODO: delete exception message in error message + await Application.Current.MainPage.DisplayAlert("Error", $"The Backend isn't working: {e.Message}", "Ok"); } } + // This method must be in a class in a platform project, even if + // the HttpClient object is constructed in a shared project. + public HttpClientHandler GetInsecureHandler() + { + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => + { + if (cert.Issuer.Equals("CN=localhost")) + return true; + return errors == System.Net.Security.SslPolicyErrors.None; + }; + return handler; + } + public string Name { get; } public decimal Amount @@ -98,13 +125,15 @@ namespace QRBee.ViewModels { var trans = new MerchantToClientRequest { - TransactionId = Guid.NewGuid().ToString("D"), + //TODO get merchant id from database + MerchantId = Guid.NewGuid().ToString("D"), + MerchantTransactionId = Guid.NewGuid().ToString("D"), Name = Name, Amount = Amount, TimeStampUTC = DateTime.UtcNow }; // TODO Create merchant signature. - QrCode = trans.AsString(); + QrCode = trans.AsQRCodeString(); IsVisible = true; } diff --git a/QRBee/QRBee/ViewModels/RegisterViewModel.cs b/QRBee/QRBee/ViewModels/RegisterViewModel.cs index f48ac53..5058cec 100644 --- a/QRBee/QRBee/ViewModels/RegisterViewModel.cs +++ b/QRBee/QRBee/ViewModels/RegisterViewModel.cs @@ -94,7 +94,8 @@ namespace QRBee.ViewModels private async void OnRegisterClicked(object obj) { - using var client = new HttpClient(GetInsecureHandler()); + //TODO when to dispose the client? + var client = new HttpClient(GetInsecureHandler()); var localSettings = DependencyService.Resolve(); var service = new Core.Client.Client(localSettings.QRBeeApiUrl,client); diff --git a/QRBee/QRBee/Views/MerchantPage.xaml b/QRBee/QRBee/Views/MerchantPage.xaml index 3083f91..299adf2 100644 --- a/QRBee/QRBee/Views/MerchantPage.xaml +++ b/QRBee/QRBee/Views/MerchantPage.xaml @@ -27,7 +27,8 @@ BarcodeFormat="QR_CODE" BarcodeValue="{Binding QrCode}" HorizontalOptions="FillAndExpand" - VerticalOptions="FillAndExpand"> + VerticalOptions="FillAndExpand" + IsVisible="{Binding IsVisible}"> diff --git a/QRBeeApi/Controllers/QRBeeController.cs b/QRBeeApi/Controllers/QRBeeController.cs index b93fd37..1fd8bc6 100644 --- a/QRBeeApi/Controllers/QRBeeController.cs +++ b/QRBeeApi/Controllers/QRBeeController.cs @@ -34,5 +34,12 @@ namespace QRBee.Api.Controllers _logger.LogInformation($"Trying to update user {value.Name}"); return _service.Update(clientId,value); } + + [HttpPost("InsertTransaction")] + public Task InsertTransaction([FromBody] PaymentRequest value) + { + _logger.LogInformation($"Trying to insert new transaction {value.ClientResponse.MerchantRequest.MerchantTransactionId}"); + return _service.InsertTransaction(value); + } } } diff --git a/QRBeeApi/Services/Database/IStorage.cs b/QRBeeApi/Services/Database/IStorage.cs index b362113..a839d15 100644 --- a/QRBeeApi/Services/Database/IStorage.cs +++ b/QRBeeApi/Services/Database/IStorage.cs @@ -9,7 +9,6 @@ /// Insert userInfo into database /// /// Information to be inserted - /// Task PutUserInfo(UserInfo info); /// @@ -19,7 +18,17 @@ /// User information Task GetUserInfo(string email); + /// + /// Update user record + /// + /// New user record + /// Task UpdateUser(UserInfo info); + /// + /// Insert transactionInfo into database + /// + /// Information to be inserted + Task PutTransactionInfo(TransactionInfo info); } } diff --git a/QRBeeApi/Services/Database/Storage.cs b/QRBeeApi/Services/Database/Storage.cs index df59218..505814d 100644 --- a/QRBeeApi/Services/Database/Storage.cs +++ b/QRBeeApi/Services/Database/Storage.cs @@ -68,5 +68,37 @@ namespace QRBee.Api.Services.Database await collection.ReplaceOneAsync($"{{ _id: \"{info.Id}\" }}",info, new ReplaceOptions(){IsUpsert = false}); } + public async Task PutTransactionInfo(TransactionInfo info) + { + var collection = _database.GetCollection("Transactions"); + + var transaction = await TryGetTransactionInfo(info.Id); + + if (transaction == null) + { + await collection.InsertOneAsync(info); + _logger.LogInformation($"Inserted new transaction with ID: {info.Id}"); + return; + } + + _logger.LogInformation($"Found transaction with ClientId: {info.Id}"); + } + + /// + /// Try to find if the Transaction already exists in the database + /// + /// parameter by which to find TransactionInfo + /// null if transaction doesn't exist or TransactionInfo + private async Task TryGetTransactionInfo(string id) + { + var collection = _database.GetCollection("Transactions"); + using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}"); + if (!await cursor.MoveNextAsync()) + { + return null; + } + + return cursor.Current.FirstOrDefault(); + } } } diff --git a/QRBeeApi/Services/Database/TransactionInfo.cs b/QRBeeApi/Services/Database/TransactionInfo.cs new file mode 100644 index 0000000..e892bd9 --- /dev/null +++ b/QRBeeApi/Services/Database/TransactionInfo.cs @@ -0,0 +1,38 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using QRBee.Core.Data; + +namespace QRBee.Api.Services.Database +{ + public record TransactionInfo + { + +#pragma warning disable CS8618 + public TransactionInfo() +#pragma warning restore CS8618 + { + + } + + public TransactionInfo(PaymentRequest request, DateTime serverTimeStamp) + { + //TODO This is a side effect. The original request will be modified. + request.ClientResponse.EncryptedClientCardData = null; + + ServerTimeStamp = serverTimeStamp; + Request = request; + Id = $"{request.ClientResponse.MerchantRequest.MerchantId}-{request.ClientResponse.MerchantRequest.MerchantTransactionId}"; + } + + /// + /// Never use directly. Use instead. + /// + [BsonId] public string Id { get; set; } + + [BsonIgnore] public string? TransactionId => Id; + public DateTime ServerTimeStamp { get; set; } + + public PaymentRequest Request { get; set; } + + } +} diff --git a/QRBeeApi/Services/IQRBeeAPI.cs b/QRBeeApi/Services/IQRBeeAPI.cs index cf64ebf..8442b5b 100644 --- a/QRBeeApi/Services/IQRBeeAPI.cs +++ b/QRBeeApi/Services/IQRBeeAPI.cs @@ -23,5 +23,11 @@ namespace QRBee.Api.Services /// Update request Task Update(string clientId, RegistrationRequest value); + /// + /// Handles InsertTransaction request + /// + /// Payment request + Task InsertTransaction(PaymentRequest value); + } } diff --git a/QRBeeApi/Services/QRBeeAPI.cs b/QRBeeApi/Services/QRBeeAPI.cs index f39bf31..064a254 100644 --- a/QRBeeApi/Services/QRBeeAPI.cs +++ b/QRBeeApi/Services/QRBeeAPI.cs @@ -39,6 +39,12 @@ namespace QRBee.Api.Services return _storage.UpdateUser(info); } + public Task InsertTransaction(PaymentRequest value) + { + var info = Convert(value); + return _storage.PutTransactionInfo(info); + } + private static void Validate(RegistrationRequest request) { if (request == null) @@ -77,5 +83,10 @@ namespace QRBee.Api.Services return new UserInfo(request.Name, request.Email, request.DateOfBirth); } + private static TransactionInfo Convert(PaymentRequest request) + { + return new TransactionInfo(request, DateTime.UtcNow); + } + } }