Files
server/src/Core/Billing/Payment/Queries/GetPaymentMethodQuery.cs
Alex Morask 7f65a655d4 [PM-21881] Manage payment details outside of checkout (#6032)
* Add feature flag

* Further establish billing command pattern and use in PreviewTaxAmountCommand

* Add billing address models/commands/queries/tests

* Update TypeReadingJsonConverter to account for new union types

* Add payment method models/commands/queries/tests

* Add credit models/commands/queries/tests

* Add command/query registrations

* Add new endpoints to support new command model and payment functionality

* Run dotnet format

* Add InjectUserAttribute for easier AccountBillilngVNextController handling

* Add InjectOrganizationAttribute for easier OrganizationBillingVNextController handling

* Add InjectProviderAttribute for easier ProviderBillingVNextController handling

* Add XML documentation for billing command pipeline

* Fix StripeConstants post-nullability

* More nullability cleanup

* Run dotnet format
2025-07-10 08:32:25 -05:00

97 lines
3.2 KiB
C#

#nullable enable
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 Microsoft.Extensions.Logging;
using Stripe;
namespace Bit.Core.Billing.Payment.Queries;
public interface IGetPaymentMethodQuery
{
Task<MaskedPaymentMethod?> Run(ISubscriber subscriber);
}
public class GetPaymentMethodQuery(
IBraintreeGateway braintreeGateway,
ILogger<GetPaymentMethodQuery> logger,
ISetupIntentCache setupIntentCache,
IStripeAdapter stripeAdapter,
ISubscriberService subscriberService) : IGetPaymentMethodQuery
{
public async Task<MaskedPaymentMethod?> Run(ISubscriber subscriber)
{
var customer = await subscriberService.GetCustomer(subscriber,
new CustomerGetOptions { Expand = ["default_source", "invoice_settings.default_payment_method"] });
if (customer.Metadata.TryGetValue(StripeConstants.MetadataKeys.BraintreeCustomerId, out var braintreeCustomerId))
{
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);
return null;
}
var paymentMethod = customer.InvoiceSettings.DefaultPaymentMethod != null
? customer.InvoiceSettings.DefaultPaymentMethod.Type switch
{
"card" => MaskedPaymentMethod.From(customer.InvoiceSettings.DefaultPaymentMethod.Card),
"us_bank_account" => MaskedPaymentMethod.From(customer.InvoiceSettings.DefaultPaymentMethod.UsBankAccount),
_ => null
}
: null;
if (paymentMethod != null)
{
return paymentMethod;
}
if (customer.DefaultSource != null)
{
paymentMethod = customer.DefaultSource switch
{
Card card => MaskedPaymentMethod.From(card),
BankAccount bankAccount => MaskedPaymentMethod.From(bankAccount),
Source { Card: not null } source => MaskedPaymentMethod.From(source.Card),
_ => null
};
if (paymentMethod != null)
{
return paymentMethod;
}
}
var setupIntentId = await setupIntentCache.Get(subscriber.Id);
if (string.IsNullOrEmpty(setupIntentId))
{
return null;
}
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
{
Expand = ["payment_method"]
});
// ReSharper disable once ConvertIfStatementToReturnStatement
if (!setupIntent.IsUnverifiedBankAccount())
{
return null;
}
return MaskedPaymentMethod.From(setupIntent);
}
}