mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
Add sponsorship validation to upcoming invoice webhook
This commit is contained in:
@ -141,14 +141,6 @@ namespace Bit.Billing.Controllers
|
|||||||
{
|
{
|
||||||
var newEndPeriod = subscription.CurrentPeriodEnd;
|
var newEndPeriod = subscription.CurrentPeriodEnd;
|
||||||
|
|
||||||
// sponsored org
|
|
||||||
if (IsSponsoredSubscription(subscription))
|
|
||||||
{
|
|
||||||
var sponsorshipValid = await _organizationSponsorshipService
|
|
||||||
.ValidateSponsorshipAsync(ids.Item1.Value);
|
|
||||||
// TODO: How do we return from this?
|
|
||||||
}
|
|
||||||
|
|
||||||
await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
|
await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
|
||||||
subscription.CurrentPeriodEnd);
|
subscription.CurrentPeriodEnd);
|
||||||
}
|
}
|
||||||
@ -177,6 +169,13 @@ namespace Bit.Billing.Controllers
|
|||||||
// org
|
// org
|
||||||
if (ids.Item1.HasValue)
|
if (ids.Item1.HasValue)
|
||||||
{
|
{
|
||||||
|
// sponsored org
|
||||||
|
if (IsSponsoredSubscription(subscription))
|
||||||
|
{
|
||||||
|
await _organizationSponsorshipService
|
||||||
|
.ValidateSponsorshipAsync(ids.Item1.Value);
|
||||||
|
}
|
||||||
|
|
||||||
var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);
|
var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);
|
||||||
if (org != null && OrgPlanForInvoiceNotifications(org))
|
if (org != null && OrgPlanForInvoiceNotifications(org))
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ namespace Bit.Core.Models.Business
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) =>
|
protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) =>
|
||||||
subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
|
planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -122,58 +122,70 @@ namespace Bit.Core.Models.Business
|
|||||||
|
|
||||||
public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
||||||
{
|
{
|
||||||
private string _existingPlanStripeId;
|
private readonly string _existingPlanStripeId;
|
||||||
private string _sponsoredPlanStripeId;
|
private readonly string _sponsoredPlanStripeId;
|
||||||
private bool _applySponsorship;
|
private readonly bool _applySponsorship;
|
||||||
protected override List<string> PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId };
|
protected override List<string> PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId };
|
||||||
|
|
||||||
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
|
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
|
||||||
{
|
{
|
||||||
_existingPlanStripeId = existingPlan.StripePlanId;
|
_existingPlanStripeId = existingPlan.StripePlanId;
|
||||||
_sponsoredPlanStripeId = sponsoredPlan.StripePlanId;
|
_sponsoredPlanStripeId = sponsoredPlan?.StripePlanId;
|
||||||
_applySponsorship = applySponsorship;
|
_applySponsorship = applySponsorship;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
return new()
|
var result = new List<SubscriptionItemOptions>();
|
||||||
|
if (AddStripeItem(subscription) != null)
|
||||||
{
|
{
|
||||||
new SubscriptionItemOptions
|
result.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Id = AddStripeItem(subscription)?.Id,
|
Id = AddStripeItem(subscription)?.Id,
|
||||||
Plan = AddStripePlanId,
|
Plan = AddStripePlanId,
|
||||||
Quantity = 0,
|
Quantity = 0,
|
||||||
Deleted = true,
|
Deleted = true,
|
||||||
},
|
});
|
||||||
new SubscriptionItemOptions
|
}
|
||||||
|
|
||||||
|
if (RemoveStripeItem(subscription) != null)
|
||||||
|
{
|
||||||
|
result.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Id = RemoveStripeItem(subscription)?.Id,
|
Id = RemoveStripeItem(subscription)?.Id,
|
||||||
Plan = RemoveStripePlanId,
|
Plan = RemoveStripePlanId,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
Deleted = false,
|
Deleted = false,
|
||||||
},
|
});
|
||||||
};
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||||
{
|
{
|
||||||
return new()
|
var result = new List<SubscriptionItemOptions>();
|
||||||
|
if (RemoveStripeItem(subscription) != null)
|
||||||
{
|
{
|
||||||
new SubscriptionItemOptions
|
result.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Id = RemoveStripeItem(subscription)?.Id,
|
Id = RemoveStripeItem(subscription)?.Id,
|
||||||
Plan = RemoveStripePlanId,
|
Plan = RemoveStripePlanId,
|
||||||
Quantity = 0,
|
Quantity = 0,
|
||||||
Deleted = true,
|
Deleted = true,
|
||||||
},
|
});
|
||||||
new SubscriptionItemOptions
|
}
|
||||||
|
|
||||||
|
if (AddStripeItem(subscription) != null)
|
||||||
|
{
|
||||||
|
result.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Id = AddStripeItem(subscription)?.Id,
|
Id = AddStripeItem(subscription)?.Id,
|
||||||
Plan = AddStripePlanId,
|
Plan = AddStripePlanId,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
Deleted = false,
|
Deleted = false,
|
||||||
},
|
});
|
||||||
};
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId;
|
private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId;
|
||||||
|
@ -2,6 +2,6 @@ namespace Bit.Core.Models.Mail.FamiliesForEnterprise
|
|||||||
{
|
{
|
||||||
public class FamiliesForEnterpriseSponsorshipRevertingViewModel : BaseMailModel
|
public class FamiliesForEnterpriseSponsorshipRevertingViewModel : BaseMailModel
|
||||||
{
|
{
|
||||||
|
public string OrganizationName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ namespace Bit.Core.Services
|
|||||||
Task SendFamiliesForEnterpriseOfferEmailAsync(string email, string organizationName, string token);
|
Task SendFamiliesForEnterpriseOfferEmailAsync(string email, string organizationName, string token);
|
||||||
Task SendFamiliesForEnterpriseRedeemedEmailsAsync(string familyUserEmail, string sponsorEmail, string sponsorOrgName);
|
Task SendFamiliesForEnterpriseRedeemedEmailsAsync(string familyUserEmail, string sponsorEmail, string sponsorOrgName);
|
||||||
Task SendFamiliesForEnterpriseReconfirmationRequiredEmailAsync(string email);
|
Task SendFamiliesForEnterpriseReconfirmationRequiredEmailAsync(string email);
|
||||||
Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email);
|
Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email, string familyOrgName);
|
||||||
Task SendFamiliesForEnterpriseSponsorshipEndingEmailAsync(string email);
|
Task SendFamiliesForEnterpriseSponsorshipEndingEmailAsync(string email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -801,7 +801,7 @@ namespace Bit.Core.Services
|
|||||||
var message = CreateDefaultMessage("A User Has Redeemeed Your Sponsorship", email);
|
var message = CreateDefaultMessage("A User Has Redeemeed Your Sponsorship", email);
|
||||||
var model = new FamiliesForEnterpriseRedeemedToOrgUserViewModel
|
var model = new FamiliesForEnterpriseRedeemedToOrgUserViewModel
|
||||||
{
|
{
|
||||||
OrganizationName = organizationName,
|
OrganizationName = CoreHelpers.SanitizeForEmail(organizationName, false),
|
||||||
};
|
};
|
||||||
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseRedeemedToOrgUser", model);
|
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseRedeemedToOrgUser", model);
|
||||||
message.Category = "FamilyForEnterpriseRedeemedToOrgUser";
|
message.Category = "FamilyForEnterpriseRedeemedToOrgUser";
|
||||||
@ -821,13 +821,13 @@ namespace Bit.Core.Services
|
|||||||
await _mailDeliveryService.SendEmailAsync(message);
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email)
|
public async Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email, string familyOrgName)
|
||||||
{
|
{
|
||||||
// TODO: Complete emails
|
// TODO: Complete emails
|
||||||
var message = CreateDefaultMessage("A Family Organization Sponsorship Is Reverting", email);
|
var message = CreateDefaultMessage($"{familyOrgName} Organization Sponsorship Is No Longer Valid", email);
|
||||||
var model = new FamiliesForEnterpriseSponsorshipRevertingViewModel
|
var model = new FamiliesForEnterpriseSponsorshipRevertingViewModel
|
||||||
{
|
{
|
||||||
|
OrganizationName = CoreHelpers.SanitizeForEmail(familyOrgName, false),
|
||||||
};
|
};
|
||||||
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseSponsorshipReverting", model);
|
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseSponsorshipReverting", model);
|
||||||
message.Category = "FamiliesForEnterpriseSponsorshipReverting";
|
message.Category = "FamiliesForEnterpriseSponsorshipReverting";
|
||||||
|
@ -133,38 +133,30 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if (existingSponsorship == null)
|
if (existingSponsorship == null)
|
||||||
{
|
{
|
||||||
// TODO: null safe this method
|
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization, null);
|
await RemoveSponsorshipAsync(sponsoredOrganization, null);
|
||||||
// TODO on fail, mark org as disabled.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validated = true;
|
if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null || existingSponsorship.PlanSponsorshipType == null)
|
||||||
if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null)
|
|
||||||
{
|
{
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
||||||
validated = false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(existingSponsorship.PlanSponsorshipType.Value);
|
||||||
|
|
||||||
var sponsoringOrganization = await _organizationRepository
|
var sponsoringOrganization = await _organizationRepository
|
||||||
.GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value);
|
.GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value);
|
||||||
if (!sponsoringOrganization.Enabled)
|
if (sponsoringOrganization == null)
|
||||||
{
|
{
|
||||||
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
||||||
validated = false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validated && existingSponsorship.SponsoredOrganizationId != null)
|
var sponsoringOrgPlan = Utilities.StaticStore.GetPlan(sponsoringOrganization.PlanType);
|
||||||
|
if (!sponsoringOrganization.Enabled || sponsoredPlan.SponsoringProductType != sponsoringOrgPlan.Product)
|
||||||
{
|
{
|
||||||
existingSponsorship.TimesRenewedWithoutValidation += 1;
|
await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship);
|
||||||
existingSponsorship.SponsorshipLapsedDate ??= DateTime.UtcNow;
|
return false;
|
||||||
|
|
||||||
await _organizationSponsorshipRepository.UpsertAsync(existingSponsorship);
|
|
||||||
if (existingSponsorship.TimesRenewedWithoutValidation >= 6)
|
|
||||||
{
|
|
||||||
sponsoredOrganization.Enabled = false;
|
|
||||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -175,6 +167,10 @@ namespace Bit.Core.Services
|
|||||||
await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization, sponsorship);
|
await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization, sponsorship);
|
||||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
||||||
|
|
||||||
|
await _mailService.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(
|
||||||
|
sponsoredOrganization.BillingEmailAddress(),
|
||||||
|
sponsoredOrganization.Name);
|
||||||
|
|
||||||
if (sponsorship == null)
|
if (sponsorship == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -197,6 +193,5 @@ namespace Bit.Core.Services
|
|||||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,9 @@ namespace Bit.Core.Services
|
|||||||
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship)
|
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship)
|
||||||
{
|
{
|
||||||
var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType);
|
var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType);
|
||||||
var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value);
|
var sponsoredPlan = sponsorship != null ?
|
||||||
|
Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) :
|
||||||
|
null;
|
||||||
var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
|
var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
|
||||||
|
|
||||||
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, DateTime.UtcNow);
|
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, DateTime.UtcNow);
|
||||||
|
Reference in New Issue
Block a user