mirror of
https://github.com/bitwarden/server
synced 2025-12-22 19:23:45 +00:00
[PM-26692] Count unverified setup intent as payment method during organization subscription creation (#6433)
* Updated check that determines whether org has payment method to include bank account when determining how to set trial_settings * Run dotnet format
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Services;
|
||||
@@ -30,8 +30,8 @@ public interface IGetOrganizationWarningsQuery
|
||||
|
||||
public class GetOrganizationWarningsQuery(
|
||||
ICurrentContext currentContext,
|
||||
IHasPaymentMethodQuery hasPaymentMethodQuery,
|
||||
IProviderRepository providerRepository,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IGetOrganizationWarningsQuery
|
||||
{
|
||||
@@ -81,15 +81,7 @@ public class GetOrganizationWarningsQuery(
|
||||
return null;
|
||||
}
|
||||
|
||||
var customer = subscription.Customer;
|
||||
|
||||
var hasUnverifiedBankAccount = await HasUnverifiedBankAccountAsync(organization);
|
||||
|
||||
var hasPaymentMethod =
|
||||
!string.IsNullOrEmpty(customer.InvoiceSettings.DefaultPaymentMethodId) ||
|
||||
!string.IsNullOrEmpty(customer.DefaultSourceId) ||
|
||||
hasUnverifiedBankAccount ||
|
||||
customer.Metadata.ContainsKey(MetadataKeys.BraintreeCustomerId);
|
||||
var hasPaymentMethod = await hasPaymentMethodQuery.Run(organization);
|
||||
|
||||
if (hasPaymentMethod)
|
||||
{
|
||||
@@ -287,22 +279,4 @@ public class GetOrganizationWarningsQuery(
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<bool> HasUnverifiedBankAccountAsync(
|
||||
Organization organization)
|
||||
{
|
||||
var setupIntentId = await setupIntentCache.GetSetupIntentIdForSubscriber(organization.Id);
|
||||
|
||||
if (string.IsNullOrEmpty(setupIntentId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
|
||||
{
|
||||
Expand = ["payment_method"]
|
||||
});
|
||||
|
||||
return setupIntent.IsUnverifiedBankAccount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
@@ -27,6 +28,7 @@ namespace Bit.Core.Billing.Organizations.Services;
|
||||
public class OrganizationBillingService(
|
||||
IBraintreeGateway braintreeGateway,
|
||||
IGlobalSettings globalSettings,
|
||||
IHasPaymentMethodQuery hasPaymentMethodQuery,
|
||||
ILogger<OrganizationBillingService> logger,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPricingClient pricingClient,
|
||||
@@ -43,7 +45,7 @@ public class OrganizationBillingService(
|
||||
? await CreateCustomerAsync(organization, customerSetup, subscriptionSetup.PlanType)
|
||||
: await GetCustomerWhileEnsuringCorrectTaxExemptionAsync(organization, subscriptionSetup);
|
||||
|
||||
var subscription = await CreateSubscriptionAsync(organization.Id, customer, subscriptionSetup);
|
||||
var subscription = await CreateSubscriptionAsync(organization, customer, subscriptionSetup);
|
||||
|
||||
if (subscription.Status is StripeConstants.SubscriptionStatus.Trialing or StripeConstants.SubscriptionStatus.Active)
|
||||
{
|
||||
@@ -120,8 +122,7 @@ public class OrganizationBillingService(
|
||||
orgOccupiedSeats.Total);
|
||||
}
|
||||
|
||||
public async Task
|
||||
UpdatePaymentMethod(
|
||||
public async Task UpdatePaymentMethod(
|
||||
Organization organization,
|
||||
TokenizedPaymentSource tokenizedPaymentSource,
|
||||
TaxInformation taxInformation)
|
||||
@@ -397,7 +398,7 @@ public class OrganizationBillingService(
|
||||
}
|
||||
|
||||
private async Task<Subscription> CreateSubscriptionAsync(
|
||||
Guid organizationId,
|
||||
Organization organization,
|
||||
Customer customer,
|
||||
SubscriptionSetup subscriptionSetup)
|
||||
{
|
||||
@@ -465,7 +466,7 @@ public class OrganizationBillingService(
|
||||
Items = subscriptionItemOptionsList,
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
["organizationId"] = organizationId.ToString(),
|
||||
["organizationId"] = organization.Id.ToString(),
|
||||
["trialInitiationPath"] = !string.IsNullOrEmpty(subscriptionSetup.InitiationPath) &&
|
||||
subscriptionSetup.InitiationPath.Contains("trial from marketing website")
|
||||
? "marketing-initiated"
|
||||
@@ -475,9 +476,10 @@ public class OrganizationBillingService(
|
||||
TrialPeriodDays = subscriptionSetup.SkipTrial ? 0 : plan.TrialPeriodDays
|
||||
};
|
||||
|
||||
var hasPaymentMethod = await hasPaymentMethodQuery.Run(organization);
|
||||
|
||||
// Only set trial_settings.end_behavior.missing_payment_method to "cancel" if there is no payment method
|
||||
if (string.IsNullOrEmpty(customer.InvoiceSettings?.DefaultPaymentMethodId) &&
|
||||
!customer.Metadata.ContainsKey(BraintreeCustomerIdKey))
|
||||
if (!hasPaymentMethod)
|
||||
{
|
||||
subscriptionCreateOptions.TrialSettings = new SubscriptionTrialSettingsOptions
|
||||
{
|
||||
|
||||
58
src/Core/Billing/Payment/Queries/HasPaymentMethodQuery.cs
Normal file
58
src/Core/Billing/Payment/Queries/HasPaymentMethodQuery.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Payment.Queries;
|
||||
|
||||
using static StripeConstants;
|
||||
|
||||
public interface IHasPaymentMethodQuery
|
||||
{
|
||||
Task<bool> Run(ISubscriber subscriber);
|
||||
}
|
||||
|
||||
public class HasPaymentMethodQuery(
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IHasPaymentMethodQuery
|
||||
{
|
||||
public async Task<bool> Run(ISubscriber subscriber)
|
||||
{
|
||||
var hasUnverifiedBankAccount = await HasUnverifiedBankAccountAsync(subscriber);
|
||||
|
||||
var customer = await subscriberService.GetCustomer(subscriber);
|
||||
|
||||
if (customer == null)
|
||||
{
|
||||
return hasUnverifiedBankAccount;
|
||||
}
|
||||
|
||||
return
|
||||
!string.IsNullOrEmpty(customer.InvoiceSettings.DefaultPaymentMethodId) ||
|
||||
!string.IsNullOrEmpty(customer.DefaultSourceId) ||
|
||||
hasUnverifiedBankAccount ||
|
||||
customer.Metadata.ContainsKey(MetadataKeys.BraintreeCustomerId);
|
||||
}
|
||||
|
||||
private async Task<bool> HasUnverifiedBankAccountAsync(
|
||||
ISubscriber subscriber)
|
||||
{
|
||||
var setupIntentId = await setupIntentCache.GetSetupIntentIdForSubscriber(subscriber.Id);
|
||||
|
||||
if (string.IsNullOrEmpty(setupIntentId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
|
||||
{
|
||||
Expand = ["payment_method"]
|
||||
});
|
||||
|
||||
return setupIntent.IsUnverifiedBankAccount();
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,6 @@ public static class Registrations
|
||||
services.AddTransient<IGetBillingAddressQuery, GetBillingAddressQuery>();
|
||||
services.AddTransient<IGetCreditQuery, GetCreditQuery>();
|
||||
services.AddTransient<IGetPaymentMethodQuery, GetPaymentMethodQuery>();
|
||||
services.AddTransient<IHasPaymentMethodQuery, HasPaymentMethodQuery>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user