1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-16 18:48:16 -05:00

[PM-8445] Allow for organization sales with no payment method for trials (#4800)

* Allow for OrganizationSales with no payment method

* Run dotnet format
This commit is contained in:
Alex Morask 2024-09-25 08:55:45 -04:00 committed by GitHub
parent 6514b342fc
commit 2e072aebe3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 101 deletions

View File

@ -4,7 +4,9 @@
public class CustomerSetup public class CustomerSetup
{ {
public required TokenizedPaymentSource TokenizedPaymentSource { get; set; } public TokenizedPaymentSource? TokenizedPaymentSource { get; set; }
public required TaxInformation TaxInformation { get; set; } public TaxInformation? TaxInformation { get; set; }
public string? Coupon { get; set; } public string? Coupon { get; set; }
public bool IsBillable => TokenizedPaymentSource != null && TaxInformation != null;
} }

View File

@ -41,18 +41,27 @@ public class OrganizationSale
SubscriptionSetup = GetSubscriptionSetup(upgrade) SubscriptionSetup = GetSubscriptionSetup(upgrade)
}; };
private static CustomerSetup? GetCustomerSetup(OrganizationSignup signup) private static CustomerSetup GetCustomerSetup(OrganizationSignup signup)
{ {
var customerSetup = new CustomerSetup
{
Coupon = signup.IsFromProvider
? StripeConstants.CouponIDs.MSPDiscount35
: signup.IsFromSecretsManagerTrial
? StripeConstants.CouponIDs.SecretsManagerStandalone
: null
};
if (!signup.PaymentMethodType.HasValue) if (!signup.PaymentMethodType.HasValue)
{ {
return null; return customerSetup;
} }
var tokenizedPaymentSource = new TokenizedPaymentSource( customerSetup.TokenizedPaymentSource = new TokenizedPaymentSource(
signup.PaymentMethodType!.Value, signup.PaymentMethodType!.Value,
signup.PaymentToken); signup.PaymentToken);
var taxInformation = new TaxInformation( customerSetup.TaxInformation = new TaxInformation(
signup.TaxInfo.BillingAddressCountry, signup.TaxInfo.BillingAddressCountry,
signup.TaxInfo.BillingAddressPostalCode, signup.TaxInfo.BillingAddressPostalCode,
signup.TaxInfo.TaxIdNumber, signup.TaxInfo.TaxIdNumber,
@ -61,18 +70,7 @@ public class OrganizationSale
signup.TaxInfo.BillingAddressCity, signup.TaxInfo.BillingAddressCity,
signup.TaxInfo.BillingAddressState); signup.TaxInfo.BillingAddressState);
var coupon = signup.IsFromProvider return customerSetup;
? StripeConstants.CouponIDs.MSPDiscount35
: signup.IsFromSecretsManagerTrial
? StripeConstants.CouponIDs.SecretsManagerStandalone
: null;
return new CustomerSetup
{
TokenizedPaymentSource = tokenizedPaymentSource,
TaxInformation = taxInformation,
Coupon = coupon
};
} }
private static SubscriptionSetup GetSubscriptionSetup(OrganizationUpgrade upgrade) private static SubscriptionSetup GetSubscriptionSetup(OrganizationUpgrade upgrade)

View File

@ -4,6 +4,8 @@ using Bit.Core.Billing.Models.Sales;
namespace Bit.Core.Billing.Services; namespace Bit.Core.Billing.Services;
#nullable enable
public interface IOrganizationBillingService public interface IOrganizationBillingService
{ {
/// <summary> /// <summary>
@ -29,7 +31,7 @@ public interface IOrganizationBillingService
/// </summary> /// </summary>
/// <param name="organizationId">The ID of the organization to retrieve metadata for.</param> /// <param name="organizationId">The ID of the organization to retrieve metadata for.</param>
/// <returns>An <see cref="OrganizationMetadata"/> record.</returns> /// <returns>An <see cref="OrganizationMetadata"/> record.</returns>
Task<OrganizationMetadata> GetMetadata(Guid organizationId); Task<OrganizationMetadata?> GetMetadata(Guid organizationId);
/// <summary> /// <summary>
/// Updates the provided <paramref name="organization"/>'s payment source and tax information. /// Updates the provided <paramref name="organization"/>'s payment source and tax information.

View File

@ -19,6 +19,8 @@ using Subscription = Stripe.Subscription;
namespace Bit.Core.Billing.Services.Implementations; namespace Bit.Core.Billing.Services.Implementations;
#nullable enable
public class OrganizationBillingService( public class OrganizationBillingService(
IBraintreeGateway braintreeGateway, IBraintreeGateway braintreeGateway,
IGlobalSettings globalSettings, IGlobalSettings globalSettings,
@ -53,7 +55,7 @@ public class OrganizationBillingService(
await organizationRepository.ReplaceAsync(organization); await organizationRepository.ReplaceAsync(organization);
} }
public async Task<OrganizationMetadata> GetMetadata(Guid organizationId) public async Task<OrganizationMetadata?> GetMetadata(Guid organizationId)
{ {
var organization = await organizationRepository.GetByIdAsync(organizationId); var organization = await organizationRepository.GetByIdAsync(organizationId);
@ -90,7 +92,7 @@ public class OrganizationBillingService(
new CustomerSetup new CustomerSetup
{ {
TokenizedPaymentSource = tokenizedPaymentSource, TokenizedPaymentSource = tokenizedPaymentSource,
TaxInformation = taxInformation, TaxInformation = taxInformation
}); });
organization.Gateway = GatewayType.Stripe; organization.Gateway = GatewayType.Stripe;
@ -110,7 +112,36 @@ public class OrganizationBillingService(
private async Task<Customer> CreateCustomerAsync( private async Task<Customer> CreateCustomerAsync(
Organization organization, Organization organization,
CustomerSetup customerSetup, CustomerSetup customerSetup,
List<string> expand = null) List<string>? expand = null)
{
var organizationDisplayName = organization.DisplayName();
var customerCreateOptions = new CustomerCreateOptions
{
Coupon = customerSetup.Coupon,
Description = organization.DisplayBusinessName(),
Email = organization.BillingEmail,
Expand = expand,
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
CustomFields = [
new CustomerInvoiceSettingsCustomFieldOptions
{
Name = organization.SubscriberType(),
Value = organizationDisplayName.Length <= 30
? organizationDisplayName
: organizationDisplayName[..30]
}]
},
Metadata = new Dictionary<string, string>
{
{ "region", globalSettings.BaseServiceUri.CloudRegion }
}
};
var braintreeCustomerId = "";
if (customerSetup.IsBillable)
{ {
if (customerSetup.TokenizedPaymentSource is not if (customerSetup.TokenizedPaymentSource is not
{ {
@ -136,41 +167,15 @@ public class OrganizationBillingService(
var (address, taxIdData) = customerSetup.TaxInformation.GetStripeOptions(); var (address, taxIdData) = customerSetup.TaxInformation.GetStripeOptions();
var organizationDisplayName = organization.DisplayName(); customerCreateOptions.Address = address;
customerCreateOptions.Tax = new CustomerTaxOptions
var customerCreateOptions = new CustomerCreateOptions
{
Address = address,
Coupon = customerSetup.Coupon,
Description = organization.DisplayBusinessName(),
Email = organization.BillingEmail,
Expand = expand,
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
CustomFields = [
new CustomerInvoiceSettingsCustomFieldOptions
{
Name = organization.SubscriberType(),
Value = organizationDisplayName.Length <= 30
? organizationDisplayName
: organizationDisplayName[..30]
}]
},
Metadata = new Dictionary<string, string>
{
{ "region", globalSettings.BaseServiceUri.CloudRegion }
},
Tax = new CustomerTaxOptions
{ {
ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately
},
TaxIdData = taxIdData
}; };
customerCreateOptions.TaxIdData = taxIdData;
var (type, token) = customerSetup.TokenizedPaymentSource; var (type, token) = customerSetup.TokenizedPaymentSource;
var braintreeCustomerId = "";
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (type) switch (type)
{ {
@ -212,6 +217,7 @@ public class OrganizationBillingService(
throw new BillingException(); throw new BillingException();
} }
} }
}
try try
{ {
@ -240,9 +246,11 @@ public class OrganizationBillingService(
} }
async Task Revert() async Task Revert()
{
if (customerSetup.IsBillable)
{ {
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (type) switch (customerSetup.TokenizedPaymentSource!.Type)
{ {
case PaymentMethodType.BankAccount: case PaymentMethodType.BankAccount:
{ {
@ -257,6 +265,7 @@ public class OrganizationBillingService(
} }
} }
} }
}
private async Task<Subscription> CreateSubscriptionAsync( private async Task<Subscription> CreateSubscriptionAsync(
Guid organizationId, Guid organizationId,
@ -334,7 +343,7 @@ public class OrganizationBillingService(
["organizationId"] = organizationId.ToString() ["organizationId"] = organizationId.ToString()
}, },
OffSession = true, OffSession = true,
TrialPeriodDays = plan.TrialPeriodDays, TrialPeriodDays = plan.TrialPeriodDays
}; };
return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);