From dd6041d7c6982fc91939190b84ffbdc29ab216d0 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Thu, 1 May 2025 11:20:23 +0200 Subject: [PATCH 1/5] minor fixes --- TickAPI/TickAPI/Common/Mail/Services/MailService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/TickAPI/TickAPI/Common/Mail/Services/MailService.cs b/TickAPI/TickAPI/Common/Mail/Services/MailService.cs index 1e5a1c9..24456bb 100644 --- a/TickAPI/TickAPI/Common/Mail/Services/MailService.cs +++ b/TickAPI/TickAPI/Common/Mail/Services/MailService.cs @@ -1,5 +1,4 @@ -using System.Net; -using SendGrid; +using SendGrid; using SendGrid.Helpers.Mail; using TickAPI.Common.Mail.Abstractions; using TickAPI.Common.Mail.Models; @@ -9,8 +8,8 @@ namespace TickAPI.Common.Mail.Services; public class MailService : IMailService { - private SendGridClient _client; - private EmailAddress _fromEmailAddress; + private readonly SendGridClient _client; + private readonly EmailAddress _fromEmailAddress; public MailService(IConfiguration configuration) { From c275607c9f6e1f51ae05541e90ac042b329b7734 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Thu, 1 May 2025 12:44:44 +0200 Subject: [PATCH 2/5] Create `PaymentGatewayService` --- .../Abstractions/IPaymentGatewayService.cs | 9 +++++ .../Payment/Models/PaymentErrorResponsePG.cs | 7 ++++ .../Common/Payment/Models/PaymentRequestPG.cs | 11 ++++++ .../Payment/Models/PaymentResponsePG.cs | 8 ++++ .../Payment/Services/PaymentGatewayService.cs | 39 +++++++++++++++++++ TickAPI/TickAPI/Program.cs | 3 ++ TickAPI/TickAPI/appsettings.example.json | 4 +- 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Models/PaymentErrorResponsePG.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Models/PaymentResponsePG.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs diff --git a/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs new file mode 100644 index 0000000..5ba1be0 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs @@ -0,0 +1,9 @@ +using TickAPI.Common.Payment.Models; +using TickAPI.Common.Results.Generic; + +namespace TickAPI.Common.Payment.Abstractions; + +public interface IPaymentGatewayService +{ + Task> ProcessPayment(PaymentRequestPG request); +} diff --git a/TickAPI/TickAPI/Common/Payment/Models/PaymentErrorResponsePG.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentErrorResponsePG.cs new file mode 100644 index 0000000..74e1618 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentErrorResponsePG.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace TickAPI.Common.Payment.Models; + +public record PaymentErrorResponsePG( + [property: JsonPropertyName("error")] string Error +); diff --git a/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs new file mode 100644 index 0000000..40b551c --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace TickAPI.Common.Payment.Models; + +public record PaymentRequestPG( + [property: JsonPropertyName("amount")] decimal Amount, + [property: JsonPropertyName("currency")] string Currency, + [property: JsonPropertyName("card_number")] string CardNumber, + [property: JsonPropertyName("card_expiry")] string CardExpiry, + [property: JsonPropertyName("cvv")] string CVV +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Payment/Models/PaymentResponsePG.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentResponsePG.cs new file mode 100644 index 0000000..fac4e2a --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentResponsePG.cs @@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace TickAPI.Common.Payment.Models; + +public record PaymentResponsePG( + [property: JsonPropertyName("transaction_id")] string TransactionId, + [property: JsonPropertyName("status")] string Status +); diff --git a/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs new file mode 100644 index 0000000..e5a67f9 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs @@ -0,0 +1,39 @@ +using System.Text; +using System.Text.Json; +using TickAPI.Common.Payment.Abstractions; +using TickAPI.Common.Payment.Models; +using TickAPI.Common.Results.Generic; + +namespace TickAPI.Common.Payment.Services; + +public class PaymentGatewayService : IPaymentGatewayService +{ + private readonly IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; + + public PaymentGatewayService(IConfiguration configuration, IHttpClientFactory httpClientFactory) + { + _configuration = configuration; + _httpClientFactory = httpClientFactory; + } + + public async Task> ProcessPayment(PaymentRequestPG request) + { + var client = _httpClientFactory.CreateClient(); + var url = _configuration["PaymentGateway:Url"]!; + var json = JsonSerializer.Serialize(request); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await client.PostAsync(url, content); + var jsonResponse = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + var errorResponse = JsonSerializer.Deserialize(jsonResponse, new JsonSerializerOptions()); + return Result.Failure((int)response.StatusCode, errorResponse!.Error); + } + + var successResponse = JsonSerializer.Deserialize(jsonResponse, new JsonSerializerOptions()); + return Result.Success(successResponse!); + } +} diff --git a/TickAPI/TickAPI/Program.cs b/TickAPI/TickAPI/Program.cs index f355ec8..cb5bd92 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -39,6 +39,8 @@ using TickAPI.Common.Redis.Services; using TickAPI.Common.Mail.Abstractions; using TickAPI.Common.Mail.Services; +using TickAPI.Common.Payment.Abstractions; +using TickAPI.Common.Payment.Services; // Builder constants const string allowClientPolicyName = "AllowClient"; @@ -122,6 +124,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/TickAPI/TickAPI/appsettings.example.json b/TickAPI/TickAPI/appsettings.example.json index bbb517d..420009a 100644 --- a/TickAPI/TickAPI/appsettings.example.json +++ b/TickAPI/TickAPI/appsettings.example.json @@ -26,10 +26,12 @@ "ExpirySeconds" : "3600" } }, - "SendGrid": { "ApiKey": "ApiKey", "FromEmail": "your_mail", "FromName": "Resellio" + }, + "PaymentGateway": { + "Url": "http://localhost:7474" } } \ No newline at end of file From 0ec7be2f71d6ad3694b8072044e6cc8e1f534d3e Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Fri, 2 May 2025 00:18:15 +0200 Subject: [PATCH 3/5] Fix invalid url --- .../Common/Payment/Services/PaymentGatewayService.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs index e5a67f9..f6f4a85 100644 --- a/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs +++ b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs @@ -17,10 +17,16 @@ public PaymentGatewayService(IConfiguration configuration, IHttpClientFactory ht _httpClientFactory = httpClientFactory; } + public Task> HealthCheck() + { + throw new NotImplementedException(); + } + public async Task> ProcessPayment(PaymentRequestPG request) { var client = _httpClientFactory.CreateClient(); - var url = _configuration["PaymentGateway:Url"]!; + var baseUrl = _configuration["PaymentGateway:Url"]!; + var url = $"{baseUrl}/payments"; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); From 51421d7baeb1a2dbf9ea25ebb6b853c552deca80 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Fri, 2 May 2025 00:18:27 +0200 Subject: [PATCH 4/5] Add missing field --- TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs index 40b551c..68511e8 100644 --- a/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs @@ -7,5 +7,6 @@ public record PaymentRequestPG( [property: JsonPropertyName("currency")] string Currency, [property: JsonPropertyName("card_number")] string CardNumber, [property: JsonPropertyName("card_expiry")] string CardExpiry, - [property: JsonPropertyName("cvv")] string CVV + [property: JsonPropertyName("cvv")] string CVV, + [property: JsonPropertyName("force_error")] bool ForceError ); \ No newline at end of file From 57b62932b700b47ca0a8f1cc7826737ebfdbd2b6 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Fri, 2 May 2025 00:33:41 +0200 Subject: [PATCH 5/5] Create health check for payment gateway connection --- .../Abstractions/IPaymentGatewayService.cs | 1 + .../PaymentGatewayHealthStatusExtensions.cs | 11 ++++++++ .../Health/PaymentGatewayHealthCheck.cs | 25 +++++++++++++++++++ .../Models/PaymentGatewayHealthStatus.cs | 7 ++++++ .../Payment/Services/PaymentGatewayService.cs | 10 ++++++-- TickAPI/TickAPI/Program.cs | 5 +++- 6 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 TickAPI/TickAPI/Common/Payment/Extensions/PaymentGatewayHealthStatusExtensions.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Health/PaymentGatewayHealthCheck.cs create mode 100644 TickAPI/TickAPI/Common/Payment/Models/PaymentGatewayHealthStatus.cs diff --git a/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs index 5ba1be0..b9ca162 100644 --- a/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs +++ b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs @@ -5,5 +5,6 @@ namespace TickAPI.Common.Payment.Abstractions; public interface IPaymentGatewayService { + Task HealthCheck(); Task> ProcessPayment(PaymentRequestPG request); } diff --git a/TickAPI/TickAPI/Common/Payment/Extensions/PaymentGatewayHealthStatusExtensions.cs b/TickAPI/TickAPI/Common/Payment/Extensions/PaymentGatewayHealthStatusExtensions.cs new file mode 100644 index 0000000..629d4e0 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Extensions/PaymentGatewayHealthStatusExtensions.cs @@ -0,0 +1,11 @@ +using TickAPI.Common.Payment.Models; + +namespace TickAPI.Common.Payment.Extensions; + +public static class PaymentGatewayHealthStatusExtensions +{ + public static bool IsHealthy(this PaymentGatewayHealthStatus response) + { + return response.Status.Equals("ok", StringComparison.CurrentCultureIgnoreCase); + } +} diff --git a/TickAPI/TickAPI/Common/Payment/Health/PaymentGatewayHealthCheck.cs b/TickAPI/TickAPI/Common/Payment/Health/PaymentGatewayHealthCheck.cs new file mode 100644 index 0000000..858a9d3 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Health/PaymentGatewayHealthCheck.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using TickAPI.Common.Payment.Abstractions; +using TickAPI.Common.Payment.Extensions; + +namespace TickAPI.Common.Payment.Health; + +public class PaymentGatewayHealthCheck : IHealthCheck +{ + private readonly IPaymentGatewayService _paymentGateway; + + public PaymentGatewayHealthCheck(IPaymentGatewayService paymentGateway) + { + _paymentGateway = paymentGateway; + } + + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) + { + var status = await _paymentGateway.HealthCheck(); + if (status.IsHealthy()) + { + return HealthCheckResult.Healthy("Payment gateway is reachable."); + } + return HealthCheckResult.Unhealthy("Payment gateway is not reachable."); + } +} diff --git a/TickAPI/TickAPI/Common/Payment/Models/PaymentGatewayHealthStatus.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentGatewayHealthStatus.cs new file mode 100644 index 0000000..e4b0b7a --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentGatewayHealthStatus.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace TickAPI.Common.Payment.Models; + +public record PaymentGatewayHealthStatus( + [property: JsonPropertyName("status")] string Status +); diff --git a/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs index f6f4a85..f30f958 100644 --- a/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs +++ b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs @@ -17,9 +17,15 @@ public PaymentGatewayService(IConfiguration configuration, IHttpClientFactory ht _httpClientFactory = httpClientFactory; } - public Task> HealthCheck() + public async Task HealthCheck() { - throw new NotImplementedException(); + var client = _httpClientFactory.CreateClient(); + var baseUrl = _configuration["PaymentGateway:Url"]!; + var url = $"{baseUrl}/health"; + var response = await client.GetAsync(url); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var status = JsonSerializer.Deserialize(jsonResponse, new JsonSerializerOptions()); + return status!; } public async Task> ProcessPayment(PaymentRequestPG request) diff --git a/TickAPI/TickAPI/Program.cs b/TickAPI/TickAPI/Program.cs index cb5bd92..0a431a1 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -40,6 +40,7 @@ using TickAPI.Common.Mail.Abstractions; using TickAPI.Common.Mail.Services; using TickAPI.Common.Payment.Abstractions; +using TickAPI.Common.Payment.Health; using TickAPI.Common.Payment.Services; // Builder constants @@ -183,7 +184,9 @@ // TODO: when we start using redis we should probably also check here if we can connect to it // Setup healtcheck -builder.Services.AddHealthChecks().AddSqlServer(connectionString: builder.Configuration.GetConnectionString("ResellioDatabase") ?? ""); +builder.Services.AddHealthChecks() + .AddSqlServer(connectionString: builder.Configuration.GetConnectionString("ResellioDatabase") ?? "") + .AddCheck("PaymentGateway"); // Add http client builder.Services.AddHttpClient();