Harden paid registration flow and add unit tests

This commit is contained in:
2026-04-24 21:10:28 -04:00
parent 4153acf3aa
commit 27ac793f62
8 changed files with 817 additions and 79 deletions
@@ -195,6 +195,49 @@ public class StripeService : IStripeService
return session.Url;
}
/// <summary>
/// Verifies that the supplied Stripe Checkout session belongs to the registration flow and has
/// reached the paid/complete state. Returns <c>false</c> for any missing/invalid/unpaid session
/// so the caller can safely stop before creating any local company or user records.
/// </summary>
public async Task<bool> IsRegistrationCheckoutPaidAsync(string sessionId)
{
try
{
var sessionService = new SessionService();
var session = await sessionService.GetAsync(sessionId);
if (!session.Metadata.TryGetValue("registration", out var isRegistration) ||
!string.Equals(isRegistration, "true", StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning(
"Registration checkout validation failed for session {SessionId}: missing registration metadata",
sessionId);
return false;
}
var isPaidAndComplete = session.PaymentStatus == "paid" && session.Status == "complete";
if (!isPaidAndComplete)
{
_logger.LogWarning(
"Registration checkout validation failed for session {SessionId}: paymentStatus={PaymentStatus}, status={Status}",
sessionId, session.PaymentStatus, session.Status);
}
return isPaidAndComplete;
}
catch (StripeException ex)
{
_logger.LogWarning(ex, "Stripe rejected registration checkout validation for session {SessionId}", sessionId);
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error validating registration checkout session {SessionId}", sessionId);
return false;
}
}
/// <summary>
/// Finalizes a registration checkout after payment is confirmed. Called from the
/// registration success redirect (not the webhook path). Retrieves the session from Stripe