From f7aa56b324eea15fb2c16eb4f65f58262285c68f Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:07:47 -0400 Subject: [PATCH] Handle case where Stripe IDs do not relate to Stripe entities (#4021) --- .../Implementations/SubscriberQueries.cs | 132 ++++++++++++------ .../Billing/Queries/SubscriberQueriesTests.cs | 71 +++++++++- test/Core.Test/Billing/Utilities.cs | 9 +- 3 files changed, 163 insertions(+), 49 deletions(-) diff --git a/src/Core/Billing/Queries/Implementations/SubscriberQueries.cs b/src/Core/Billing/Queries/Implementations/SubscriberQueries.cs index f9420cd48c..b9fe492a1d 100644 --- a/src/Core/Billing/Queries/Implementations/SubscriberQueries.cs +++ b/src/Core/Billing/Queries/Implementations/SubscriberQueries.cs @@ -24,16 +24,27 @@ public class SubscriberQueries( return null; } - var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions); - - if (customer != null) + try { - return customer; + var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions); + + if (customer != null) + { + return customer; + } + + logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})", + subscriber.GatewayCustomerId, subscriber.Id); + + return null; } + catch (StripeException exception) + { + logger.LogError("An error occurred while trying to retrieve Stripe customer ({CustomerID}) for subscriber ({SubscriberID}): {Error}", + subscriber.GatewayCustomerId, subscriber.Id, exception.Message); - logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})", subscriber.GatewayCustomerId, subscriber.Id); - - return null; + return null; + } } public async Task GetSubscription( @@ -49,41 +60,28 @@ public class SubscriberQueries( return null; } - var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions); - - if (subscription != null) + try { - return subscription; + var subscription = + await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions); + + if (subscription != null) + { + return subscription; + } + + logger.LogError("Could not find Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID})", + subscriber.GatewaySubscriptionId, subscriber.Id); + + return null; } - - logger.LogError("Could not find Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID})", subscriber.GatewaySubscriptionId, subscriber.Id); - - return null; - } - - public async Task GetSubscriptionOrThrow( - ISubscriber subscriber, - SubscriptionGetOptions subscriptionGetOptions = null) - { - ArgumentNullException.ThrowIfNull(subscriber); - - if (string.IsNullOrEmpty(subscriber.GatewaySubscriptionId)) + catch (StripeException exception) { - logger.LogError("Cannot retrieve subscription for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewaySubscriptionId)); + logger.LogError("An error occurred while trying to retrieve Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID}): {Error}", + subscriber.GatewaySubscriptionId, subscriber.Id, exception.Message); - throw ContactSupport(); + return null; } - - var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions); - - if (subscription != null) - { - return subscription; - } - - logger.LogError("Could not find Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID})", subscriber.GatewaySubscriptionId, subscriber.Id); - - throw ContactSupport(); } public async Task GetCustomerOrThrow( @@ -99,15 +97,63 @@ public class SubscriberQueries( throw ContactSupport(); } - var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions); - - if (customer != null) + try { - return customer; + var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions); + + if (customer != null) + { + return customer; + } + + logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})", + subscriber.GatewayCustomerId, subscriber.Id); + + throw ContactSupport(); + } + catch (StripeException exception) + { + logger.LogError("An error occurred while trying to retrieve Stripe customer ({CustomerID}) for subscriber ({SubscriberID}): {Error}", + subscriber.GatewayCustomerId, subscriber.Id, exception.Message); + + throw ContactSupport("An error occurred while trying to retrieve a Stripe Customer", exception); + } + } + + public async Task GetSubscriptionOrThrow( + ISubscriber subscriber, + SubscriptionGetOptions subscriptionGetOptions = null) + { + ArgumentNullException.ThrowIfNull(subscriber); + + if (string.IsNullOrEmpty(subscriber.GatewaySubscriptionId)) + { + logger.LogError("Cannot retrieve subscription for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewaySubscriptionId)); + + throw ContactSupport(); } - logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})", subscriber.GatewayCustomerId, subscriber.Id); + try + { + var subscription = + await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions); - throw ContactSupport(); + if (subscription != null) + { + return subscription; + } + + logger.LogError("Could not find Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID})", + subscriber.GatewaySubscriptionId, subscriber.Id); + + throw ContactSupport(); + } + catch (StripeException exception) + { + logger.LogError("An error occurred while trying to retrieve Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID}): {Error}", + subscriber.GatewaySubscriptionId, subscriber.Id, exception.Message); + + throw ContactSupport("An error occurred while trying to retrieve a Stripe Subscription", exception); + } } } diff --git a/test/Core.Test/Billing/Queries/SubscriberQueriesTests.cs b/test/Core.Test/Billing/Queries/SubscriberQueriesTests.cs index 8fcba59aac..c1539e868e 100644 --- a/test/Core.Test/Billing/Queries/SubscriberQueriesTests.cs +++ b/test/Core.Test/Billing/Queries/SubscriberQueriesTests.cs @@ -4,6 +4,7 @@ using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; +using NSubstitute.ExceptionExtensions; using NSubstitute.ReturnsExtensions; using Stripe; using Xunit; @@ -48,6 +49,20 @@ public class SubscriberQueriesTests Assert.Null(customer); } + [Theory, BitAutoData] + public async Task GetCustomer_StripeException_ReturnsNull( + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .CustomerGetAsync(organization.GatewayCustomerId) + .ThrowsAsync(); + + var customer = await sutProvider.Sut.GetCustomer(organization); + + Assert.Null(customer); + } + [Theory, BitAutoData] public async Task GetCustomer_Succeeds( Organization organization, @@ -98,6 +113,20 @@ public class SubscriberQueriesTests Assert.Null(subscription); } + [Theory, BitAutoData] + public async Task GetSubscription_StripeException_ReturnsNull( + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .SubscriptionGetAsync(organization.GatewaySubscriptionId) + .ThrowsAsync(); + + var subscription = await sutProvider.Sut.GetSubscription(organization); + + Assert.Null(subscription); + } + [Theory, BitAutoData] public async Task GetSubscription_Succeeds( Organization organization, @@ -123,7 +152,7 @@ public class SubscriberQueriesTests async () => await sutProvider.Sut.GetCustomerOrThrow(null)); [Theory, BitAutoData] - public async Task GetCustomerOrThrow_NoGatewaySubscriptionId_ThrowsGatewayException( + public async Task GetCustomerOrThrow_NoGatewayCustomerId_ContactSupport( Organization organization, SutProvider sutProvider) { @@ -133,7 +162,7 @@ public class SubscriberQueriesTests } [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_NoCustomer_ThrowsGatewayException( + public async Task GetCustomerOrThrow_NoCustomer_ContactSupport( Organization organization, SutProvider sutProvider) { @@ -144,6 +173,23 @@ public class SubscriberQueriesTests await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetCustomerOrThrow(organization)); } + [Theory, BitAutoData] + public async Task GetCustomerOrThrow_StripeException_ContactSupport( + Organization organization, + SutProvider sutProvider) + { + var stripeException = new StripeException(); + + sutProvider.GetDependency() + .CustomerGetAsync(organization.GatewayCustomerId) + .ThrowsAsync(stripeException); + + await ThrowsContactSupportAsync( + async () => await sutProvider.Sut.GetCustomerOrThrow(organization), + "An error occurred while trying to retrieve a Stripe Customer", + stripeException); + } + [Theory, BitAutoData] public async Task GetCustomerOrThrow_Succeeds( Organization organization, @@ -169,7 +215,7 @@ public class SubscriberQueriesTests async () => await sutProvider.Sut.GetSubscriptionOrThrow(null)); [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_NoGatewaySubscriptionId_ThrowsGatewayException( + public async Task GetSubscriptionOrThrow_NoGatewaySubscriptionId_ContactSupport( Organization organization, SutProvider sutProvider) { @@ -179,7 +225,7 @@ public class SubscriberQueriesTests } [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_NoSubscription_ThrowsGatewayException( + public async Task GetSubscriptionOrThrow_NoSubscription_ContactSupport( Organization organization, SutProvider sutProvider) { @@ -190,6 +236,23 @@ public class SubscriberQueriesTests await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization)); } + [Theory, BitAutoData] + public async Task GetSubscriptionOrThrow_StripeException_ContactSupport( + Organization organization, + SutProvider sutProvider) + { + var stripeException = new StripeException(); + + sutProvider.GetDependency() + .SubscriptionGetAsync(organization.GatewaySubscriptionId) + .ThrowsAsync(stripeException); + + await ThrowsContactSupportAsync( + async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization), + "An error occurred while trying to retrieve a Stripe Subscription", + stripeException); + } + [Theory, BitAutoData] public async Task GetSubscriptionOrThrow_Succeeds( Organization organization, diff --git a/test/Core.Test/Billing/Utilities.cs b/test/Core.Test/Billing/Utilities.cs index ea9e6c694c..a66feebef6 100644 --- a/test/Core.Test/Billing/Utilities.cs +++ b/test/Core.Test/Billing/Utilities.cs @@ -7,12 +7,17 @@ namespace Bit.Core.Test.Billing; public static class Utilities { - public static async Task ThrowsContactSupportAsync(Func function) + public static async Task ThrowsContactSupportAsync( + Func function, + string internalMessage = null, + Exception innerException = null) { - var contactSupport = ContactSupport(); + var contactSupport = ContactSupport(internalMessage, innerException); var exception = await Assert.ThrowsAsync(function); + Assert.Equal(contactSupport.ClientFriendlyMessage, exception.ClientFriendlyMessage); Assert.Equal(contactSupport.Message, exception.Message); + Assert.Equal(contactSupport.InnerException, exception.InnerException); } }