diff --git a/src/Core/Billing/Payment/Queries/GetPaymentMethodQuery.cs b/src/Core/Billing/Payment/Queries/GetPaymentMethodQuery.cs index e03a785278..45ab8aff74 100644 --- a/src/Core/Billing/Payment/Queries/GetPaymentMethodQuery.cs +++ b/src/Core/Billing/Payment/Queries/GetPaymentMethodQuery.cs @@ -5,6 +5,7 @@ using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Services; using Bit.Core.Entities; using Braintree; +using Braintree.Exceptions; using Microsoft.Extensions.Logging; using Stripe; @@ -35,14 +36,27 @@ public class GetPaymentMethodQuery( // First check for PayPal if (customer.Metadata.TryGetValue(StripeConstants.MetadataKeys.BraintreeCustomerId, out var braintreeCustomerId)) { - var braintreeCustomer = await braintreeGateway.Customer.FindAsync(braintreeCustomerId); - - if (braintreeCustomer.DefaultPaymentMethod is PayPalAccount payPalAccount) + try { - return new MaskedPayPalAccount { Email = payPalAccount.Email }; - } + var braintreeCustomer = await braintreeGateway.Customer.FindAsync(braintreeCustomerId); - logger.LogWarning("Subscriber ({SubscriberID}) has a linked Braintree customer ({BraintreeCustomerId}) with no PayPal account.", subscriber.Id, braintreeCustomerId); + if (braintreeCustomer.DefaultPaymentMethod is PayPalAccount payPalAccount) + { + return new MaskedPayPalAccount { Email = payPalAccount.Email }; + } + + logger.LogWarning( + "Subscriber ({SubscriberID}) has a linked Braintree customer ({BraintreeCustomerId}) with no PayPal account.", + subscriber.Id, + braintreeCustomerId); + } + catch (NotFoundException) + { + logger.LogWarning( + "Subscriber ({SubscriberID}) is linked to a Braintree Customer ({BraintreeCustomerId}) that does not exist.", + subscriber.Id, + braintreeCustomerId); + } return null; } diff --git a/test/Core.Test/Billing/Payment/Queries/GetPaymentMethodQueryTests.cs b/test/Core.Test/Billing/Payment/Queries/GetPaymentMethodQueryTests.cs index 4e4c5199e2..17f29e38dd 100644 --- a/test/Core.Test/Billing/Payment/Queries/GetPaymentMethodQueryTests.cs +++ b/test/Core.Test/Billing/Payment/Queries/GetPaymentMethodQueryTests.cs @@ -5,6 +5,7 @@ using Bit.Core.Billing.Payment.Queries; using Bit.Core.Billing.Services; using Bit.Core.Test.Billing.Extensions; using Braintree; +using Braintree.Exceptions; using Microsoft.Extensions.Logging; using NSubstitute; using NSubstitute.ReturnsExtensions; @@ -344,4 +345,34 @@ public class GetPaymentMethodQueryTests var maskedPayPalAccount = maskedPaymentMethod.AsT2; Assert.Equal("user@gmail.com", maskedPayPalAccount.Email); } + + [Fact] + public async Task Run_BraintreeCustomerNotFound_ReturnsNull() + { + var organization = new Organization + { + Id = Guid.NewGuid() + }; + + var customer = new Customer + { + InvoiceSettings = new CustomerInvoiceSettings(), + Metadata = new Dictionary + { + [MetadataKeys.BraintreeCustomerId] = "non_existent_braintree_customer_id" + } + }; + + _subscriberService.GetCustomer(organization, + Arg.Is(options => + options.HasExpansions("default_source", "invoice_settings.default_payment_method"))).Returns(customer); + + var customerGateway = Substitute.For(); + customerGateway.FindAsync("non_existent_braintree_customer_id").Returns(_ => throw new NotFoundException()); + _braintreeGateway.Customer.Returns(customerGateway); + + var maskedPaymentMethod = await _query.Run(organization); + + Assert.Null(maskedPaymentMethod); + } }