feat(payments): enhance payment model migrations and update event handling

This commit is contained in:
Stevan Freeborn
2026-01-04 21:32:30 -06:00
parent d27e33a69b
commit 4f485644eb
9 changed files with 109 additions and 102 deletions
+2 -2
View File
@@ -52,8 +52,8 @@ jobs:
script: | script: |
TAG=${{ secrets.DOCKERHUB_USERNAME }}/groundsforsupport.stevanfreeborn.com:${{ needs.build.outputs.version }} TAG=${{ secrets.DOCKERHUB_USERNAME }}/groundsforsupport.stevanfreeborn.com:${{ needs.build.outputs.version }}
docker stop groundsforsupport.stevanfreeborn.com docker stop groundsforsupport.stevanfreeborn.com || true
docker rm groundsforsupport.stevanfreeborn.com docker rm groundsforsupport.stevanfreeborn.com || true
docker pull $TAG docker pull $TAG
docker run \ docker run \
@@ -4,29 +4,29 @@
namespace GroundsForSupport.Server.Migrations namespace GroundsForSupport.Server.Migrations
{ {
/// <inheritdoc />
public partial class AddPaymentsModel : Migration
{
/// <inheritdoc /> /// <inheritdoc />
public partial class AddPaymentsModel : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.CreateTable(
protected override void Up(MigrationBuilder migrationBuilder) name: "Payments",
{ columns: table => new
migrationBuilder.CreateTable( {
name: "Payments", Id = table.Column<string>(type: "TEXT", nullable: false)
columns: table => new },
{ constraints: table =>
Id = table.Column<string>(type: "TEXT", nullable: false) {
}, table.PrimaryKey("PK_Payments", x => x.Id);
constraints: table => });
{
table.PrimaryKey("PK_Payments", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Payments");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Payments");
}
}
}
@@ -4,26 +4,26 @@
namespace GroundsForSupport.Server.Migrations namespace GroundsForSupport.Server.Migrations
{ {
/// <inheritdoc />
public partial class AddAmountToPaymentModel : Migration
{
/// <inheritdoc /> /// <inheritdoc />
public partial class AddAmountToPaymentModel : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.AddColumn<long>(
protected override void Up(MigrationBuilder migrationBuilder) name: "Amount",
{ table: "Payments",
migrationBuilder.AddColumn<long>( type: "INTEGER",
name: "Amount", nullable: false,
table: "Payments", defaultValue: 0L);
type: "INTEGER",
nullable: false,
defaultValue: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Amount",
table: "Payments");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Amount",
table: "Payments");
}
}
}
@@ -4,36 +4,36 @@
namespace GroundsForSupport.Server.Migrations namespace GroundsForSupport.Server.Migrations
{ {
/// <inheritdoc />
public partial class AddNameMessagePropertiesToPaymentModel : Migration
{
/// <inheritdoc /> /// <inheritdoc />
public partial class AddNameMessagePropertiesToPaymentModel : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.AddColumn<string>(
protected override void Up(MigrationBuilder migrationBuilder) name: "Message",
{ table: "Payments",
migrationBuilder.AddColumn<string>( type: "TEXT",
name: "Message", nullable: true);
table: "Payments",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>( migrationBuilder.AddColumn<string>(
name: "Name", name: "Name",
table: "Payments", table: "Payments",
type: "TEXT", type: "TEXT",
nullable: false, nullable: false,
defaultValue: ""); defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Message",
table: "Payments");
migrationBuilder.DropColumn(
name: "Name",
table: "Payments");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Message",
table: "Payments");
migrationBuilder.DropColumn(
name: "Name",
table: "Payments");
}
}
}
@@ -4,26 +4,26 @@
namespace GroundsForSupport.Server.Migrations namespace GroundsForSupport.Server.Migrations
{ {
/// <inheritdoc />
public partial class AddCreatedAtTimeStampToPaymentsModel : Migration
{
/// <inheritdoc /> /// <inheritdoc />
public partial class AddCreatedAtTimeStampToPaymentsModel : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.AddColumn<long>(
protected override void Up(MigrationBuilder migrationBuilder) name: "CreatedAtUnix",
{ table: "Payments",
migrationBuilder.AddColumn<long>( type: "INTEGER",
name: "CreatedAtUnix", nullable: false,
table: "Payments", defaultValue: 0L);
type: "INTEGER",
nullable: false,
defaultValue: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAtUnix",
table: "Payments");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAtUnix",
table: "Payments");
}
}
}
@@ -29,9 +29,8 @@ internal static class EventsEndpoint
try try
{ {
var stripeEvent = EventUtility.ParseEvent(json);
var signatureHeader = httpContext.Request.Headers[StripeSignatureHeader]; var signatureHeader = httpContext.Request.Headers[StripeSignatureHeader];
stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.EventsWebhookSecret); var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.EventsWebhookSecret);
if (stripeEvent.Type is not EventTypes.PaymentIntentSucceeded) if (stripeEvent.Type is not EventTypes.PaymentIntentSucceeded)
{ {
@@ -39,8 +38,14 @@ internal static class EventsEndpoint
} }
var paymentIntent = (PaymentIntent)stripeEvent.Data.Object; var paymentIntent = (PaymentIntent)stripeEvent.Data.Object;
var name = paymentIntent.Metadata[nameof(Payment.Name)];
var message = paymentIntent.Metadata[nameof(Payment.Message)]; var name = paymentIntent.Metadata.TryGetValue(nameof(Payment.Name), out var metaName)
? metaName
: "unknown";
var message = paymentIntent.Metadata.TryGetValue(nameof(Payment.Message), out var metaMessage)
? metaMessage
: string.Empty;
var payment = new Payment() var payment = new Payment()
{ {
@@ -8,10 +8,12 @@ namespace GroundsForSupport.Server.Payments.Stripe;
internal sealed class StripeService( internal sealed class StripeService(
IOptions<StripeOptions> options, IOptions<StripeOptions> options,
HttpClient httpClient HttpClient httpClient,
ILogger<StripeService> logger
) : IStripeService ) : IStripeService
{ {
private readonly StripeClient _client = new(options.Value.ApiKey, httpClient: new SystemNetHttpClient(httpClient)); private readonly StripeClient _client = new(options.Value.ApiKey, httpClient: new SystemNetHttpClient(httpClient));
private readonly ILogger<StripeService> _logger = logger;
public async Task<(bool IsSuccess, Intent Intent)> CreatePaymentIntentAsync(string name, decimal amount, string? message, string? email) public async Task<(bool IsSuccess, Intent Intent)> CreatePaymentIntentAsync(string name, decimal amount, string? message, string? email)
{ {
@@ -43,7 +45,7 @@ internal sealed class StripeService(
} }
catch (Exception) catch (Exception)
{ {
// TODO: Log exception _logger.LogError("Failed to create Stripe payment intent for {Name} with amount {Amount}", name, amount);
return (false, new Intent(string.Empty)); return (false, new Intent(string.Empty));
} }
} }
@@ -11,7 +11,7 @@ internal static class FixedRateLimitPolicy
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown", partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: static partition => new FixedWindowRateLimiterOptions factory: static partition => new FixedWindowRateLimiterOptions
{ {
PermitLimit = 10, PermitLimit = 100,
Window = TimeSpan.FromHours(1), Window = TimeSpan.FromHours(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0 QueueLimit = 0
@@ -4,4 +4,4 @@ namespace GroundsForSupport.API.Tests.Integration.Infra;
public sealed class TestApi : WebApplicationFactory<Program> public sealed class TestApi : WebApplicationFactory<Program>
{ {
} }