1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-24 12:35:25 +01: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 required TokenizedPaymentSource TokenizedPaymentSource { get; set; }
public required TaxInformation TaxInformation { get; set; }
public TokenizedPaymentSource? TokenizedPaymentSource { get; set; }
public TaxInformation? TaxInformation { 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)
};
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)
{
return null;
return customerSetup;
}
var tokenizedPaymentSource = new TokenizedPaymentSource(
customerSetup.TokenizedPaymentSource = new TokenizedPaymentSource(
signup.PaymentMethodType!.Value,
signup.PaymentToken);
var taxInformation = new TaxInformation(
customerSetup.TaxInformation = new TaxInformation(
signup.TaxInfo.BillingAddressCountry,
signup.TaxInfo.BillingAddressPostalCode,
signup.TaxInfo.TaxIdNumber,
@ -61,18 +70,7 @@ public class OrganizationSale
signup.TaxInfo.BillingAddressCity,
signup.TaxInfo.BillingAddressState);
var coupon = signup.IsFromProvider
? StripeConstants.CouponIDs.MSPDiscount35
: signup.IsFromSecretsManagerTrial
? StripeConstants.CouponIDs.SecretsManagerStandalone
: null;
return new CustomerSetup
{
TokenizedPaymentSource = tokenizedPaymentSource,
TaxInformation = taxInformation,
Coupon = coupon
};
return customerSetup;
}
private static SubscriptionSetup GetSubscriptionSetup(OrganizationUpgrade upgrade)

View File

@ -4,6 +4,8 @@ using Bit.Core.Billing.Models.Sales;
namespace Bit.Core.Billing.Services;
#nullable enable
public interface IOrganizationBillingService
{
/// <summary>
@ -29,7 +31,7 @@ public interface IOrganizationBillingService
/// </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);
Task<OrganizationMetadata?> GetMetadata(Guid organizationId);
/// <summary>
/// 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;
#nullable enable
public class OrganizationBillingService(
IBraintreeGateway braintreeGateway,
IGlobalSettings globalSettings,
@ -53,7 +55,7 @@ public class OrganizationBillingService(
await organizationRepository.ReplaceAsync(organization);
}
public async Task<OrganizationMetadata> GetMetadata(Guid organizationId)
public async Task<OrganizationMetadata?> GetMetadata(Guid organizationId)
{
var organization = await organizationRepository.GetByIdAsync(organizationId);
@ -90,7 +92,7 @@ public class OrganizationBillingService(
new CustomerSetup
{
TokenizedPaymentSource = tokenizedPaymentSource,
TaxInformation = taxInformation,
TaxInformation = taxInformation
});
organization.Gateway = GatewayType.Stripe;
@ -110,7 +112,36 @@ public class OrganizationBillingService(
private async Task<Customer> CreateCustomerAsync(
Organization organization,
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
{
@ -136,41 +167,15 @@ public class OrganizationBillingService(
var (address, taxIdData) = customerSetup.TaxInformation.GetStripeOptions();
var organizationDisplayName = organization.DisplayName();
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
customerCreateOptions.Address = address;
customerCreateOptions.Tax = new CustomerTaxOptions
{
ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately
},
TaxIdData = taxIdData
};
customerCreateOptions.TaxIdData = taxIdData;
var (type, token) = customerSetup.TokenizedPaymentSource;
var braintreeCustomerId = "";
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (type)
{
@ -212,6 +217,7 @@ public class OrganizationBillingService(
throw new BillingException();
}
}
}
try
{
@ -240,9 +246,11 @@ public class OrganizationBillingService(
}
async Task Revert()
{
if (customerSetup.IsBillable)
{
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (type)
switch (customerSetup.TokenizedPaymentSource!.Type)
{
case PaymentMethodType.BankAccount:
{
@ -257,6 +265,7 @@ public class OrganizationBillingService(
}
}
}
}
private async Task<Subscription> CreateSubscriptionAsync(
Guid organizationId,
@ -334,7 +343,7 @@ public class OrganizationBillingService(
["organizationId"] = organizationId.ToString()
},
OffSession = true,
TrialPeriodDays = plan.TrialPeriodDays,
TrialPeriodDays = plan.TrialPeriodDays
};
return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);