Skip to content

Commit 18511b4

Browse files
committed
Add WebhookEvent entity for tracking webhook deliveries
1 parent 35c549c commit 18511b4

5 files changed

Lines changed: 2452 additions & 2 deletions

File tree

cloud/src/LrmCloud.Api/Controllers/WebhooksController.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,35 @@ private async Task<IActionResult> HandleWebhookAsync(string providerName, string
9494
return BadRequest($"Missing {signatureHeader} header");
9595
}
9696

97+
// Collect additional headers for PayPal verification
98+
var webhookHeaders = new Dictionary<string, string>();
99+
if (providerName == "paypal")
100+
{
101+
// PayPal requires additional headers for webhook verification
102+
var headerNames = new[]
103+
{
104+
"PAYPAL-AUTH-ALGO",
105+
"PAYPAL-CERT-URL",
106+
"PAYPAL-TRANSMISSION-ID",
107+
"PAYPAL-TRANSMISSION-TIME",
108+
"PAYPAL-TRANSMISSION-SIG"
109+
};
110+
111+
foreach (var headerName in headerNames)
112+
{
113+
var value = Request.Headers[headerName].FirstOrDefault();
114+
if (!string.IsNullOrEmpty(value))
115+
{
116+
webhookHeaders[headerName] = value;
117+
}
118+
}
119+
}
120+
97121
try
98122
{
99-
// Process webhook with provider
100-
var result = await provider.ProcessWebhookAsync(payload, signature);
123+
// Process webhook with provider (pass headers for PayPal verification)
124+
var result = await provider.ProcessWebhookAsync(payload, signature,
125+
webhookHeaders.Count > 0 ? webhookHeaders : null);
101126

102127
if (!result.Success)
103128
{

cloud/src/LrmCloud.Api/Data/AppDbContext.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
5656
public DbSet<ProjectReviewer> ProjectReviewers => Set<ProjectReviewer>();
5757
public DbSet<OrganizationReviewer> OrganizationReviewers => Set<OrganizationReviewer>();
5858

59+
// Billing & Webhooks
60+
public DbSet<WebhookEvent> WebhookEvents => Set<WebhookEvent>();
61+
5962
protected override void OnModelCreating(ModelBuilder modelBuilder)
6063
{
6164
base.OnModelCreating(modelBuilder);
@@ -518,6 +521,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
518521
// Match parent's soft-delete filter
519522
entity.HasQueryFilter(e => e.Organization!.DeletedAt == null);
520523
});
524+
525+
// =====================================================================
526+
// Webhook Events (Idempotency)
527+
// =====================================================================
528+
modelBuilder.Entity<WebhookEvent>(entity =>
529+
{
530+
// Unique constraint to prevent duplicate processing
531+
entity.HasIndex(e => new { e.ProviderEventId, e.ProviderName }).IsUnique();
532+
entity.HasIndex(e => e.ProcessedAt);
533+
entity.HasIndex(e => e.UserId);
534+
});
521535
}
522536

523537
/// <summary>

0 commit comments

Comments
 (0)