1
0
mirror of https://github.com/bitwarden/server synced 2026-02-25 17:03:22 +00:00

[PM-31040] Replace ISetupIntentCache with customer-based approach (#6954)

* docs(billing): add design document for replacing SetupIntent cache

* docs(billing): add implementation plan for replacing SetupIntent cache

* feat(db): add gateway lookup stored procedures for Organization, Provider, and User

* feat(db): add gateway lookup indexes to Organization, Provider, and User table definitions

* chore(db): add SQL Server migration for gateway lookup indexes and stored procedures

* feat(repos): add gateway lookup methods to IOrganizationRepository and Dapper implementation

* feat(repos): add gateway lookup methods to IProviderRepository and Dapper implementation

* feat(repos): add gateway lookup methods to IUserRepository and Dapper implementation

* feat(repos): add EF OrganizationRepository gateway lookup methods and index configuration

* feat(repos): add EF ProviderRepository gateway lookup methods and index configuration

* feat(repos): add EF UserRepository gateway lookup methods and index configuration

* chore(db): add EF migrations for gateway lookup indexes

* refactor(billing): update SetupIntentSucceededHandler to use repository instead of cache

* refactor(billing): simplify StripeEventService by expanding customer on SetupIntent

* refactor(billing): query Stripe for SetupIntents by customer ID in GetPaymentMethodQuery

* refactor(billing): query Stripe for SetupIntents by customer ID in HasPaymentMethodQuery

* refactor(billing): update OrganizationBillingService to set customer on SetupIntent

* refactor(billing): update ProviderBillingService to set customer on SetupIntent and query by customer

* refactor(billing): update UpdatePaymentMethodCommand to set customer on SetupIntent

* refactor(billing): remove bank account support from CreatePremiumCloudHostedSubscriptionCommand

* refactor(billing): remove OrganizationBillingService.UpdatePaymentMethod dead code

* refactor(billing): remove ProviderBillingService.UpdatePaymentMethod

* refactor(billing): remove PremiumUserBillingService.UpdatePaymentMethod and UserService.ReplacePaymentMethodAsync

* refactor(billing): remove SubscriberService.UpdatePaymentSource and related dead code

* refactor(billing): update SubscriberService.GetPaymentSourceAsync to query Stripe by customer ID

Add Task 15a to plan - this was a missed requirement for updating
GetPaymentSourceAsync which still used the cache.

* refactor(billing): complete removal of PremiumUserBillingService.Finalize and UserService.SignUpPremiumAsync

* refactor(billing): remove ISetupIntentCache and SetupIntentDistributedCache

* chore: remove temporary planning documents

* chore: run dotnet format

* fix(billing): add MaxLength(50) to Provider gateway ID properties

* chore(db): add EF migrations for Provider gateway column lengths

* chore: run dotnet format

* chore: rename SQL migration for chronological order
This commit is contained in:
Alex Morask
2026-02-18 13:20:25 -06:00
committed by GitHub
parent 2ce98277b4
commit cfd5bedae0
69 changed files with 22548 additions and 1892 deletions

View File

@@ -15,13 +15,10 @@ using Bit.Core.Auth.Enums;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Billing.Licenses;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Models.Business;
using Bit.Core.Billing.Models.Sales;
using Bit.Core.Billing.Premium.Queries;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Billing.Tax.Models;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -68,7 +65,6 @@ public class UserService : UserManager<User>, IUserService
private readonly IProviderUserRepository _providerUserRepository;
private readonly IStripeSyncService _stripeSyncService;
private readonly IFeatureService _featureService;
private readonly IPremiumUserBillingService _premiumUserBillingService;
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IDistributedCache _distributedCache;
@@ -105,7 +101,6 @@ public class UserService : UserManager<User>, IUserService
IProviderUserRepository providerUserRepository,
IStripeSyncService stripeSyncService,
IFeatureService featureService,
IPremiumUserBillingService premiumUserBillingService,
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IDistributedCache distributedCache,
@@ -146,7 +141,6 @@ public class UserService : UserManager<User>, IUserService
_providerUserRepository = providerUserRepository;
_stripeSyncService = stripeSyncService;
_featureService = featureService;
_premiumUserBillingService = premiumUserBillingService;
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_distributedCache = distributedCache;
@@ -742,78 +736,6 @@ public class UserService : UserManager<User>, IUserService
return true;
}
public async Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,
TaxInfo taxInfo)
{
if (user.Premium)
{
throw new BadRequestException("Already a premium user.");
}
if (additionalStorageGb < 0)
{
throw new BadRequestException("You can't subtract storage!");
}
string paymentIntentClientSecret = null;
IStripePaymentService paymentService = null;
if (_globalSettings.SelfHosted)
{
if (license == null || !_licenseService.VerifyLicense(license))
{
throw new BadRequestException("Invalid license.");
}
var claimsPrincipal = _licenseService.GetClaimsPrincipalFromLicense(license);
if (!license.CanUse(user, claimsPrincipal, out var exceptionMessage))
{
throw new BadRequestException(exceptionMessage);
}
var dir = $"{_globalSettings.LicenseDirectory}/user";
Directory.CreateDirectory(dir);
using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
}
else
{
var sale = PremiumUserSale.From(user, paymentMethodType, paymentToken, taxInfo, additionalStorageGb);
await _premiumUserBillingService.Finalize(sale);
}
user.Premium = true;
user.RevisionDate = DateTime.UtcNow;
if (_globalSettings.SelfHosted)
{
user.MaxStorageGb = Constants.SelfHostedMaxStorageGb;
user.LicenseKey = license.LicenseKey;
user.PremiumExpirationDate = license.Expires;
}
else
{
user.LicenseKey = CoreHelpers.SecureRandomString(20);
}
try
{
await SaveUserAsync(user);
await _pushService.PushSyncVaultAsync(user.Id);
}
catch when (!_globalSettings.SelfHosted)
{
await paymentService.CancelAndRecoverChargesAsync(user);
throw;
}
return new Tuple<bool, string>(string.IsNullOrWhiteSpace(paymentIntentClientSecret),
paymentIntentClientSecret);
}
public async Task UpdateLicenseAsync(User user, UserLicense license)
{
if (!_globalSettings.SelfHosted)
@@ -883,20 +805,6 @@ public class UserService : UserManager<User>, IUserService
return secret;
}
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType, TaxInfo taxInfo)
{
if (paymentToken.StartsWith("btok_"))
{
throw new BadRequestException("Invalid token.");
}
var tokenizedPaymentSource = new TokenizedPaymentSource(paymentMethodType, paymentToken);
var taxInformation = TaxInformation.From(taxInfo);
await _premiumUserBillingService.UpdatePaymentMethod(user, tokenizedPaymentSource, taxInformation);
await SaveUserAsync(user);
}
public async Task CancelPremiumAsync(User user, bool? endOfPeriod = null)
{
var eop = endOfPeriod.GetValueOrDefault(true);