Mitc.Integrations.Stripe 2.1.0
Mitc.Integrations.Stripe
A turnkey Stripe integration library for ASP.NET Core applications. Handles webhook processing, product/price catalog syncing, subscription management, checkout session creation, and metered usage reporting.
Supported Frameworks
- .NET 9
- .NET 10
Setup
1. Configuration
Add a StripeOptions section to your appsettings.json:
{
"StripeOptions": {
"ApiKey": "sk_...",
"EndpointSecret": "whsec_...",
"RegistrationCompletedPage": "https://example.com/registration/complete",
"RegistrationCancelledPage": "https://example.com/registration/cancelled",
"AllowLiveEvents": true,
"AllowTestEvents": false,
"EnableCatalogSync": true,
"SyncInterval": "01:00:00",
"EnableUsageReporting": true,
"ReportFrequency": "01:00:00",
"EnableLocalWebhook": true,
"ProcessingDelay": "00:00:05",
"ProcessingInterval": "00:00:01",
"EventCleanupInterval": "1.00:00:00",
"EventRetentionPeriod": "30.00:00:00",
"MaxEventRetries": 3,
"EventRetryDelay": "00:00:20"
}
}
| Option | Description |
|---|---|
ApiKey |
Stripe secret API key. Required. |
EndpointSecret |
Stripe webhook signing secret. Required. |
RegistrationCompletedPage |
URL the customer is redirected to after checkout. Required. |
RegistrationCancelledPage |
URL the customer is redirected to if they cancel checkout. Required. |
AllowLiveEvents |
Accept events from Stripe live mode. At least one of AllowLiveEvents / AllowTestEvents must be true. |
AllowTestEvents |
Accept events from Stripe test mode. |
EnableCatalogSync |
Enables periodic polling of Stripe for catalog updates. When false, SyncInterval is not required. |
SyncInterval |
How often to poll Stripe for product/price catalog changes. Required when EnableCatalogSync is true. |
EnableUsageReporting |
Enables periodic reporting of subscriber usage to Stripe. When false, ReportFrequency is not required. |
ReportFrequency |
How often metered usage is reported to Stripe. Required when EnableUsageReporting is true. |
EnableLocalWebhook |
Enables local webhook event processing. When false, ProcessingDelay and ProcessingInterval are not required. |
ProcessingDelay |
Grace period before processing an event, allowing out-of-order events to arrive. Required when EnableLocalWebhook is true. |
ProcessingInterval |
How often the event processor drains and processes queued events. Required when EnableLocalWebhook is true. |
EventCleanupInterval |
How often old processed events are cleaned up from the database. |
EventRetentionPeriod |
How long processed events are retained before deletion. |
AllowPromotionCodes |
Whether or not promotional codes are allowed during checkout. |
2. Register Services
builder.SetupStripeIntegration<AppDbContext>();
The generic type parameter TDbContext must be your application's DbContext type. This allows the default store implementations to resolve your concrete DbContext from DI.
This registers all required services, background workers, and a webhook controller endpoint at POST /stripe/webhook.
3. EF Core Entity Configuration
Implement IStripeContext on your DbContext and call ConfigureStripeEntities in OnModelCreating:
public class AppDbContext : DbContext, IStripeContext
{
public DbSet<StripeEvent> StripeEvents { get; init; }
public DbSet<StripeProduct> StripeProducts { get; init; }
public DbSet<StripePrice> StripePrices { get; init; }
public DbSet<StripeCheckoutSession> StripeCheckoutSessions { get; init; }
public DbSet<StripeSubscription> StripeSubscriptions { get; init; }
public DbSet<StripeSubscriptionItem> StripeSubscriptionItems { get; init; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ConfigureStripeEntities();
}
}
ConfigureStripeEntities configures string primary keys (Stripe-assigned, not auto-generated), StripeMetadataValueConverter on metadata properties, and navigation relationships (StripeProduct → StripePrice, StripeSubscription → StripeSubscriptionItem).
4. Implement Required Interfaces
Checkout session event handler — No default implementation is provided; you must supply one:
public class MyCheckoutHandler : IStripeCheckoutSessionEventHandler
{
public Task<Outcome> HandleCheckoutSessionCompleted(Session session) { /* ... */ }
public Task<Outcome> HandleCheckoutSessionExpired(Session session) { /* ... */ }
// Async (delayed) payment methods fire async_payment_succeeded / async_payment_failed.
// Both default to a no-op success, so only override them if you accept such methods:
// public Task<Outcome> HandleCheckoutSessionAsyncPaymentSucceeded(Session session) { /* ... */ }
// public Task<Outcome> HandleCheckoutSessionAsyncPaymentFailed(Session session) { /* ... */ }
}
Usage report generator — Required by the usage reporting service. Return one SubscriberUsage per active subscription item. Only include active, non-cancelled subscriptions; cancelled or ended subscriptions should be filtered out by your query:
public class MyUsageReportGenerator : IStripeUsageReportGenerator
{
public Task<IEnumerable<SubscriberUsage>> GetUsagesAsync() { /* ... */ }
}
Customizing Services
Default store implementations are provided and registered automatically. Custom stores are only needed if you require behavior beyond the defaults. The default stores expose virtual members, so you can inherit one and override only the methods you need:
public class MyStripeEventStore(AppDbContext db) : StripeEventStore(db);
public class MyStripeProductStore(AppDbContext db) : StripeProductStore(db);
public class MyStripePriceStore(AppDbContext db) : StripePriceStore(db);
public class MyStripeSubscriptionStore(AppDbContext db) : StripeSubscriptionStore(db);
Or implement the store interfaces (IStripeEventStore, IStripeProductStore, etc.) directly for full control.
Use the builder callback to register custom stores or event handlers:
builder.SetupStripeIntegration<AppDbContext>(stripe => stripe
.AddEventStore<MyStripeEventStore>()
.AddProductStore<MyStripeProductStore>()
.AddPriceStore<MyStripePriceStore>()
.AddSubscriptionStore<MyStripeSubscriptionStore>()
.AddCheckoutSessionEventHandler<MyCheckoutHandler>()
);
Builder overrides are registered before the library's defaults, so TryAddScoped will skip any service you've already configured.
Available Builder Methods
Stores (scoped, depend on DbContext):
| Method | Interface |
|---|---|
AddEventStore<T>() |
IStripeEventStore |
AddProductStore<T>() |
IStripeProductStore |
AddPriceStore<T>() |
IStripePriceStore |
AddSubscriptionStore<T>() |
IStripeSubscriptionStore |
Event handlers (scoped):
| Method | Interface |
|---|---|
AddCheckoutSessionEventHandler<T>() |
IStripeCheckoutSessionEventHandler |
AddProductEventHandler<T>() |
IStripeProductEventHandler |
AddPriceEventHandler<T>() |
IStripePriceEventHandler |
AddSubscriptionEventHandler<T>() |
IStripeSubscriptionEventHandler |
AddUsageReportGenerator<T>() |
IStripeUsageReportGenerator |
Architecture
Event Processing Pipeline
Stripe webhook
-> StripeController (POST /stripe/webhook)
-> IStripeEventParser (signature verification)
-> Event.IsForEnabledMode (live/test filter; ignored events are acked and dropped)
-> IStripeEventStore.AddAsync (idempotent durable insert — 200 OK only after the row commits)
StripeEventProcessor (background service, periodic)
-> IStripeEventStore.GetProcessableEventsAsync (settled + due events)
-> IStripe*EventHandler (handle by type, in dependency order)
Events are persisted at intake and become processable only after they have aged past ProcessingDelay (the settle window, keyed off arrival time). This lets a burst of out-of-order events finish arriving before the processor sorts and applies them by (Created, tier, id). Dedup is a primary-key guarantee: a redelivered event id is swallowed on insert.
Because every event is durable before the webhook is acked, ProcessingDelay is purely a query delay rather than an in-memory hold — it can be lengthened to absorb larger or more out-of-order bursts with no added risk of data loss.
Background Services
| Service | Responsibility |
|---|---|
StripeEventProcessor |
Reads settled, due events from the store and dispatches them to handlers in dependency order. |
StripeEventCleanupService |
Deletes processed events older than EventRetentionPeriod. |
StripeOfferingSyncService |
Periodically syncs the product/price catalog from Stripe. Deactivates local products and prices that no longer exist on Stripe. |
StripeUsageReportingService |
Reports metered subscription usage to Stripe. |
Upgrading: This version adds a
ReceivedAtcolumn to theStripeEvententity. Consuming applications must add and apply an EF Core migration (dotnet ef migrations add AddStripeEventReceivedAt) against their ownDbContext.
Handled Webhook Events
| Category | Events |
|---|---|
| Checkout Session | completed, expired, async_payment_succeeded, async_payment_failed |
| Product | created, updated, deleted |
| Price | created, updated, deleted |
| Subscription | created, updated, deleted, paused, resumed |
Metadata
StripeMetadata provides strongly-typed access to Stripe metadata dictionaries via DataKey<T>:
public static readonly DataKey<Guid> RegistrationId = new("registration_id");
// Writing
var metadata = new StripeMetadata();
metadata.Add(RegistrationId, someGuid);
// Reading
Guid id = metadata.Get(RegistrationId);
Guid? maybeId = metadata.TryGet(RegistrationId);
Includes an EF Core ValueConverter (StripeMetadataValueConverter) for database persistence.
Checkout
IStripeOperations creates Stripe Checkout sessions for both subscriptions and one-time payments. The mode is selected automatically from the price: a recurring price creates a subscription, a one-time price creates a payment.
public class MyService(IStripeOperations operations)
{
public async Task StartCheckout(ICheckoutRequest request, StripeMetadata metadata)
{
var session = await operations.CreateCheckoutSessionAsync(request, metadata);
// Redirect user to session.Url
}
}
The provided metadata is attached to the checkout session and, depending on the mode, the resulting subscription or payment intent.
Local Development
When builder.Environment.IsDevelopment() is true, the library:
- Bypasses Stripe webhook signature verification
- Starts a
StripeLocalEventListenerthat uses the Stripe CLI to forward events to your local server (requires the Stripe CLI to be installed andEnableLocalWebhookto be set totrue)
No packages depend on Mitc.Integrations.Stripe.
.NET 10.0
- Mitc.Support.Results (>= 1.1.0)
- CliWrap (>= 3.10.0)
- Microsoft.EntityFrameworkCore (>= 10.0.0 && < 11.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0 && < 11.0.0)
- Mitc.Support.Enumeration (>= 1.0.5)
- Mitc.Support.Enumeration.EntityFramework (>= 1.0.1)
- Stripe.net (>= 50.3.0)
.NET 9.0
- Mitc.Support.Results (>= 1.1.0)
- CliWrap (>= 3.10.0)
- Microsoft.EntityFrameworkCore (>= 9.0.0 && < 10.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0 && < 10.0.0)
- Mitc.Support.Enumeration (>= 1.0.5)
- Mitc.Support.Enumeration.EntityFramework (>= 1.0.1)
- Stripe.net (>= 50.3.0)
| Version | Downloads | Last updated |
|---|---|---|
| 4.0.0 | 0 | 6/10/2026 |
| 3.3.0 | 0 | 6/8/2026 |
| 3.2.0 | 0 | 6/5/2026 |
| 3.1.0 | 0 | 6/5/2026 |
| 3.0.0 | 0 | 6/5/2026 |
| 2.1.0 | 0 | 6/4/2026 |
| 2.0.0 | 0 | 6/2/2026 |
| 1.6.0 | 0 | 4/29/2026 |
| 1.5.0 | 0 | 4/13/2026 |
| 1.4.0 | 0 | 4/13/2026 |
| 1.3.6 | 16 | 2/21/2026 |
| 1.3.5 | 0 | 2/18/2026 |
| 1.3.1 | 27 | 2/18/2026 |
| 1.3.0 | 5 | 2/17/2026 |
| 1.2.9 | 4 | 2/12/2026 |
| 1.2.8 | 7 | 2/11/2026 |
| 1.2.7 | 1 | 2/11/2026 |
| 1.2.6 | 1 | 2/11/2026 |
| 1.2.5 | 1 | 2/11/2026 |
| 1.2.4 | 1 | 2/11/2026 |
| 1.2.3 | 1 | 2/11/2026 |
| 1.2.2 | 2 | 2/11/2026 |
| 1.2.1 | 1 | 2/11/2026 |
| 1.2.0 | 1 | 2/11/2026 |
| 1.1.9 | 1 | 2/10/2026 |
| 1.1.8 | 1 | 2/10/2026 |
| 1.1.7 | 1 | 2/9/2026 |
| 1.1.6 | 1 | 2/9/2026 |
| 1.1.5 | 1 | 2/9/2026 |
| 1.1.4 | 1 | 2/9/2026 |
| 1.1.3 | 1 | 2/9/2026 |
| 1.1.2 | 1 | 2/9/2026 |
| 1.1.1 | 1 | 2/9/2026 |
| 1.1.0 | 1 | 2/9/2026 |
| 1.0.10 | 1 | 2/9/2026 |
| 1.0.9 | 1 | 2/7/2026 |
| 1.0.8 | 1 | 2/7/2026 |
| 1.0.7 | 1 | 2/7/2026 |
| 1.0.6 | 1 | 2/7/2026 |
| 1.0.5 | 1 | 2/7/2026 |
| 1.0.4 | 1 | 2/7/2026 |
| 1.0.3 | 1 | 2/7/2026 |
| 1.0.2 | 1 | 2/6/2026 |
| 1.0.1 | 0 | 2/6/2026 |
| 1.0.0 | 0 | 2/6/2026 |