mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 00:22:50 -05:00
Use upgrade path to change sponsorship
Sponsorships need to be annual to match the GB add-on charge rate
This commit is contained in:
@ -1,39 +0,0 @@
|
|||||||
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,14 +1,12 @@
|
|||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models.Business
|
namespace Bit.Core.Models.Business
|
||||||
{
|
{
|
||||||
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
|
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
|
||||||
{
|
{
|
||||||
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan,
|
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
||||||
int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
|
||||||
{
|
{
|
||||||
Items = new List<SubscriptionItemOptions>();
|
Items = new List<SubscriptionItemOptions>();
|
||||||
Metadata = new Dictionary<string, string>
|
Metadata = new Dictionary<string, string>
|
||||||
@ -16,6 +14,15 @@ namespace Bit.Core.Models.Business
|
|||||||
[org.GatewayIdField()] = org.Id.ToString()
|
[org.GatewayIdField()] = org.Id.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (plan.StripePlanId != null)
|
||||||
|
{
|
||||||
|
Items.Add(new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Plan = plan.StripePlanId,
|
||||||
|
Quantity = 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
|
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
|
||||||
{
|
{
|
||||||
Items.Add(new SubscriptionItemOptions
|
Items.Add(new SubscriptionItemOptions
|
||||||
@ -42,53 +49,15 @@ namespace Bit.Core.Models.Business
|
|||||||
Quantity = 1
|
Quantity = 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected void AddPlanItem(StaticStore.Plan plan) => AddPlanItem(plan.StripePlanId);
|
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId))
|
||||||
protected void AddPlanItem(StaticStore.SponsoredPlan sponsoredPlan) => AddPlanItem(sponsoredPlan.StripePlanId);
|
|
||||||
protected void AddPlanItem(string stripePlanId)
|
|
||||||
{
|
|
||||||
if (stripePlanId != null)
|
|
||||||
{
|
{
|
||||||
Items.Add(new SubscriptionItemOptions
|
DefaultTaxRates = new List<string> { taxInfo.StripeTaxRateId };
|
||||||
{
|
|
||||||
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(tax => !string.IsNullOrWhiteSpace(tax)))
|
|
||||||
{
|
|
||||||
DefaultTaxRates = taxRateIds;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class UnsponsoredOrganizationSubscriptionOptionsBase : OrganizationSubscriptionOptionsBase
|
public class OrganizationPurchaseSubscriptionOptions : 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(
|
public OrganizationPurchaseSubscriptionOptions(
|
||||||
Organization org, StaticStore.Plan plan,
|
Organization org, StaticStore.Plan plan,
|
||||||
@ -101,7 +70,7 @@ namespace Bit.Core.Models.Business
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrganizationUpgradeSubscriptionOptions : UnsponsoredOrganizationSubscriptionOptionsBase
|
public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||||
{
|
{
|
||||||
public OrganizationUpgradeSubscriptionOptions(
|
public OrganizationUpgradeSubscriptionOptions(
|
||||||
string customerId, Organization org,
|
string customerId, Organization org,
|
||||||
@ -112,43 +81,5 @@ namespace Bit.Core.Models.Business
|
|||||||
{
|
{
|
||||||
Customer = customerId;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
@ -6,16 +7,28 @@ namespace Bit.Core.Models.Business
|
|||||||
{
|
{
|
||||||
public abstract class SubscriptionUpdate
|
public abstract class SubscriptionUpdate
|
||||||
{
|
{
|
||||||
protected abstract string PlanId { get; }
|
protected abstract List<string> PlanIds { get; }
|
||||||
|
|
||||||
public abstract SubscriptionItemOptions RevertItemOptions(Subscription subscription);
|
public abstract List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription);
|
||||||
public abstract SubscriptionItemOptions UpgradeItemOptions(Subscription subscription);
|
public abstract List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription);
|
||||||
|
|
||||||
public bool UpdateNeeded(Subscription subscription) =>
|
public bool UpdateNeeded(Subscription subscription)
|
||||||
(SubscriptionItem(subscription)?.Quantity ?? 0) != (UpgradeItemOptions(subscription).Quantity ?? 0);
|
{
|
||||||
|
var upgradeItemsOptions = UpgradeItemsOptions(subscription);
|
||||||
|
foreach (var upgradeItemOptions in upgradeItemsOptions)
|
||||||
|
{
|
||||||
|
var upgradeQuantity = upgradeItemOptions.Quantity ?? 0;
|
||||||
|
var existingQuantity = SubscriptionItem(subscription, upgradeItemOptions.Plan)?.Quantity ?? 0;
|
||||||
|
if (upgradeQuantity != existingQuantity)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected SubscriptionItem SubscriptionItem(Subscription subscription) =>
|
protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) =>
|
||||||
subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == PlanId);
|
subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +37,7 @@ namespace Bit.Core.Models.Business
|
|||||||
private readonly Organization _organization;
|
private readonly Organization _organization;
|
||||||
private readonly StaticStore.Plan _plan;
|
private readonly StaticStore.Plan _plan;
|
||||||
private readonly long? _additionalSeats;
|
private readonly long? _additionalSeats;
|
||||||
protected override string PlanId => _plan.StripeSeatPlanId;
|
protected override List<string> PlanIds => new() { _plan.StripeSeatPlanId };
|
||||||
|
|
||||||
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
||||||
{
|
{
|
||||||
@ -33,27 +46,33 @@ namespace Bit.Core.Models.Business
|
|||||||
_additionalSeats = additionalSeats;
|
_additionalSeats = additionalSeats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SubscriptionItemOptions UpgradeItemOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
var item = SubscriptionItem(subscription);
|
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||||
return new SubscriptionItemOptions
|
return new()
|
||||||
{
|
{
|
||||||
Id = item?.Id,
|
new SubscriptionItemOptions
|
||||||
Plan = PlanId,
|
{
|
||||||
Quantity = _additionalSeats,
|
Id = item?.Id,
|
||||||
Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null,
|
Plan = PlanIds.Single(),
|
||||||
|
Quantity = _additionalSeats,
|
||||||
|
Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SubscriptionItemOptions RevertItemOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
var item = SubscriptionItem(subscription);
|
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||||
return new SubscriptionItemOptions
|
return new()
|
||||||
{
|
{
|
||||||
Id = item?.Id,
|
new SubscriptionItemOptions
|
||||||
Plan = PlanId,
|
{
|
||||||
Quantity = _organization.Seats,
|
Id = item?.Id,
|
||||||
Deleted = item?.Id != null ? true : (bool?)null,
|
Plan = PlanIds.Single(),
|
||||||
|
Quantity = _organization.Seats,
|
||||||
|
Deleted = item?.Id != null ? true : (bool?)null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +81,7 @@ namespace Bit.Core.Models.Business
|
|||||||
{
|
{
|
||||||
private readonly string _plan;
|
private readonly string _plan;
|
||||||
private readonly long? _additionalStorage;
|
private readonly long? _additionalStorage;
|
||||||
protected override string PlanId => _plan;
|
protected override List<string> PlanIds => new() { _plan };
|
||||||
|
|
||||||
public StorageSubscriptionUpdate(string plan, long? additionalStorage)
|
public StorageSubscriptionUpdate(string plan, long? additionalStorage)
|
||||||
{
|
{
|
||||||
@ -70,28 +89,102 @@ namespace Bit.Core.Models.Business
|
|||||||
_additionalStorage = additionalStorage;
|
_additionalStorage = additionalStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SubscriptionItemOptions UpgradeItemOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
var item = SubscriptionItem(subscription);
|
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||||
return new SubscriptionItemOptions
|
return new()
|
||||||
{
|
{
|
||||||
Id = item?.Id,
|
new SubscriptionItemOptions
|
||||||
Plan = _plan,
|
{
|
||||||
Quantity = _additionalStorage,
|
Id = item?.Id,
|
||||||
Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null,
|
Plan = _plan,
|
||||||
|
Quantity = _additionalStorage,
|
||||||
|
Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SubscriptionItemOptions RevertItemOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
var item = SubscriptionItem(subscription);
|
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||||
return new SubscriptionItemOptions
|
return new()
|
||||||
{
|
{
|
||||||
Id = item?.Id,
|
new SubscriptionItemOptions
|
||||||
Plan = _plan,
|
{
|
||||||
Quantity = item?.Quantity ?? 0,
|
Id = item?.Id,
|
||||||
Deleted = item?.Id != null ? true : (bool?)null,
|
Plan = _plan,
|
||||||
|
Quantity = item?.Quantity ?? 0,
|
||||||
|
Deleted = item?.Id != null ? true : (bool?)null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
||||||
|
{
|
||||||
|
private string _existingPlanStripeId;
|
||||||
|
private string _sponsoredPlanStripeId;
|
||||||
|
private bool _applySponsorship;
|
||||||
|
protected override List<string> PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId };
|
||||||
|
|
||||||
|
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
|
||||||
|
{
|
||||||
|
_existingPlanStripeId = existingPlan.StripePlanId;
|
||||||
|
_sponsoredPlanStripeId = sponsoredPlan.StripePlanId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Id = AddStripeItem(subscription)?.Id,
|
||||||
|
Plan = AddStripePlanId,
|
||||||
|
Quantity = 0,
|
||||||
|
Deleted = true,
|
||||||
|
},
|
||||||
|
new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Id = RemoveStripeItem(subscription)?.Id,
|
||||||
|
Plan = RemoveStripePlanId,
|
||||||
|
Quantity = 1,
|
||||||
|
Deleted = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Id = RemoveStripeItem(subscription)?.Id,
|
||||||
|
Plan = RemoveStripePlanId,
|
||||||
|
Quantity = 0,
|
||||||
|
Deleted = true,
|
||||||
|
},
|
||||||
|
new SubscriptionItemOptions
|
||||||
|
{
|
||||||
|
Id = AddStripeItem(subscription)?.Id,
|
||||||
|
Plan = AddStripePlanId,
|
||||||
|
Quantity = 1,
|
||||||
|
Deleted = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId;
|
||||||
|
private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId;
|
||||||
|
private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) =>
|
||||||
|
_applySponsorship ?
|
||||||
|
SubscriptionItem(subscription, _existingPlanStripeId) :
|
||||||
|
SubscriptionItem(subscription, _sponsoredPlanStripeId);
|
||||||
|
private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) =>
|
||||||
|
_applySponsorship ?
|
||||||
|
SubscriptionItem(subscription, _sponsoredPlanStripeId) :
|
||||||
|
SubscriptionItem(subscription, _existingPlanStripeId);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace Bit.Core.Services
|
|||||||
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
|
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
|
||||||
bool premiumAccessAddon, TaxInfo taxInfo);
|
bool premiumAccessAddon, TaxInfo taxInfo);
|
||||||
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||||
Task<bool> RemoveOrganizationSponsorshipAsync(Organization org);
|
Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||||
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan,
|
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan,
|
||||||
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
|
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
|
||||||
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
|
@ -128,7 +128,8 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if (existingSponsorship == null)
|
if (existingSponsorship == null)
|
||||||
{
|
{
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
// TODO: null safe this method
|
||||||
|
await RemoveSponsorshipAsync(sponsoredOrganization, null);
|
||||||
// TODO on fail, mark org as disabled.
|
// TODO on fail, mark org as disabled.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -136,7 +137,7 @@ namespace Bit.Core.Services
|
|||||||
var validated = true;
|
var validated = true;
|
||||||
if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null)
|
if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null)
|
||||||
{
|
{
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
||||||
validated = false;
|
validated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ namespace Bit.Core.Services
|
|||||||
.GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value);
|
.GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value);
|
||||||
if (!sponsoringOrganization.Enabled)
|
if (!sponsoringOrganization.Enabled)
|
||||||
{
|
{
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
||||||
validated = false;
|
validated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null)
|
public async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null)
|
||||||
{
|
{
|
||||||
var success = await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization);
|
await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization, sponsorship);
|
||||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
||||||
|
|
||||||
if (sponsorship == null)
|
if (sponsorship == null)
|
||||||
@ -174,49 +175,22 @@ namespace Bit.Core.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success)
|
// Initialize the record as available
|
||||||
{
|
sponsorship.SponsoredOrganizationId = null;
|
||||||
// Initialize the record as available
|
sponsorship.FriendlyName = null;
|
||||||
sponsorship.SponsoredOrganizationId = null;
|
sponsorship.OfferedToEmail = null;
|
||||||
sponsorship.FriendlyName = null;
|
sponsorship.PlanSponsorshipType = null;
|
||||||
sponsorship.OfferedToEmail = null;
|
sponsorship.TimesRenewedWithoutValidation = 0;
|
||||||
sponsorship.PlanSponsorshipType = null;
|
sponsorship.SponsorshipLapsedDate = null;
|
||||||
sponsorship.TimesRenewedWithoutValidation = 0;
|
|
||||||
sponsorship.SponsorshipLapsedDate = null;
|
|
||||||
|
|
||||||
if (sponsorship.CloudSponsor || sponsorship.SponsorshipLapsedDate.HasValue)
|
if (sponsorship.CloudSponsor || sponsorship.SponsorshipLapsedDate.HasValue)
|
||||||
{
|
{
|
||||||
await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
|
await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sponsorship.SponsoringOrganizationId = null;
|
|
||||||
sponsorship.SponsoringOrganizationUserId = null;
|
|
||||||
|
|
||||||
if (!sponsorship.CloudSponsor)
|
|
||||||
{
|
|
||||||
// Sef-hosted sponsorship record
|
|
||||||
// we need to make the existing sponsorship available, and add
|
|
||||||
// a new sponsorship record to record the lapsed sponsorship
|
|
||||||
var cleanSponsorship = new OrganizationSponsorship
|
|
||||||
{
|
|
||||||
InstallationId = sponsorship.InstallationId,
|
|
||||||
SponsoringOrganizationId = sponsorship.SponsoringOrganizationId,
|
|
||||||
SponsoringOrganizationUserId = sponsorship.SponsoringOrganizationUserId,
|
|
||||||
CloudSponsor = sponsorship.CloudSponsor,
|
|
||||||
};
|
|
||||||
await _organizationSponsorshipRepository.UpsertAsync(cleanSponsorship);
|
|
||||||
}
|
|
||||||
|
|
||||||
sponsorship.SponsorshipLapsedDate ??= DateTime.UtcNow;
|
|
||||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -192,43 +192,25 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship)
|
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship)
|
||||||
{
|
{
|
||||||
var customer = await _stripeAdapter.CustomerGetAsync(org.GatewayCustomerId);
|
var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType);
|
||||||
|
var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value);
|
||||||
|
var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
|
||||||
|
|
||||||
|
var prorationTime = DateTime.UtcNow;
|
||||||
|
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, prorationTime);
|
||||||
|
|
||||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
||||||
|
org.ExpirationDate = sub.CurrentPeriodEnd;
|
||||||
|
|
||||||
var sponsoredSubscription = new SponsoredOrganizationSubscription(org, sub);
|
|
||||||
|
|
||||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
|
||||||
false, PaymentMethodType.None, sponsoredSubscription.GetSponsorSubscriptionOptions(sponsorship), null);
|
|
||||||
org.GatewaySubscriptionId = subscription.Id;
|
|
||||||
|
|
||||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> RemoveOrganizationSponsorshipAsync(Organization org)
|
public Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship) =>
|
||||||
{
|
ChangeOrganizationSponsorship(org, sponsorship, true);
|
||||||
var customer = await _stripeAdapter.CustomerGetAsync(org.GatewayCustomerId);
|
|
||||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
|
||||||
|
|
||||||
var sponsoredSubscription = new SponsoredOrganizationSubscription(org, sub);
|
public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) =>
|
||||||
var subCreateOptions = sponsoredSubscription.RemoveOrganizationSubscriptionOptions();
|
ChangeOrganizationSponsorship(org, sponsorship, false);
|
||||||
|
|
||||||
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
|
|
||||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
|
||||||
stripePaymentMethod, paymentMethodType, subCreateOptions, null);
|
|
||||||
|
|
||||||
if (subscription.Status == "incomplete")
|
|
||||||
{
|
|
||||||
// TODO: revert
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
org.GatewaySubscriptionId = subscription.Id;
|
|
||||||
org.Enabled = true;
|
|
||||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan,
|
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan,
|
||||||
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo)
|
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo)
|
||||||
@ -736,11 +718,11 @@ namespace Bit.Core.Services
|
|||||||
var collectionMethod = sub.CollectionMethod;
|
var collectionMethod = sub.CollectionMethod;
|
||||||
var daysUntilDue = sub.DaysUntilDue;
|
var daysUntilDue = sub.DaysUntilDue;
|
||||||
var chargeNow = collectionMethod == "charge_automatically";
|
var chargeNow = collectionMethod == "charge_automatically";
|
||||||
var updatedItemOptions = subscriptionUpdate.UpgradeItemOptions(sub);
|
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
|
||||||
|
|
||||||
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
|
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
|
||||||
{
|
{
|
||||||
Items = new List<Stripe.SubscriptionItemOptions> { updatedItemOptions },
|
Items = updatedItemOptions,
|
||||||
ProrationBehavior = "always_invoice",
|
ProrationBehavior = "always_invoice",
|
||||||
DaysUntilDue = daysUntilDue ?? 1,
|
DaysUntilDue = daysUntilDue ?? 1,
|
||||||
CollectionMethod = "send_invoice",
|
CollectionMethod = "send_invoice",
|
||||||
@ -783,14 +765,8 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
|
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no amount due, invoice is autofinalized, we're done
|
|
||||||
if (invoice.AmountDue <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string paymentIntentClientSecret = null;
|
string paymentIntentClientSecret = null;
|
||||||
if (updatedItemOptions.Quantity > 0)
|
if (invoice.AmountDue > 0 && updatedItemOptions.Any(i => i.Quantity > 0))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -814,7 +790,7 @@ namespace Bit.Core.Services
|
|||||||
// Need to revert the subscription
|
// Need to revert the subscription
|
||||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
|
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
|
||||||
{
|
{
|
||||||
Items = new List<Stripe.SubscriptionItemOptions> { subscriptionUpdate.RevertItemOptions(sub) },
|
Items = subscriptionUpdate.RevertItemsOptions(sub),
|
||||||
// This proration behavior prevents a false "credit" from
|
// This proration behavior prevents a false "credit" from
|
||||||
// being applied forward to the next month's invoice
|
// being applied forward to the next month's invoice
|
||||||
ProrationBehavior = "none",
|
ProrationBehavior = "none",
|
||||||
|
@ -484,7 +484,7 @@ namespace Bit.Core.Utilities
|
|||||||
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||||
SponsoredProductType = ProductType.Families,
|
SponsoredProductType = ProductType.Families,
|
||||||
SponsoringProductType = ProductType.Enterprise,
|
SponsoringProductType = ProductType.Enterprise,
|
||||||
StripePlanId = "2021-enterprise-sponsored-families-org-monthly",
|
StripePlanId = "2021-family-for-enterprise-annually",
|
||||||
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
|
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
|
||||||
GetPlan(org.PlanType).Product == ProductType.Enterprise,
|
GetPlan(org.PlanType).Product == ProductType.Enterprise,
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user