Skip to content

Commit

Permalink
[AC-2965] Use OrganizationBillingService to purchase org when FF is on (
Browse files Browse the repository at this point in the history
#4737)

* Add PurchaseSubscription to OrganizationBillingService and call from OrganizationService.SignUpAsync when FF is on

* Run dotnet format

* Missed billing service DI for SCIM which uses the OrganizationService
  • Loading branch information
amorask-bitwarden authored Sep 6, 2024
1 parent 8491c58 commit c0a4ba8
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 39 deletions.
2 changes: 2 additions & 0 deletions bitwarden_license/src/Scim/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using Bit.Core.Billing.Extensions;
using Bit.Core.Context;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.SecretsManager.Repositories.Noop;
Expand Down Expand Up @@ -68,6 +69,7 @@ public void ConfigureServices(IServiceCollection services)
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddBillingOperations();

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Expand Down
2 changes: 2 additions & 0 deletions src/Billing/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Globalization;
using Bit.Billing.Services;
using Bit.Billing.Services.Implementations;
using Bit.Core.Billing.Extensions;
using Bit.Core.Context;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.SecretsManager.Repositories.Noop;
Expand Down Expand Up @@ -74,6 +75,7 @@ public void ConfigureServices(IServiceCollection services)
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddBillingOperations();

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
Expand Down Expand Up @@ -70,6 +71,7 @@ public class OrganizationService : IOrganizationService
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
private readonly IFeatureService _featureService;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IOrganizationBillingService _organizationBillingService;

public OrganizationService(
IOrganizationRepository organizationRepository,
Expand Down Expand Up @@ -103,7 +105,8 @@ public OrganizationService(
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
IProviderRepository providerRepository,
IFeatureService featureService,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery)
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IOrganizationBillingService organizationBillingService)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
Expand Down Expand Up @@ -137,6 +140,7 @@ public OrganizationService(
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
_featureService = featureService;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_organizationBillingService = organizationBillingService;
}

public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
Expand Down Expand Up @@ -577,10 +581,21 @@ await _referenceEventService.RaiseEventAsync(
}
else if (plan.Type != PlanType.Free)
{
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
var deprecateStripeSourcesAPI = _featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI);

if (deprecateStripeSourcesAPI)
{
var subscriptionPurchase = signup.ToSubscriptionPurchase(provider);

await _organizationBillingService.PurchaseSubscription(organization, subscriptionPurchase);
}
else
{
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
}
}

var ownerId = provider ? default : signup.Owner.Id;
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Billing/Constants/StripeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static class CollectionMethod

public static class CouponIDs
{
public const string MSPDiscount35 = "msp-discount-35";
public const string SecretsManagerStandalone = "sm-standalone";
}

Expand Down Expand Up @@ -51,4 +52,10 @@ public static class SubscriptionStatus
public const string Unpaid = "unpaid";
public const string Paused = "paused";
}

public static class ValidateTaxLocationTiming
{
public const string Deferred = "deferred";
public const string Immediately = "immediately";
}
}
27 changes: 27 additions & 0 deletions src/Core/Billing/Models/OrganizationSubscriptionPurchase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Bit.Core.Billing.Enums;

namespace Bit.Core.Billing.Models;

public record OrganizationSubscriptionPurchase(
OrganizationSubscriptionPurchaseMetadata Metadata,
OrganizationPasswordManagerSubscriptionPurchase PasswordManagerSubscription,
TokenizedPaymentSource PaymentSource,
PlanType PlanType,
OrganizationSecretsManagerSubscriptionPurchase SecretsManagerSubscription,
TaxInformation TaxInformation);

public record OrganizationPasswordManagerSubscriptionPurchase(
int Storage,
bool PremiumAccess,
int Seats);

public record OrganizationSecretsManagerSubscriptionPurchase(
int Seats,
int ServiceAccounts);

public record OrganizationSubscriptionPurchaseMetadata(
bool FromProvider,
bool FromSecretsManagerStandalone)
{
public static OrganizationSubscriptionPurchaseMetadata Default => new(false, false);
}
17 changes: 16 additions & 1 deletion src/Core/Billing/Services/IOrganizationBillingService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
using Bit.Core.Billing.Models;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Models;

namespace Bit.Core.Billing.Services;

public interface IOrganizationBillingService
{
/// <summary>
/// Retrieve metadata about the organization represented bsy the provided <paramref name="organizationId"/>.
/// </summary>
/// <param name="organizationId">The ID of the organization to retrieve metadata for.</param>
/// <returns>An <see cref="OrganizationMetadata"/> record.</returns>
Task<OrganizationMetadata> GetMetadata(Guid organizationId);

/// <summary>
/// Purchase a subscription for the provided <paramref name="organization"/> using the provided <paramref name="organizationSubscriptionPurchase"/>.
/// If successful, a Stripe <see cref="Stripe.Customer"/> and <see cref="Stripe.Subscription"/> will be created for the organization and the
/// organization will be enabled.
/// </summary>
/// <param name="organization">The organization to purchase a subscription for.</param>
/// <param name="organizationSubscriptionPurchase">The purchase information for the organization's subscription.</param>
Task PurchaseSubscription(Organization organization, OrganizationSubscriptionPurchase organizationSubscriptionPurchase);
}
10 changes: 10 additions & 0 deletions src/Core/Billing/Services/ISubscriberService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ Task CancelSubscription(
OffboardingSurveyResponse offboardingSurveyResponse,
bool cancelImmediately);

/// <summary>
/// Creates a Braintree <see cref="Braintree.Customer"/> for the provided <paramref name="subscriber"/> while attaching the provided <paramref name="paymentMethodNonce"/>.
/// </summary>
/// <param name="subscriber">The subscriber to create a Braintree customer for.</param>
/// <param name="paymentMethodNonce">A nonce representing the PayPal payment method the customer will use for payments.</param>
/// <returns>The <see cref="Braintree.Customer.Id"/> of the created Braintree customer.</returns>
Task<string> CreateBraintreeCustomer(
ISubscriber subscriber,
string paymentMethodNonce);

/// <summary>
/// Retrieves a Stripe <see cref="Customer"/> using the <paramref name="subscriber"/>'s <see cref="ISubscriber.GatewayCustomerId"/> property.
/// </summary>
Expand Down
Loading

0 comments on commit c0a4ba8

Please sign in to comment.