diff --git a/src/Billing/BillingSettings.cs b/src/Billing/BillingSettings.cs index 06027952c..91ea8f122 100644 --- a/src/Billing/BillingSettings.cs +++ b/src/Billing/BillingSettings.cs @@ -6,6 +6,7 @@ public class BillingSettings public virtual string StripeWebhookKey { get; set; } public virtual string StripeWebhookSecret { get; set; } public virtual string StripeWebhookSecret20231016 { get; set; } + public virtual string StripeWebhookSecret20240620 { get; set; } public virtual string BitPayWebhookKey { get; set; } public virtual string AppleWebhookKey { get; set; } public virtual FreshDeskSettings FreshDesk { get; set; } = new FreshDeskSettings(); diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index 9bca9f024..5ea2733a1 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -37,13 +37,18 @@ public class StripeController : Controller { if (!CoreHelpers.FixedTimeEquals(key, _billingSettings.StripeWebhookKey)) { + _logger.LogError("Stripe webhook key does not match configured webhook key"); return new BadRequestResult(); } var parsedEvent = await TryParseEventFromRequestBodyAsync(); if (parsedEvent is null) { - return Ok(); + return Ok(new + { + Processed = false, + Message = "Could not find a configured webhook secret to process this event with" + }); } if (StripeConfiguration.ApiVersion != parsedEvent.ApiVersion) @@ -54,7 +59,11 @@ public class StripeController : Controller parsedEvent.ApiVersion, StripeConfiguration.ApiVersion); - return new OkResult(); + return Ok(new + { + Processed = false, + Message = "SDK API version does not match the event's API version" + }); } if (string.IsNullOrWhiteSpace(parsedEvent?.Id)) @@ -72,11 +81,19 @@ public class StripeController : Controller // If the customer and server cloud regions don't match, early return 200 to avoid unnecessary errors if (!await _stripeEventService.ValidateCloudRegion(parsedEvent)) { - return new OkResult(); + return Ok(new + { + Processed = false, + Message = "Event is not for this cloud region" + }); } await _stripeEventProcessor.ProcessEventAsync(parsedEvent); - return Ok(); + return Ok(new + { + Processed = true, + Message = "Processed" + }); } /// @@ -89,19 +106,49 @@ public class StripeController : Controller /// private string PickStripeWebhookSecret(string webhookBody) { - var versionContainer = JsonSerializer.Deserialize(webhookBody); + var deliveryContainer = JsonSerializer.Deserialize(webhookBody); - return versionContainer.ApiVersion switch + _logger.LogInformation( + "Picking secret for Stripe webhook | {EventID}: {EventType} | Version: {APIVersion} | Initiating Request ID: {RequestID}", + deliveryContainer.Id, + deliveryContainer.Type, + deliveryContainer.ApiVersion, + deliveryContainer.Request?.Id); + + return deliveryContainer.ApiVersion switch { - "2023-10-16" => _billingSettings.StripeWebhookSecret20231016, - "2022-08-01" => _billingSettings.StripeWebhookSecret, - _ => HandleDefault(versionContainer.ApiVersion) + "2024-06-20" => HandleVersionWith(_billingSettings.StripeWebhookSecret20240620), + "2023-10-16" => HandleVersionWith(_billingSettings.StripeWebhookSecret20231016), + "2022-08-01" => HandleVersionWith(_billingSettings.StripeWebhookSecret), + _ => HandleDefault(deliveryContainer.ApiVersion) }; + string HandleVersionWith(string secret) + { + if (string.IsNullOrEmpty(secret)) + { + _logger.LogError("No webhook secret is configured for API version {APIVersion}", deliveryContainer.ApiVersion); + return null; + } + + if (!secret.StartsWith("whsec_")) + { + _logger.LogError("Webhook secret configured for API version {APIVersion} does not start with whsec_", + deliveryContainer.ApiVersion); + return null; + } + + var truncatedSecret = secret[..10]; + + _logger.LogInformation("Picked webhook secret {TruncatedSecret}... for API version {APIVersion}", truncatedSecret, deliveryContainer.ApiVersion); + + return secret; + } + string HandleDefault(string version) { _logger.LogWarning( - "Stripe webhook contained an recognized 'api_version': {ApiVersion}", + "Stripe webhook contained an API version ({APIVersion}) we do not process", version); return null; @@ -121,22 +168,13 @@ public class StripeController : Controller if (string.IsNullOrEmpty(webhookSecret)) { - _logger.LogDebug("Unable to parse event. No webhook secret."); return null; } - var parsedEvent = EventUtility.ConstructEvent( + return EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], webhookSecret, throwOnApiVersionMismatch: false); - - if (parsedEvent is not null) - { - return parsedEvent; - } - - _logger.LogDebug("Stripe-Signature request header doesn't match configured Stripe webhook secret"); - return null; } } diff --git a/src/Billing/Models/StripeWebhookDeliveryContainer.cs b/src/Billing/Models/StripeWebhookDeliveryContainer.cs new file mode 100644 index 000000000..6588aa7d1 --- /dev/null +++ b/src/Billing/Models/StripeWebhookDeliveryContainer.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace Bit.Billing.Models; + +public class StripeWebhookDeliveryContainer +{ + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("api_version")] + public string ApiVersion { get; set; } + [JsonPropertyName("request")] + public StripeWebhookRequestData Request { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } +} + +public class StripeWebhookRequestData +{ + [JsonPropertyName("id")] + public string Id { get; set; } +} diff --git a/src/Billing/Models/StripeWebhookVersionContainer.cs b/src/Billing/Models/StripeWebhookVersionContainer.cs deleted file mode 100644 index 594a0f0ed..000000000 --- a/src/Billing/Models/StripeWebhookVersionContainer.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Bit.Billing.Models; - -public class StripeWebhookVersionContainer -{ - [JsonPropertyName("api_version")] - public string ApiVersion { get; set; } -} diff --git a/src/Billing/appsettings.json b/src/Billing/appsettings.json index 498578457..84a67434f 100644 --- a/src/Billing/appsettings.json +++ b/src/Billing/appsettings.json @@ -59,6 +59,7 @@ "stripeWebhookKey": "SECRET", "stripeWebhookSecret": "SECRET", "stripeWebhookSecret20231016": "SECRET", + "stripeWebhookSecret20240620": "SECRET", "bitPayWebhookKey": "SECRET", "appleWebhookKey": "SECRET", "payPal": { diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 416991ac7..2d16a0317 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -55,7 +55,7 @@ - + diff --git a/test/Billing.Test/Resources/Events/charge.succeeded.json b/test/Billing.Test/Resources/Events/charge.succeeded.json index a5446a11d..3cf919f12 100644 --- a/test/Billing.Test/Resources/Events/charge.succeeded.json +++ b/test/Billing.Test/Resources/Events/charge.succeeded.json @@ -1,7 +1,7 @@ { "id": "evt_3NvKgBIGBnsLynRr0pJJqudS", "object": "event", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695909300, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/customer.subscription.updated.json b/test/Billing.Test/Resources/Events/customer.subscription.updated.json index dbd30f1c4..62a8590fa 100644 --- a/test/Billing.Test/Resources/Events/customer.subscription.updated.json +++ b/test/Billing.Test/Resources/Events/customer.subscription.updated.json @@ -1,7 +1,7 @@ { "id": "evt_1NvLMDIGBnsLynRr6oBxebrE", "object": "event", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695911902, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/customer.updated.json b/test/Billing.Test/Resources/Events/customer.updated.json index c2445c24e..9aa092851 100644 --- a/test/Billing.Test/Resources/Events/customer.updated.json +++ b/test/Billing.Test/Resources/Events/customer.updated.json @@ -2,7 +2,7 @@ "id": "evt_1NvKjSIGBnsLynRrS3MTK4DZ", "object": "event", "account": "acct_19smIXIGBnsLynRr", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695909502, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/invoice.created.json b/test/Billing.Test/Resources/Events/invoice.created.json index 4c3c85233..bf53372b5 100644 --- a/test/Billing.Test/Resources/Events/invoice.created.json +++ b/test/Billing.Test/Resources/Events/invoice.created.json @@ -1,7 +1,7 @@ { "id": "evt_1NvKzfIGBnsLynRr0SkwrlkE", "object": "event", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695910506, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/invoice.finalized.json b/test/Billing.Test/Resources/Events/invoice.finalized.json index e71cb2b4c..207fab497 100644 --- a/test/Billing.Test/Resources/Events/invoice.finalized.json +++ b/test/Billing.Test/Resources/Events/invoice.finalized.json @@ -2,7 +2,7 @@ "id": "evt_1PQaABIGBnsLynRrhoJjGnyz", "object": "event", "account": "acct_19smIXIGBnsLynRr", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1718133319, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/invoice.upcoming.json b/test/Billing.Test/Resources/Events/invoice.upcoming.json index 056f3a2d1..1ecf2c616 100644 --- a/test/Billing.Test/Resources/Events/invoice.upcoming.json +++ b/test/Billing.Test/Resources/Events/invoice.upcoming.json @@ -1,7 +1,7 @@ { "id": "evt_1Nv0w8IGBnsLynRrZoDVI44u", "object": "event", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695833408, "data": { "object": { diff --git a/test/Billing.Test/Resources/Events/payment_method.attached.json b/test/Billing.Test/Resources/Events/payment_method.attached.json index 9b6563064..2d22a929d 100644 --- a/test/Billing.Test/Resources/Events/payment_method.attached.json +++ b/test/Billing.Test/Resources/Events/payment_method.attached.json @@ -1,7 +1,7 @@ { "id": "evt_1NvKzcIGBnsLynRrPJ3hybkd", "object": "event", - "api_version": "2023-10-16", + "api_version": "2024-06-20", "created": 1695910504, "data": { "object": {