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) { diff --git a/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs new file mode 100644 index 0000000..b9ca162 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Abstractions/IPaymentGatewayService.cs @@ -0,0 +1,10 @@ +using TickAPI.Common.Payment.Models; +using TickAPI.Common.Results.Generic; + +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/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/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/Models/PaymentRequestPG.cs b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs new file mode 100644 index 0000000..68511e8 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs @@ -0,0 +1,12 @@ +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, + [property: JsonPropertyName("force_error")] bool ForceError +); \ 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..f30f958 --- /dev/null +++ b/TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs @@ -0,0 +1,51 @@ +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 HealthCheck() + { + 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) + { + var client = _httpClientFactory.CreateClient(); + var baseUrl = _configuration["PaymentGateway:Url"]!; + var url = $"{baseUrl}/payments"; + 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..0a431a1 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -39,6 +39,9 @@ using TickAPI.Common.Redis.Services; 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 const string allowClientPolicyName = "AllowClient"; @@ -122,6 +125,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(); @@ -180,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(); 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