mirror of
https://github.com/bitwarden/server.git
synced 2025-07-23 10:32:02 -05:00
Families for enterprise/stripe integrations (#1699)
* Add PlanSponsorshipType to static store * Add sponsorship type to token and creates sponsorship * PascalCase properties * Require sponsorship for remove * Create subscription sponsorship helper class * Handle Sponsored subscription changes * Add sponsorship id to subscription metadata * Make sponsoring references nullable This state indicates that a sponsorship has lapsed, but was not able to be reverted for billing reasons * WIP: Validate and remove subscriptions * Update sponsorships on organization and org user delete * Add friendly name to organization sponsorship
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class OrganizationSponsorshipRedeemRequestModel
|
||||
{
|
||||
[Required]
|
||||
public PlanSponsorshipType PlanSponsorshipType { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoredOrganizationId { get; set; }
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ namespace Bit.Core.Models.Api.Request
|
||||
[Required]
|
||||
[StringLength(256)]
|
||||
[StrictEmailAddress]
|
||||
public string sponsoredEmail { get; set; }
|
||||
public string SponsoredEmail { get; set; }
|
||||
|
||||
[StringLength(256)]
|
||||
public string FriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace Bit.Core.Models.Api
|
||||
UserId = organization.UserId?.ToString();
|
||||
ProviderId = organization.ProviderId?.ToString();
|
||||
ProviderName = organization.ProviderName;
|
||||
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -68,5 +69,6 @@ namespace Bit.Core.Models.Api
|
||||
public bool HasPublicAndPrivateKeys { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public string FamilySponsorshipFriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
public class SponsoredOrganizationSubscription
|
||||
{
|
||||
public const string OrganizationSponsorhipIdMetadataKey = "OrganizationSponsorshipId";
|
||||
private readonly string _customerId;
|
||||
private readonly Organization _org;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly List<Stripe.TaxRate> _taxRates;
|
||||
|
||||
public SponsoredOrganizationSubscription(Organization org, Stripe.Subscription existingSubscription)
|
||||
{
|
||||
_org = org;
|
||||
_customerId = org.GatewayCustomerId;
|
||||
_plan = Utilities.StaticStore.GetPlan(org.PlanType);
|
||||
_taxRates = existingSubscription.DefaultTaxRates;
|
||||
}
|
||||
|
||||
public SponsorOrganizationSubscriptionOptions GetSponsorSubscriptionOptions(OrganizationSponsorship sponsorship,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0, bool premiumAccessAddon = false)
|
||||
{
|
||||
var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value);
|
||||
|
||||
var subCreateOptions = new SponsorOrganizationSubscriptionOptions(_customerId, _org, _plan,
|
||||
sponsoredPlan, _taxRates, additionalSeats, additionalStorageGb, premiumAccessAddon);
|
||||
|
||||
subCreateOptions.Metadata.Add(OrganizationSponsorhipIdMetadataKey, sponsorship.Id.ToString());
|
||||
return subCreateOptions;
|
||||
}
|
||||
|
||||
public OrganizationUpgradeSubscriptionOptions RemoveOrganizationSubscriptionOptions(int additionalSeats = 0,
|
||||
int additionalStorageGb = 0, bool premiumAccessAddon = false) =>
|
||||
new OrganizationUpgradeSubscriptionOptions(_customerId, _org, _plan, _taxRates,
|
||||
additionalSeats, additionalStorageGb, premiumAccessAddon);
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
using Bit.Core.Models.Table;
|
||||
using Stripe;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
|
||||
{
|
||||
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
||||
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan,
|
||||
int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
||||
{
|
||||
Items = new List<SubscriptionItemOptions>();
|
||||
Metadata = new Dictionary<string, string>
|
||||
@ -14,15 +16,6 @@ namespace Bit.Core.Models.Business
|
||||
[org.GatewayIdField()] = org.Id.ToString()
|
||||
};
|
||||
|
||||
if (plan.StripePlanId != null)
|
||||
{
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Plan = plan.StripePlanId,
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
|
||||
{
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
@ -49,15 +42,53 @@ namespace Bit.Core.Models.Business
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId))
|
||||
protected void AddPlanItem(StaticStore.Plan plan) => AddPlanItem(plan.StripePlanId);
|
||||
protected void AddPlanItem(StaticStore.SponsoredPlan sponsoredPlan) => AddPlanItem(sponsoredPlan.StripePlanId);
|
||||
protected void AddPlanItem(string stripePlanId)
|
||||
{
|
||||
if (stripePlanId != null)
|
||||
{
|
||||
DefaultTaxRates = new List<string>{ taxInfo.StripeTaxRateId };
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Plan = stripePlanId,
|
||||
Quantity = 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddTaxRateItem(TaxInfo taxInfo) => AddTaxRateItem(new List<string> { taxInfo.StripeTaxRateId });
|
||||
protected void AddTaxRateItem(List<Stripe.TaxRate> taxRates) => AddTaxRateItem(taxRates?.Select(t => t.Id).ToList());
|
||||
protected void AddTaxRateItem(List<string> taxRateIds)
|
||||
{
|
||||
if (taxRateIds != null && taxRateIds.Any())
|
||||
{
|
||||
DefaultTaxRates = taxRateIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
public abstract class UnsponsoredOrganizationSubscriptionOptionsBase : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public UnsponsoredOrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo,
|
||||
int additionalSeats, int additionalStorage, bool premiumAccessAddon) :
|
||||
base(org, plan, additionalSeats, additionalStorage, premiumAccessAddon)
|
||||
{
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(taxInfo);
|
||||
}
|
||||
public UnsponsoredOrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, List<Stripe.TaxRate> taxInfo,
|
||||
int additionalSeats, int additionalStorage, bool premiumAccessAddon) :
|
||||
base(org, plan, additionalSeats, additionalStorage, premiumAccessAddon)
|
||||
{
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(taxInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class OrganizationPurchaseSubscriptionOptions : UnsponsoredOrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public OrganizationPurchaseSubscriptionOptions(
|
||||
Organization org, StaticStore.Plan plan,
|
||||
@ -70,7 +101,7 @@ namespace Bit.Core.Models.Business
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
public class OrganizationUpgradeSubscriptionOptions : UnsponsoredOrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public OrganizationUpgradeSubscriptionOptions(
|
||||
string customerId, Organization org,
|
||||
@ -81,5 +112,43 @@ namespace Bit.Core.Models.Business
|
||||
{
|
||||
Customer = customerId;
|
||||
}
|
||||
public OrganizationUpgradeSubscriptionOptions(
|
||||
string customerId, Organization org,
|
||||
StaticStore.Plan plan, List<Stripe.TaxRate> taxInfo,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0,
|
||||
bool premiumAccessAddon = false) :
|
||||
base(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
}
|
||||
}
|
||||
|
||||
public class RemoveOrganizationSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public RemoveOrganizationSubscriptionOptions(string customerId, Organization org,
|
||||
StaticStore.Plan plan, List<string> existingTaxRateStripeIds,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0,
|
||||
bool premiumAccessAddon = false) :
|
||||
base(org, plan, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(existingTaxRateStripeIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SponsorOrganizationSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public SponsorOrganizationSubscriptionOptions(
|
||||
string customerId, Organization org, StaticStore.Plan existingPlan,
|
||||
StaticStore.SponsoredPlan sponsorshipPlan, List<Stripe.TaxRate> existingTaxRates, int additionalSeats = 0,
|
||||
int additionalStorageGb = 0, bool premiumAccessAddon = false) :
|
||||
base(org, existingPlan, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
AddPlanItem(sponsorshipPlan);
|
||||
AddTaxRateItem(existingTaxRates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,5 +33,6 @@ namespace Bit.Core.Models.Data
|
||||
public string PrivateKey { get; set; }
|
||||
public Guid? ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public string FamilySponsorshipFriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
12
src/Core/Models/StaticStore/SponsoredPlan.cs
Normal file
12
src/Core/Models/StaticStore/SponsoredPlan.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.StaticStore
|
||||
{
|
||||
public class SponsoredPlan
|
||||
{
|
||||
public PlanSponsorshipType PlanSponsorshipType { get; set; }
|
||||
public ProductType SponsoredProductType { get; set; }
|
||||
public ProductType SponsoringProductType { get; set; }
|
||||
public string StripePlanId { get; set; }
|
||||
}
|
||||
}
|
@ -9,12 +9,12 @@ namespace Bit.Core.Models.Table
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid? InstallationId { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoringOrganizationId { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoringOrganizationUserId { get; set; }
|
||||
public Guid? SponsoringOrganizationId { get; set; }
|
||||
public Guid? SponsoringOrganizationUserId { get; set; }
|
||||
public Guid? SponsoredOrganizationId { get; set; }
|
||||
[MaxLength(256)]
|
||||
public string FriendlyName { get; set; }
|
||||
[MaxLength(256)]
|
||||
public string OfferedToEmail { get; set; }
|
||||
public PlanSponsorshipType? PlanSponsorshipType { get; set; }
|
||||
[Required]
|
||||
|
Reference in New Issue
Block a user