Skip to content

Commit

Permalink
Merge branch 'main' into billing/PM-16684/integrate-pricing-service
Browse files Browse the repository at this point in the history
  • Loading branch information
amorask-bitwarden committed Jan 27, 2025
2 parents 8d60635 + 9e718d7 commit 88c0b8e
Show file tree
Hide file tree
Showing 48 changed files with 1,153 additions and 437 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

<Version>2025.1.3</Version>
<Version>2025.1.4</Version>

<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
10 changes: 6 additions & 4 deletions src/Admin/AdminConsole/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Bit.Admin.Utilities;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.Providers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
Expand Down Expand Up @@ -56,7 +57,7 @@ public class OrganizationsController : Controller
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand;
private readonly IProviderBillingService _providerBillingService;
private readonly IFeatureService _featureService;
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand;
private readonly IPricingClient _pricingClient;

public OrganizationsController(
Expand Down Expand Up @@ -84,7 +85,8 @@ public OrganizationsController(
IProviderOrganizationRepository providerOrganizationRepository,
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand,
IProviderBillingService providerBillingService,
IFeatureService featureService, IPricingClient pricingClient)
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand,
IPricingClient pricingClient)
{
_organizationService = organizationService;
_organizationRepository = organizationRepository;
Expand All @@ -110,7 +112,7 @@ public OrganizationsController(
_providerOrganizationRepository = providerOrganizationRepository;
_removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand;
_providerBillingService = providerBillingService;
_featureService = featureService;
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand;
_pricingClient = pricingClient;
}

Expand Down Expand Up @@ -326,7 +328,7 @@ public async Task<IActionResult> DeleteInitiation(Guid id, OrganizationInitiateD
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization != null)
{
await _organizationService.InitiateDeleteAsync(organization, model.AdminEmail);
await _organizationInitiateDeleteCommand.InitiateDeleteAsync(organization, model.AdminEmail);
TempData["Success"] = "The request to initiate deletion of the organization has been sent.";
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/Api/AdminConsole/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Enums;
Expand Down Expand Up @@ -59,6 +60,7 @@ public class OrganizationsController : Controller
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
private readonly IPricingClient _pricingClient;

public OrganizationsController(
Expand All @@ -81,6 +83,7 @@ public OrganizationsController(
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
IOrganizationDeleteCommand organizationDeleteCommand,
IPricingClient pricingClient)
{
_organizationRepository = organizationRepository;
Expand All @@ -102,6 +105,7 @@ public OrganizationsController(
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
_removeOrganizationUserCommand = removeOrganizationUserCommand;
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
_organizationDeleteCommand = organizationDeleteCommand;
_pricingClient = pricingClient;
}

Expand Down Expand Up @@ -311,7 +315,7 @@ await _providerBillingService.ScaleSeats(
}
}

await _organizationService.DeleteAsync(organization);
await _organizationDeleteCommand.DeleteAsync(organization);
}

[HttpPost("{id}/delete-recover-token")]
Expand Down Expand Up @@ -341,7 +345,7 @@ await _providerBillingService.ScaleSeats(
}
}

await _organizationService.DeleteAsync(organization);
await _organizationDeleteCommand.DeleteAsync(organization);
}

[HttpPost("{id}/api-key")]
Expand Down
31 changes: 0 additions & 31 deletions src/Api/Billing/Controllers/OrganizationBillingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Billing.Models.Requests;
using Bit.Api.Billing.Models.Responses;
using Bit.Core;
using Bit.Core.Billing.Models.Sales;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
Expand Down Expand Up @@ -139,11 +138,6 @@ public async Task<IResult> GetBillingAsync(Guid organizationId)
[HttpGet("payment-method")]
public async Task<IResult> GetPaymentMethodAsync([FromRoute] Guid organizationId)
{
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand All @@ -168,11 +162,6 @@ public async Task<IResult> UpdatePaymentMethodAsync(
[FromRoute] Guid organizationId,
[FromBody] UpdatePaymentMethodRequestBody requestBody)
{
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand All @@ -199,11 +188,6 @@ public async Task<IResult> VerifyBankAccountAsync(
[FromRoute] Guid organizationId,
[FromBody] VerifyBankAccountRequestBody requestBody)
{
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand All @@ -229,11 +213,6 @@ public async Task<IResult> VerifyBankAccountAsync(
[HttpGet("tax-information")]
public async Task<IResult> GetTaxInformationAsync([FromRoute] Guid organizationId)
{
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand All @@ -258,11 +237,6 @@ public async Task<IResult> UpdateTaxInformationAsync(
[FromRoute] Guid organizationId,
[FromBody] TaxInformationRequestBody requestBody)
{
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand Down Expand Up @@ -292,11 +266,6 @@ public async Task<IResult> RestartSubscriptionAsync([FromRoute] Guid organizatio
throw new UnauthorizedAccessException();
}

if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
{
return Error.NotFound();
}

if (!await currentContext.EditPaymentMethods(organizationId))
{
return Error.Unauthorized();
Expand Down
2 changes: 2 additions & 0 deletions src/Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Tools.ImportFeatures;
using Bit.Core.Tools.ReportFeatures;


Expand Down Expand Up @@ -175,6 +176,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddCoreLocalizationServices();
services.AddBillingOperations();
services.AddReportingServices();
services.AddImportServices();

// Authorization Handlers
services.AddAuthorizationHandlers();
Expand Down
13 changes: 6 additions & 7 deletions src/Api/Tools/Controllers/ImportCiphersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Vault.Services;
using Bit.Core.Tools.ImportFeatures.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -17,31 +17,30 @@ namespace Bit.Api.Tools.Controllers;
[Authorize("Application")]
public class ImportCiphersController : Controller
{
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly ILogger<ImportCiphersController> _logger;
private readonly GlobalSettings _globalSettings;
private readonly ICollectionRepository _collectionRepository;
private readonly IAuthorizationService _authorizationService;
private readonly IImportCiphersCommand _importCiphersCommand;

public ImportCiphersController(
ICipherService cipherService,
IUserService userService,
ICurrentContext currentContext,
ILogger<ImportCiphersController> logger,
GlobalSettings globalSettings,
ICollectionRepository collectionRepository,
IAuthorizationService authorizationService,
IOrganizationRepository organizationRepository)
IImportCiphersCommand importCiphersCommand)
{
_cipherService = cipherService;
_userService = userService;
_currentContext = currentContext;
_logger = logger;
_globalSettings = globalSettings;
_collectionRepository = collectionRepository;
_authorizationService = authorizationService;
_importCiphersCommand = importCiphersCommand;
}

[HttpPost("import")]
Expand All @@ -57,7 +56,7 @@ public async Task PostImport([FromBody] ImportCiphersRequestModel model)
var userId = _userService.GetProperUserId(User).Value;
var folders = model.Folders.Select(f => f.ToFolder(userId)).ToList();
var ciphers = model.Ciphers.Select(c => c.ToCipherDetails(userId, false)).ToList();
await _cipherService.ImportCiphersAsync(folders, ciphers, model.FolderRelationships);
await _importCiphersCommand.ImportIntoIndividualVaultAsync(folders, ciphers, model.FolderRelationships);
}

[HttpPost("import-organization")]
Expand Down Expand Up @@ -85,7 +84,7 @@ public async Task PostImport([FromQuery] string organizationId,

var userId = _userService.GetProperUserId(User).Value;
var ciphers = model.Ciphers.Select(l => l.ToOrganizationCipherDetails(orgId)).ToList();
await _cipherService.ImportCiphersAsync(collections, ciphers, model.CollectionRelationships, userId);
await _importCiphersCommand.ImportIntoOrganizationalVaultAsync(collections, ciphers, model.CollectionRelationships, userId);
}

private async Task<bool> CheckOrgImportPermission(List<Collection> collections, Guid orgId)
Expand Down
1 change: 1 addition & 0 deletions src/Billing/Jobs/JobsHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ public override async Task StartAsync(CancellationToken cancellationToken)
public static void AddJobsServices(IServiceCollection services)
{
services.AddTransient<AliveJob>();
services.AddTransient<SubscriptionCancellationJob>();
}
}
58 changes: 58 additions & 0 deletions src/Billing/Jobs/SubscriptionCancellationJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Bit.Billing.Services;
using Bit.Core.Repositories;
using Quartz;
using Stripe;

namespace Bit.Billing.Jobs;

public class SubscriptionCancellationJob(
IStripeFacade stripeFacade,
IOrganizationRepository organizationRepository)
: IJob
{
public async Task Execute(IJobExecutionContext context)
{
var subscriptionId = context.MergedJobDataMap.GetString("subscriptionId");
var organizationId = new Guid(context.MergedJobDataMap.GetString("organizationId") ?? string.Empty);

var organization = await organizationRepository.GetByIdAsync(organizationId);
if (organization == null || organization.Enabled)
{
// Organization was deleted or re-enabled by CS, skip cancellation
return;
}

var subscription = await stripeFacade.GetSubscription(subscriptionId);
if (subscription?.Status != "unpaid")
{
// Subscription is no longer unpaid, skip cancellation
return;
}

// Cancel the subscription
await stripeFacade.CancelSubscription(subscriptionId, new SubscriptionCancelOptions());

// Void any open invoices
var options = new InvoiceListOptions
{
Status = "open",
Subscription = subscriptionId,
Limit = 100
};
var invoices = await stripeFacade.ListInvoices(options);
foreach (var invoice in invoices)
{
await stripeFacade.VoidInvoice(invoice.Id);
}

while (invoices.HasMore)
{
options.StartingAfter = invoices.Data.Last().Id;
invoices = await stripeFacade.ListInvoices(options);
foreach (var invoice in invoices)
{
await stripeFacade.VoidInvoice(invoice.Id);
}
}
}
}
Loading

0 comments on commit 88c0b8e

Please sign in to comment.