1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[PM-21938] Fix: Invoice Payment Issues After Payment Method Updates (#6306)

* Resolve the unpaid issue after valid payment method is added

* Removed the draft status

* Remove draft from the logger msg
This commit is contained in:
cyprain-okeke
2025-09-11 16:04:05 +01:00
committed by GitHub
parent aab50ef5c4
commit c2cf290054
2 changed files with 11 additions and 54 deletions

View File

@@ -580,11 +580,6 @@ public class SubscriberService(
PaymentMethod = token PaymentMethod = token
}); });
var getExistingSetupIntentsForCustomer = stripeAdapter.SetupIntentList(new SetupIntentListOptions
{
Customer = subscriber.GatewayCustomerId
});
// Find the setup intent for the incoming payment method token. // Find the setup intent for the incoming payment method token.
var setupIntentsForUpdatedPaymentMethod = await getSetupIntentsForUpdatedPaymentMethod; var setupIntentsForUpdatedPaymentMethod = await getSetupIntentsForUpdatedPaymentMethod;
@@ -597,24 +592,15 @@ public class SubscriberService(
var matchingSetupIntent = setupIntentsForUpdatedPaymentMethod.First(); var matchingSetupIntent = setupIntentsForUpdatedPaymentMethod.First();
// Find the customer's existing setup intents that should be canceled.
var existingSetupIntentsForCustomer = (await getExistingSetupIntentsForCustomer)
.Where(si =>
si.Status is "requires_payment_method" or "requires_confirmation" or "requires_action");
// Store the incoming payment method's setup intent ID in the cache for the subscriber so it can be verified later. // Store the incoming payment method's setup intent ID in the cache for the subscriber so it can be verified later.
await setupIntentCache.Set(subscriber.Id, matchingSetupIntent.Id); await setupIntentCache.Set(subscriber.Id, matchingSetupIntent.Id);
// Cancel the customer's other open setup intents.
var postProcessing = existingSetupIntentsForCustomer.Select(si =>
stripeAdapter.SetupIntentCancel(si.Id,
new SetupIntentCancelOptions { CancellationReason = "abandoned" })).ToList();
// Remove the customer's other attached Stripe payment methods. // Remove the customer's other attached Stripe payment methods.
postProcessing.Add(RemoveStripePaymentMethodsAsync(customer)); var postProcessing = new List<Task>
{
// Remove the customer's Braintree customer ID. RemoveStripePaymentMethodsAsync(customer),
postProcessing.Add(RemoveBraintreeCustomerIdAsync(customer)); RemoveBraintreeCustomerIdAsync(customer)
};
await Task.WhenAll(postProcessing); await Task.WhenAll(postProcessing);
@@ -622,11 +608,6 @@ public class SubscriberService(
} }
case PaymentMethodType.Card: case PaymentMethodType.Card:
{ {
var getExistingSetupIntentsForCustomer = stripeAdapter.SetupIntentList(new SetupIntentListOptions
{
Customer = subscriber.GatewayCustomerId
});
// Remove the customer's other attached Stripe payment methods. // Remove the customer's other attached Stripe payment methods.
await RemoveStripePaymentMethodsAsync(customer); await RemoveStripePaymentMethodsAsync(customer);
@@ -634,16 +615,6 @@ public class SubscriberService(
await stripeAdapter.PaymentMethodAttachAsync(token, await stripeAdapter.PaymentMethodAttachAsync(token,
new PaymentMethodAttachOptions { Customer = subscriber.GatewayCustomerId }); new PaymentMethodAttachOptions { Customer = subscriber.GatewayCustomerId });
// Find the customer's existing setup intents that should be canceled.
var existingSetupIntentsForCustomer = (await getExistingSetupIntentsForCustomer)
.Where(si =>
si.Status is "requires_payment_method" or "requires_confirmation" or "requires_action");
// Cancel the customer's other open setup intents.
var postProcessing = existingSetupIntentsForCustomer.Select(si =>
stripeAdapter.SetupIntentCancel(si.Id,
new SetupIntentCancelOptions { CancellationReason = "abandoned" })).ToList();
var metadata = customer.Metadata; var metadata = customer.Metadata;
if (metadata.TryGetValue(BraintreeCustomerIdKey, out var value)) if (metadata.TryGetValue(BraintreeCustomerIdKey, out var value))
@@ -653,16 +624,14 @@ public class SubscriberService(
} }
// Set the customer's default payment method in Stripe and remove their Braintree customer ID. // Set the customer's default payment method in Stripe and remove their Braintree customer ID.
postProcessing.Add(stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, new CustomerUpdateOptions await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, new CustomerUpdateOptions
{ {
InvoiceSettings = new CustomerInvoiceSettingsOptions InvoiceSettings = new CustomerInvoiceSettingsOptions
{ {
DefaultPaymentMethod = token DefaultPaymentMethod = token
}, },
Metadata = metadata Metadata = metadata
})); });
await Task.WhenAll(postProcessing);
break; break;
} }

View File

@@ -1320,12 +1320,6 @@ public class SubscriberServiceTests
stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options => options.PaymentMethod == "TOKEN")) stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options => options.PaymentMethod == "TOKEN"))
.Returns([matchingSetupIntent]); .Returns([matchingSetupIntent]);
stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options => options.Customer == provider.GatewayCustomerId))
.Returns([
new SetupIntent { Id = "setup_intent_2", Status = "requires_payment_method" },
new SetupIntent { Id = "setup_intent_3", Status = "succeeded" }
]);
stripeAdapter.CustomerListPaymentMethods(provider.GatewayCustomerId).Returns([ stripeAdapter.CustomerListPaymentMethods(provider.GatewayCustomerId).Returns([
new PaymentMethod { Id = "payment_method_1" } new PaymentMethod { Id = "payment_method_1" }
]); ]);
@@ -1335,8 +1329,8 @@ public class SubscriberServiceTests
await sutProvider.GetDependency<ISetupIntentCache>().Received(1).Set(provider.Id, "setup_intent_1"); await sutProvider.GetDependency<ISetupIntentCache>().Received(1).Set(provider.Id, "setup_intent_1");
await stripeAdapter.Received(1).SetupIntentCancel("setup_intent_2", await stripeAdapter.DidNotReceive().SetupIntentCancel(Arg.Any<string>(),
Arg.Is<SetupIntentCancelOptions>(options => options.CancellationReason == "abandoned")); Arg.Any<SetupIntentCancelOptions>());
await stripeAdapter.Received(1).PaymentMethodDetachAsync("payment_method_1"); await stripeAdapter.Received(1).PaymentMethodDetachAsync("payment_method_1");
@@ -1364,12 +1358,6 @@ public class SubscriberServiceTests
} }
}); });
stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options => options.Customer == provider.GatewayCustomerId))
.Returns([
new SetupIntent { Id = "setup_intent_2", Status = "requires_payment_method" },
new SetupIntent { Id = "setup_intent_3", Status = "succeeded" }
]);
stripeAdapter.CustomerListPaymentMethods(provider.GatewayCustomerId).Returns([ stripeAdapter.CustomerListPaymentMethods(provider.GatewayCustomerId).Returns([
new PaymentMethod { Id = "payment_method_1" } new PaymentMethod { Id = "payment_method_1" }
]); ]);
@@ -1377,8 +1365,8 @@ public class SubscriberServiceTests
await sutProvider.Sut.UpdatePaymentSource(provider, await sutProvider.Sut.UpdatePaymentSource(provider,
new TokenizedPaymentSource(PaymentMethodType.Card, "TOKEN")); new TokenizedPaymentSource(PaymentMethodType.Card, "TOKEN"));
await stripeAdapter.Received(1).SetupIntentCancel("setup_intent_2", await stripeAdapter.DidNotReceive().SetupIntentCancel(Arg.Any<string>(),
Arg.Is<SetupIntentCancelOptions>(options => options.CancellationReason == "abandoned")); Arg.Any<SetupIntentCancelOptions>());
await stripeAdapter.Received(1).PaymentMethodDetachAsync("payment_method_1"); await stripeAdapter.Received(1).PaymentMethodDetachAsync("payment_method_1");