feat: Add Stripe payment integration and subscription management

- Add Stripe.net SDK for payment processing
- Implement StripeService with checkout sessions, customer portal, webhooks
- Implement SubscriptionService for quota management
- Add quota enforcement to CVCheckService
- Create Pricing, Billing, Settings pages
- Add checkout success/cancel pages
- Update Check and Dashboard with usage indicators
- Add ResetMonthlyUsageJob for billing cycle resets
- Add database migration for subscription fields

Plan tiers: Free (3 checks), Professional £49/mo (30), Enterprise £199/mo (unlimited)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 12:03:24 +00:00
parent 6f384f8d09
commit 28d7d41b25
27 changed files with 2427 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
@implements IDisposable
@inject ICVCheckService CVCheckService
@inject ISubscriptionService SubscriptionService
@inject NavigationManager NavigationManager
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject ILogger<Dashboard> Logger
@@ -18,6 +19,30 @@
<div>
<h1 class="fw-bold mb-1">Dashboard</h1>
<p class="text-muted mb-0">View and manage your CV verification checks</p>
@if (_subscription != null)
{
<div class="mt-2">
@if (_subscription.IsUnlimited)
{
<span class="badge bg-success-subtle text-success px-3 py-2">
Enterprise - Unlimited checks
</span>
}
else
{
var percentage = _subscription.MonthlyLimit > 0
? Math.Min(100, (_subscription.ChecksUsedThisMonth * 100) / _subscription.MonthlyLimit)
: 0;
<span class="badge @(percentage >= 90 ? "bg-danger-subtle text-danger" : percentage >= 75 ? "bg-warning-subtle text-warning" : "bg-primary-subtle text-primary") px-3 py-2">
@_subscription.Plan: @_subscription.ChecksUsedThisMonth / @_subscription.MonthlyLimit checks used
</span>
@if (_subscription.ChecksRemaining <= 0)
{
<a href="/pricing" class="btn btn-sm btn-warning ms-2">Upgrade</a>
}
}
</div>
}
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-secondary" @onclick="ExportToPdf" disabled="@(_isExporting || !HasCompletedChecks())">
@@ -486,6 +511,7 @@
private bool _isDeleting;
private string? _errorMessage;
private Guid _userId;
private SubscriptionInfoDto? _subscription;
private System.Threading.Timer? _pollingTimer;
private volatile bool _isPolling;
private volatile bool _disposed;
@@ -579,6 +605,7 @@
}
_checks = await CVCheckService.GetUserChecksAsync(_userId) ?? [];
_subscription = await SubscriptionService.GetSubscriptionInfoAsync(_userId);
}
catch (Exception ex)
{