mirror of
https://github.com/bitwarden/server.git
synced 2026-02-04 02:05:30 -06:00
Allow addition of PayPal payment method when bad Braintree customer ID is linked
This commit is contained in:
parent
8cc906ec5f
commit
bcf483d967
@ -4,6 +4,7 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Braintree;
|
||||
@ -23,6 +24,7 @@ public interface IUpdatePaymentMethodCommand
|
||||
|
||||
public class UpdatePaymentMethodCommand(
|
||||
IBraintreeGateway braintreeGateway,
|
||||
IBraintreeService braintreeService,
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<UpdatePaymentMethodCommand> logger,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
@ -123,12 +125,10 @@ public class UpdatePaymentMethodCommand(
|
||||
Customer customer,
|
||||
string token)
|
||||
{
|
||||
Braintree.Customer braintreeCustomer;
|
||||
var braintreeCustomer = await braintreeService.GetCustomer(customer);
|
||||
|
||||
if (customer.Metadata.TryGetValue(StripeConstants.MetadataKeys.BraintreeCustomerId, out var braintreeCustomerId))
|
||||
if (braintreeCustomer != null)
|
||||
{
|
||||
braintreeCustomer = await braintreeGateway.Customer.FindAsync(braintreeCustomerId);
|
||||
|
||||
await ReplaceBraintreePaymentMethodAsync(braintreeCustomer, token);
|
||||
}
|
||||
else
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Braintree;
|
||||
using Braintree.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Payment.Queries;
|
||||
@ -17,8 +15,7 @@ public interface IGetPaymentMethodQuery
|
||||
}
|
||||
|
||||
public class GetPaymentMethodQuery(
|
||||
IBraintreeGateway braintreeGateway,
|
||||
ILogger<GetPaymentMethodQuery> logger,
|
||||
IBraintreeService braintreeService,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IGetPaymentMethodQuery
|
||||
@ -33,32 +30,12 @@ public class GetPaymentMethodQuery(
|
||||
return null;
|
||||
}
|
||||
|
||||
// First check for PayPal
|
||||
if (customer.Metadata.TryGetValue(StripeConstants.MetadataKeys.BraintreeCustomerId, out var braintreeCustomerId))
|
||||
// First check for a PayPal account
|
||||
var braintreeCustomer = await braintreeService.GetCustomer(customer);
|
||||
|
||||
if (braintreeCustomer is { DefaultPaymentMethod: PayPalAccount payPalAccount })
|
||||
{
|
||||
try
|
||||
{
|
||||
var braintreeCustomer = await braintreeGateway.Customer.FindAsync(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;
|
||||
return new MaskedPayPalAccount { Email = payPalAccount.Email };
|
||||
}
|
||||
|
||||
// Then check for a bank account pending verification
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
using Bit.Core.Billing.Subscriptions.Models;
|
||||
using Stripe;
|
||||
using Braintree;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IBraintreeService
|
||||
{
|
||||
Task<Customer?> GetCustomer(
|
||||
Stripe.Customer customer);
|
||||
|
||||
Task PayInvoice(
|
||||
SubscriberId subscriberId,
|
||||
Invoice invoice);
|
||||
Stripe.Invoice invoice);
|
||||
}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Subscriptions.Models;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Settings;
|
||||
using Braintree;
|
||||
using Braintree.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Services.Implementations;
|
||||
|
||||
@ -18,11 +17,34 @@ public class BraintreeService(
|
||||
IMailService mailService,
|
||||
IStripeAdapter stripeAdapter) : IBraintreeService
|
||||
{
|
||||
private readonly ConflictException _problemPayingInvoice = new("There was a problem paying for your invoice. Please contact customer support.");
|
||||
private readonly Exceptions.ConflictException _problemPayingInvoice = new("There was a problem paying for your invoice. Please contact customer support.");
|
||||
|
||||
public async Task<Customer?> GetCustomer(
|
||||
Stripe.Customer customer)
|
||||
{
|
||||
if (!customer.Metadata.TryGetValue(MetadataKeys.BraintreeCustomerId, out var braintreeCustomerId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await braintreeGateway.Customer.FindAsync(braintreeCustomerId);
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Stripe customer ({CustomerId}) is linked to a Braintree Customer ({BraintreeCustomerId}) that does not exist.",
|
||||
customer.Id,
|
||||
braintreeCustomerId);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PayInvoice(
|
||||
SubscriberId subscriberId,
|
||||
Invoice invoice)
|
||||
Stripe.Invoice invoice)
|
||||
{
|
||||
if (invoice.Customer == null)
|
||||
{
|
||||
@ -93,7 +115,7 @@ public class BraintreeService(
|
||||
return;
|
||||
}
|
||||
|
||||
await stripeAdapter.UpdateInvoiceAsync(invoice.Id, new InvoiceUpdateOptions
|
||||
await stripeAdapter.UpdateInvoiceAsync(invoice.Id, new Stripe.InvoiceUpdateOptions
|
||||
{
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
@ -102,6 +124,6 @@ public class BraintreeService(
|
||||
}
|
||||
});
|
||||
|
||||
await stripeAdapter.PayInvoiceAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
|
||||
await stripeAdapter.PayInvoiceAsync(invoice.Id, new Stripe.InvoicePayOptions { PaidOutOfBand = true });
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Payment.Commands;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.Billing.Extensions;
|
||||
using Braintree;
|
||||
@ -22,6 +23,7 @@ using static StripeConstants;
|
||||
public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
private readonly IBraintreeGateway _braintreeGateway = Substitute.For<IBraintreeGateway>();
|
||||
private readonly IBraintreeService _braintreeService = Substitute.For<IBraintreeService>();
|
||||
private readonly IGlobalSettings _globalSettings = Substitute.For<IGlobalSettings>();
|
||||
private readonly ISetupIntentCache _setupIntentCache = Substitute.For<ISetupIntentCache>();
|
||||
private readonly IStripeAdapter _stripeAdapter = Substitute.For<IStripeAdapter>();
|
||||
@ -32,6 +34,7 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
_command = new UpdatePaymentMethodCommand(
|
||||
_braintreeGateway,
|
||||
_braintreeService,
|
||||
_globalSettings,
|
||||
Substitute.For<ILogger<UpdatePaymentMethodCommand>>(),
|
||||
_setupIntentCache,
|
||||
@ -375,7 +378,6 @@ public class UpdatePaymentMethodCommandTests
|
||||
|
||||
_subscriberService.GetCustomer(organization).Returns(customer);
|
||||
|
||||
var customerGateway = Substitute.For<ICustomerGateway>();
|
||||
var braintreeCustomer = Substitute.For<Braintree.Customer>();
|
||||
braintreeCustomer.Id.Returns("braintree_customer_id");
|
||||
var existing = Substitute.For<PayPalAccount>();
|
||||
@ -383,7 +385,10 @@ public class UpdatePaymentMethodCommandTests
|
||||
existing.IsDefault.Returns(true);
|
||||
existing.Token.Returns("EXISTING");
|
||||
braintreeCustomer.PaymentMethods.Returns([existing]);
|
||||
customerGateway.FindAsync("braintree_customer_id").Returns(braintreeCustomer);
|
||||
|
||||
_braintreeService.GetCustomer(customer).Returns(braintreeCustomer);
|
||||
|
||||
var customerGateway = Substitute.For<ICustomerGateway>();
|
||||
_braintreeGateway.Customer.Returns(customerGateway);
|
||||
|
||||
var paymentMethodGateway = Substitute.For<IPaymentMethodGateway>();
|
||||
@ -471,4 +476,75 @@ public class UpdatePaymentMethodCommandTests
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Metadata[MetadataKeys.BraintreeCustomerId] == "braintree_customer_id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_PayPal_MissingBraintreeCustomer_CreatesNewBraintreeCustomer_ReturnsMaskedPayPalAccount()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
},
|
||||
Id = "cus_123",
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
[MetadataKeys.BraintreeCustomerId] = "missing_braintree_customer_id"
|
||||
}
|
||||
};
|
||||
|
||||
_subscriberService.GetCustomer(organization).Returns(customer);
|
||||
|
||||
// BraintreeService.GetCustomer returns null when the Braintree customer doesn't exist
|
||||
_braintreeService.GetCustomer(customer).Returns((Braintree.Customer?)null);
|
||||
|
||||
_globalSettings.BaseServiceUri.Returns(new GlobalSettings.BaseServiceUriSettings(new GlobalSettings())
|
||||
{
|
||||
CloudRegion = "US"
|
||||
});
|
||||
|
||||
var customerGateway = Substitute.For<ICustomerGateway>();
|
||||
var braintreeCustomer = Substitute.For<Braintree.Customer>();
|
||||
braintreeCustomer.Id.Returns("new_braintree_customer_id");
|
||||
var payPalAccount = Substitute.For<PayPalAccount>();
|
||||
payPalAccount.Email.Returns("user@gmail.com");
|
||||
payPalAccount.IsDefault.Returns(true);
|
||||
payPalAccount.Token.Returns("NONCE");
|
||||
braintreeCustomer.PaymentMethods.Returns([payPalAccount]);
|
||||
var createResult = Substitute.For<Result<Braintree.Customer>>();
|
||||
createResult.Target.Returns(braintreeCustomer);
|
||||
customerGateway.CreateAsync(Arg.Is<CustomerRequest>(options =>
|
||||
options.Id.StartsWith(organization.BraintreeCustomerIdPrefix() + organization.Id.ToString("N").ToLower()) &&
|
||||
options.CustomFields[organization.BraintreeIdField()] == organization.Id.ToString() &&
|
||||
options.CustomFields[organization.BraintreeCloudRegionField()] == "US" &&
|
||||
options.Email == organization.BillingEmailAddress() &&
|
||||
options.PaymentMethodNonce == "TOKEN")).Returns(createResult);
|
||||
_braintreeGateway.Customer.Returns(customerGateway);
|
||||
|
||||
var result = await _command.Run(organization,
|
||||
new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.PayPal, Token = "TOKEN" },
|
||||
new BillingAddress { Country = "US", PostalCode = "12345" });
|
||||
|
||||
Assert.True(result.IsT0);
|
||||
var maskedPaymentMethod = result.AsT0;
|
||||
Assert.True(maskedPaymentMethod.IsT2);
|
||||
var maskedPayPalAccount = maskedPaymentMethod.AsT2;
|
||||
Assert.Equal("user@gmail.com", maskedPayPalAccount.Email);
|
||||
|
||||
// Verify a new Braintree customer was created (not FindAsync called)
|
||||
await customerGateway.DidNotReceive().FindAsync(Arg.Any<string>());
|
||||
await customerGateway.Received(1).CreateAsync(Arg.Any<CustomerRequest>());
|
||||
|
||||
// Verify Stripe metadata was updated with the new Braintree customer ID
|
||||
await _stripeAdapter.Received(1).UpdateCustomerAsync(customer.Id,
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Metadata[MetadataKeys.BraintreeCustomerId] == "new_braintree_customer_id"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,9 @@ using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Extensions;
|
||||
using Braintree;
|
||||
using Braintree.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
@ -20,7 +19,7 @@ using static StripeConstants;
|
||||
|
||||
public class GetPaymentMethodQueryTests
|
||||
{
|
||||
private readonly IBraintreeGateway _braintreeGateway = Substitute.For<IBraintreeGateway>();
|
||||
private readonly IBraintreeService _braintreeService = Substitute.For<IBraintreeService>();
|
||||
private readonly ISetupIntentCache _setupIntentCache = Substitute.For<ISetupIntentCache>();
|
||||
private readonly IStripeAdapter _stripeAdapter = Substitute.For<IStripeAdapter>();
|
||||
private readonly ISubscriberService _subscriberService = Substitute.For<ISubscriberService>();
|
||||
@ -29,8 +28,7 @@ public class GetPaymentMethodQueryTests
|
||||
public GetPaymentMethodQueryTests()
|
||||
{
|
||||
_query = new GetPaymentMethodQuery(
|
||||
_braintreeGateway,
|
||||
Substitute.For<ILogger<GetPaymentMethodQuery>>(),
|
||||
_braintreeService,
|
||||
_setupIntentCache,
|
||||
_stripeAdapter,
|
||||
_subscriberService);
|
||||
@ -76,6 +74,34 @@ public class GetPaymentMethodQueryTests
|
||||
Assert.Null(maskedPaymentMethod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NoPaymentMethod_BraintreeCustomerNotFound_ReturnsNull()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
[MetadataKeys.BraintreeCustomerId] = "non_existent_braintree_customer_id"
|
||||
}
|
||||
};
|
||||
|
||||
_subscriberService.GetCustomer(organization,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.HasExpansions("default_source", "invoice_settings.default_payment_method"))).Returns(customer);
|
||||
|
||||
_braintreeService.GetCustomer(customer).ReturnsNull();
|
||||
|
||||
var maskedPaymentMethod = await _query.Run(organization);
|
||||
|
||||
Assert.Null(maskedPaymentMethod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_BankAccount_FromPaymentMethod_ReturnsMaskedBankAccount()
|
||||
{
|
||||
@ -329,14 +355,12 @@ public class GetPaymentMethodQueryTests
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.HasExpansions("default_source", "invoice_settings.default_payment_method"))).Returns(customer);
|
||||
|
||||
var customerGateway = Substitute.For<ICustomerGateway>();
|
||||
var braintreeCustomer = Substitute.For<Braintree.Customer>();
|
||||
var payPalAccount = Substitute.For<PayPalAccount>();
|
||||
payPalAccount.Email.Returns("user@gmail.com");
|
||||
payPalAccount.IsDefault.Returns(true);
|
||||
braintreeCustomer.PaymentMethods.Returns([payPalAccount]);
|
||||
customerGateway.FindAsync("braintree_customer_id").Returns(braintreeCustomer);
|
||||
_braintreeGateway.Customer.Returns(customerGateway);
|
||||
_braintreeService.GetCustomer(customer).Returns(braintreeCustomer);
|
||||
|
||||
var maskedPaymentMethod = await _query.Run(organization);
|
||||
|
||||
@ -345,34 +369,4 @@ 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<string, string>
|
||||
{
|
||||
[MetadataKeys.BraintreeCustomerId] = "non_existent_braintree_customer_id"
|
||||
}
|
||||
};
|
||||
|
||||
_subscriberService.GetCustomer(organization,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.HasExpansions("default_source", "invoice_settings.default_payment_method"))).Returns(customer);
|
||||
|
||||
var customerGateway = Substitute.For<ICustomerGateway>();
|
||||
customerGateway.FindAsync("non_existent_braintree_customer_id").Returns<Braintree.Customer>(_ => throw new NotFoundException());
|
||||
_braintreeGateway.Customer.Returns(customerGateway);
|
||||
|
||||
var maskedPaymentMethod = await _query.Run(organization);
|
||||
|
||||
Assert.Null(maskedPaymentMethod);
|
||||
}
|
||||
}
|
||||
|
||||
118
test/Core.Test/Services/Implementations/BraintreeServiceTests.cs
Normal file
118
test/Core.Test/Services/Implementations/BraintreeServiceTests.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Braintree;
|
||||
using Braintree.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
using BraintreeService = Bit.Core.Services.Implementations.BraintreeService;
|
||||
using Customer = Stripe.Customer;
|
||||
|
||||
namespace Bit.Core.Test.Services.Implementations;
|
||||
|
||||
public class BraintreeServiceTests
|
||||
{
|
||||
private readonly ICustomerGateway _customerGateway;
|
||||
private readonly BraintreeService _sut;
|
||||
|
||||
public BraintreeServiceTests()
|
||||
{
|
||||
var braintreeGateway = Substitute.For<IBraintreeGateway>();
|
||||
_customerGateway = Substitute.For<ICustomerGateway>();
|
||||
braintreeGateway.Customer.Returns(_customerGateway);
|
||||
|
||||
var globalSettings = Substitute.For<IGlobalSettings>();
|
||||
var logger = Substitute.For<ILogger<BraintreeService>>();
|
||||
var mailService = Substitute.For<IMailService>();
|
||||
var stripeAdapter = Substitute.For<IStripeAdapter>();
|
||||
|
||||
_sut = new BraintreeService(
|
||||
braintreeGateway,
|
||||
globalSettings,
|
||||
logger,
|
||||
mailService,
|
||||
stripeAdapter);
|
||||
}
|
||||
|
||||
#region GetCustomer
|
||||
|
||||
[Fact]
|
||||
public async Task GetCustomer_NoBraintreeCustomerIdInMetadata_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var stripeCustomer = new Customer
|
||||
{
|
||||
Id = "cus_123",
|
||||
Metadata = new Dictionary<string, string>()
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _sut.GetCustomer(stripeCustomer);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await _customerGateway.DidNotReceiveWithAnyArgs().FindAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCustomer_BraintreeCustomerFound_ReturnsCustomer()
|
||||
{
|
||||
// Arrange
|
||||
const string braintreeCustomerId = "bt_customer_123";
|
||||
|
||||
var stripeCustomer = new Customer
|
||||
{
|
||||
Id = "cus_123",
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
[StripeConstants.MetadataKeys.BraintreeCustomerId] = braintreeCustomerId
|
||||
}
|
||||
};
|
||||
|
||||
var braintreeCustomer = Substitute.For<Braintree.Customer>();
|
||||
|
||||
_customerGateway
|
||||
.FindAsync(braintreeCustomerId)
|
||||
.Returns(braintreeCustomer);
|
||||
|
||||
// Act
|
||||
var result = await _sut.GetCustomer(stripeCustomer);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Same(braintreeCustomer, result);
|
||||
await _customerGateway.Received(1).FindAsync(braintreeCustomerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCustomer_BraintreeCustomerNotFound_LogsWarningAndReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
const string braintreeCustomerId = "bt_non_existent_customer";
|
||||
|
||||
var stripeCustomer = new Customer
|
||||
{
|
||||
Id = "cus_123",
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
[StripeConstants.MetadataKeys.BraintreeCustomerId] = braintreeCustomerId
|
||||
}
|
||||
};
|
||||
|
||||
_customerGateway
|
||||
.FindAsync(braintreeCustomerId)
|
||||
.Returns<Braintree.Customer>(_ => throw new NotFoundException());
|
||||
|
||||
// Act
|
||||
var result = await _sut.GetCustomer(stripeCustomer);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
await _customerGateway.Received(1).FindAsync(braintreeCustomerId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user