Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions TickAPI/TickAPI/Common/Mail/Services/MailService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using TickAPI.Common.Payment.Models;
using TickAPI.Common.Results.Generic;

namespace TickAPI.Common.Payment.Abstractions;

public interface IPaymentGatewayService
{
Task<PaymentGatewayHealthStatus> HealthCheck();
Task<Result<PaymentResponsePG>> ProcessPayment(PaymentRequestPG request);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
25 changes: 25 additions & 0 deletions TickAPI/TickAPI/Common/Payment/Health/PaymentGatewayHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -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<HealthCheckResult> 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.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Text.Json.Serialization;

namespace TickAPI.Common.Payment.Models;

public record PaymentErrorResponsePG(
[property: JsonPropertyName("error")] string Error
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Text.Json.Serialization;

namespace TickAPI.Common.Payment.Models;

public record PaymentGatewayHealthStatus(
[property: JsonPropertyName("status")] string Status
);
12 changes: 12 additions & 0 deletions TickAPI/TickAPI/Common/Payment/Models/PaymentRequestPG.cs
Original file line number Diff line number Diff line change
@@ -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
);
8 changes: 8 additions & 0 deletions TickAPI/TickAPI/Common/Payment/Models/PaymentResponsePG.cs
Original file line number Diff line number Diff line change
@@ -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
);
51 changes: 51 additions & 0 deletions TickAPI/TickAPI/Common/Payment/Services/PaymentGatewayService.cs
Original file line number Diff line number Diff line change
@@ -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<PaymentGatewayHealthStatus> 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<PaymentGatewayHealthStatus>(jsonResponse, new JsonSerializerOptions());
return status!;
}

public async Task<Result<PaymentResponsePG>> 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<PaymentErrorResponsePG>(jsonResponse, new JsonSerializerOptions());
return Result<PaymentResponsePG>.Failure((int)response.StatusCode, errorResponse!.Error);
}

var successResponse = JsonSerializer.Deserialize<PaymentResponsePG>(jsonResponse, new JsonSerializerOptions());
return Result<PaymentResponsePG>.Success(successResponse!);
}
}
8 changes: 7 additions & 1 deletion TickAPI/TickAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -56,8 +59,8 @@
})
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Authentication:Google:ClientId"];

Check warning on line 62 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];

Check warning on line 63 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
})
.AddJwtBearer(options =>
{
Expand All @@ -69,7 +72,7 @@
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Authentication:Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Authentication:Jwt:SecurityKey"]))

Check warning on line 75 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 's' in 'byte[] Encoding.GetBytes(string s)'.
};
});

Expand Down Expand Up @@ -122,6 +125,7 @@
builder.Services.AddScoped<IClaimsService, ClaimsService>();
builder.Services.AddScoped<IRedisService, RedisService>();
builder.Services.AddScoped<IMailService, MailService>();
builder.Services.AddScoped<IPaymentGatewayService, PaymentGatewayService>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
Expand Down Expand Up @@ -161,7 +165,7 @@
});

builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("ResellioRedisCache"))

Check warning on line 168 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'configuration' in 'ConnectionMultiplexer ConnectionMultiplexer.Connect(string configuration, TextWriter? log = null)'.
);

// Create CORS policy
Expand All @@ -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<PaymentGatewayHealthCheck>("PaymentGateway");

// Add http client
builder.Services.AddHttpClient();
Expand Down
4 changes: 3 additions & 1 deletion TickAPI/TickAPI/appsettings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
"ExpirySeconds" : "3600"
}
},

"SendGrid": {
"ApiKey": "ApiKey",
"FromEmail": "your_mail",
"FromName": "Resellio"
},
"PaymentGateway": {
"Url": "http://localhost:7474"
}
}
Loading