mirror of
https://github.com/bitwarden/server.git
synced 2024-11-24 12:35:25 +01:00
[AC-1843] Automate PM discount for SM Trial (#3661)
* Added appliesTo to customer discount. Added productId to subscription item * Added IsFromSecretsManagerTrial flag to add discount for SM trials * Fixed broken tests --------- Co-authored-by: Alex Morask <amorask@bitwarden.com>
This commit is contained in:
parent
693f0566a6
commit
d7de5cbf28
@ -46,6 +46,7 @@ public class OrganizationCreateRequestModel : IValidatableObject
|
|||||||
public int? AdditionalServiceAccounts { get; set; }
|
public int? AdditionalServiceAccounts { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public bool UseSecretsManager { get; set; }
|
public bool UseSecretsManager { get; set; }
|
||||||
|
public bool IsFromSecretsManagerTrial { get; set; }
|
||||||
|
|
||||||
public virtual OrganizationSignup ToOrganizationSignup(User user)
|
public virtual OrganizationSignup ToOrganizationSignup(User user)
|
||||||
{
|
{
|
||||||
@ -67,6 +68,7 @@ public class OrganizationCreateRequestModel : IValidatableObject
|
|||||||
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(),
|
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(),
|
||||||
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(),
|
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(),
|
||||||
UseSecretsManager = UseSecretsManager,
|
UseSecretsManager = UseSecretsManager,
|
||||||
|
IsFromSecretsManagerTrial = IsFromSecretsManagerTrial,
|
||||||
TaxInfo = new TaxInfo
|
TaxInfo = new TaxInfo
|
||||||
{
|
{
|
||||||
TaxIdNumber = TaxIdNumber,
|
TaxIdNumber = TaxIdNumber,
|
||||||
|
@ -50,11 +50,13 @@ public class BillingCustomerDiscount
|
|||||||
Id = discount.Id;
|
Id = discount.Id;
|
||||||
Active = discount.Active;
|
Active = discount.Active;
|
||||||
PercentOff = discount.PercentOff;
|
PercentOff = discount.PercentOff;
|
||||||
|
AppliesTo = discount.AppliesTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
public bool Active { get; }
|
public bool Active { get; }
|
||||||
public decimal? PercentOff { get; }
|
public decimal? PercentOff { get; }
|
||||||
|
public List<string> AppliesTo { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BillingSubscription
|
public class BillingSubscription
|
||||||
@ -89,6 +91,7 @@ public class BillingSubscription
|
|||||||
{
|
{
|
||||||
public BillingSubscriptionItem(SubscriptionInfo.BillingSubscription.BillingSubscriptionItem item)
|
public BillingSubscriptionItem(SubscriptionInfo.BillingSubscription.BillingSubscriptionItem item)
|
||||||
{
|
{
|
||||||
|
ProductId = item.ProductId;
|
||||||
Name = item.Name;
|
Name = item.Name;
|
||||||
Amount = item.Amount;
|
Amount = item.Amount;
|
||||||
Interval = item.Interval;
|
Interval = item.Interval;
|
||||||
@ -97,6 +100,7 @@ public class BillingSubscription
|
|||||||
AddonSubscriptionItem = item.AddonSubscriptionItem;
|
AddonSubscriptionItem = item.AddonSubscriptionItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ProductId { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
|
@ -517,7 +517,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
|
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
|
||||||
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
|
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
|
||||||
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
|
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault());
|
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ownerId = provider ? default : signup.Owner.Id;
|
var ownerId = provider ? default : signup.Owner.Id;
|
||||||
|
@ -15,4 +15,5 @@ public class OrganizationUpgrade
|
|||||||
public int? AdditionalSmSeats { get; set; }
|
public int? AdditionalSmSeats { get; set; }
|
||||||
public int? AdditionalServiceAccounts { get; set; }
|
public int? AdditionalServiceAccounts { get; set; }
|
||||||
public bool UseSecretsManager { get; set; }
|
public bool UseSecretsManager { get; set; }
|
||||||
|
public bool IsFromSecretsManagerTrial { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,13 @@ public class SubscriptionInfo
|
|||||||
Id = discount.Id;
|
Id = discount.Id;
|
||||||
Active = discount.Start != null && discount.End == null;
|
Active = discount.Start != null && discount.End == null;
|
||||||
PercentOff = discount.Coupon?.PercentOff;
|
PercentOff = discount.Coupon?.PercentOff;
|
||||||
|
AppliesTo = discount.Coupon?.AppliesTo?.Products ?? new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
public bool Active { get; }
|
public bool Active { get; }
|
||||||
public decimal? PercentOff { get; }
|
public decimal? PercentOff { get; }
|
||||||
|
public List<string> AppliesTo { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BillingSubscription
|
public class BillingSubscription
|
||||||
@ -59,6 +61,7 @@ public class SubscriptionInfo
|
|||||||
{
|
{
|
||||||
if (item.Plan != null)
|
if (item.Plan != null)
|
||||||
{
|
{
|
||||||
|
ProductId = item.Plan.ProductId;
|
||||||
Name = item.Plan.Nickname;
|
Name = item.Plan.Nickname;
|
||||||
Amount = item.Plan.Amount.GetValueOrDefault() / 100M;
|
Amount = item.Plan.Amount.GetValueOrDefault() / 100M;
|
||||||
Interval = item.Plan.Interval;
|
Interval = item.Plan.Interval;
|
||||||
@ -72,6 +75,7 @@ public class SubscriptionInfo
|
|||||||
|
|
||||||
public bool AddonSubscriptionItem { get; set; }
|
public bool AddonSubscriptionItem { get; set; }
|
||||||
|
|
||||||
|
public string ProductId { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
|
@ -12,7 +12,7 @@ public interface IPaymentService
|
|||||||
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
|
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
|
||||||
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
|
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
|
||||||
bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0,
|
bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0,
|
||||||
int additionalServiceAccount = 0);
|
int additionalServiceAccount = 0, bool signupIsFromSecretsManagerTrial = false);
|
||||||
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||||
Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship);
|
Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||||
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan, OrganizationUpgrade upgrade);
|
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan, OrganizationUpgrade upgrade);
|
||||||
|
@ -7,6 +7,7 @@ using Bit.Core.Models.Business;
|
|||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Stripe;
|
||||||
using StaticStore = Bit.Core.Models.StaticStore;
|
using StaticStore = Bit.Core.Models.StaticStore;
|
||||||
using TaxRate = Bit.Core.Entities.TaxRate;
|
using TaxRate = Bit.Core.Entities.TaxRate;
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ public class StripePaymentService : IPaymentService
|
|||||||
private const string PremiumPlanId = "premium-annually";
|
private const string PremiumPlanId = "premium-annually";
|
||||||
private const string StoragePlanId = "storage-gb-annually";
|
private const string StoragePlanId = "storage-gb-annually";
|
||||||
private const string ProviderDiscountId = "msp-discount-35";
|
private const string ProviderDiscountId = "msp-discount-35";
|
||||||
|
private const string SecretsManagerStandaloneDiscountId = "sm-standalone";
|
||||||
|
|
||||||
private readonly ITransactionRepository _transactionRepository;
|
private readonly ITransactionRepository _transactionRepository;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
@ -47,7 +49,7 @@ public class StripePaymentService : IPaymentService
|
|||||||
public async Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
|
public async Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
|
||||||
string paymentToken, StaticStore.Plan plan, short additionalStorageGb,
|
string paymentToken, StaticStore.Plan plan, short additionalStorageGb,
|
||||||
int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false,
|
int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false,
|
||||||
int additionalSmSeats = 0, int additionalServiceAccount = 0)
|
int additionalSmSeats = 0, int additionalServiceAccount = 0, bool signupIsFromSecretsManagerTrial = false)
|
||||||
{
|
{
|
||||||
Braintree.Customer braintreeCustomer = null;
|
Braintree.Customer braintreeCustomer = null;
|
||||||
string stipeCustomerSourceToken = null;
|
string stipeCustomerSourceToken = null;
|
||||||
@ -124,7 +126,11 @@ public class StripePaymentService : IPaymentService
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Coupon = provider ? ProviderDiscountId : null,
|
Coupon = signupIsFromSecretsManagerTrial
|
||||||
|
? SecretsManagerStandaloneDiscountId
|
||||||
|
: provider
|
||||||
|
? ProviderDiscountId
|
||||||
|
: null,
|
||||||
Address = new Stripe.AddressOptions
|
Address = new Stripe.AddressOptions
|
||||||
{
|
{
|
||||||
Country = taxInfo.BillingAddressCountry,
|
Country = taxInfo.BillingAddressCountry,
|
||||||
@ -1410,7 +1416,9 @@ public class StripePaymentService : IPaymentService
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
{
|
{
|
||||||
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
|
var customerGetOptions = new CustomerGetOptions();
|
||||||
|
customerGetOptions.AddExpand("discount.coupon.applies_to");
|
||||||
|
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||||
|
|
||||||
if (customer.Discount != null)
|
if (customer.Discount != null)
|
||||||
{
|
{
|
||||||
@ -1418,28 +1426,35 @@ public class StripePaymentService : IPaymentService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
|
if (string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
|
||||||
{
|
{
|
||||||
|
return subscriptionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||||
if (sub != null)
|
if (sub != null)
|
||||||
{
|
{
|
||||||
subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub);
|
subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
if (sub is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
{
|
{
|
||||||
|
return subscriptionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var upcomingInvoice = await _stripeAdapter.InvoiceUpcomingAsync(
|
var upcomingInvoiceOptions = new UpcomingInvoiceOptions { Customer = subscriber.GatewayCustomerId };
|
||||||
new Stripe.UpcomingInvoiceOptions { Customer = subscriber.GatewayCustomerId });
|
var upcomingInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions);
|
||||||
|
|
||||||
if (upcomingInvoice != null)
|
if (upcomingInvoice != null)
|
||||||
{
|
{
|
||||||
subscriptionInfo.UpcomingInvoice =
|
subscriptionInfo.UpcomingInvoice = new SubscriptionInfo.BillingUpcomingInvoice(upcomingInvoice);
|
||||||
new SubscriptionInfo.BillingUpcomingInvoice(upcomingInvoice);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Stripe.StripeException) { }
|
catch (StripeException ex)
|
||||||
}
|
{
|
||||||
|
_logger.LogWarning(ex, "Encountered an unexpected Stripe error");
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscriptionInfo;
|
return subscriptionInfo;
|
||||||
|
@ -209,6 +209,7 @@ public class OrganizationServiceTests
|
|||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
signup.PremiumAccessAddon = false;
|
signup.PremiumAccessAddon = false;
|
||||||
signup.UseSecretsManager = false;
|
signup.UseSecretsManager = false;
|
||||||
|
signup.IsFromSecretsManagerTrial = false;
|
||||||
|
|
||||||
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
|
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
|
||||||
|
|
||||||
@ -247,7 +248,8 @@ public class OrganizationServiceTests
|
|||||||
signup.TaxInfo,
|
signup.TaxInfo,
|
||||||
false,
|
false,
|
||||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault()
|
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||||
|
signup.UseSecretsManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +328,7 @@ public class OrganizationServiceTests
|
|||||||
signup.AdditionalServiceAccounts = 20;
|
signup.AdditionalServiceAccounts = 20;
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
signup.PremiumAccessAddon = false;
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.IsFromSecretsManagerTrial = false;
|
||||||
|
|
||||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||||
|
|
||||||
@ -362,7 +365,8 @@ public class OrganizationServiceTests
|
|||||||
signup.TaxInfo,
|
signup.TaxInfo,
|
||||||
false,
|
false,
|
||||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault()
|
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||||
|
signup.IsFromSecretsManagerTrial
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user