From 7907d839c9e22a4299a32bda9d01fca7184ac259 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 26 Apr 2017 16:14:15 -0400 Subject: [PATCH] disable organization when subscription is canceled --- src/Billing/Controllers/StripeController.cs | 46 +++++++++++++++++-- src/Core/Models/Table/Organization.cs | 5 +- src/Core/Services/IOrganizationService.cs | 2 + .../Implementations/OrganizationService.cs | 32 +++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index c9c62ebe6b..a9c170de93 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -1,5 +1,10 @@ -using Microsoft.AspNetCore.Mvc; +using Bit.Core.Services; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using Stripe; +using System; +using System.Threading.Tasks; namespace Bit.Billing.Controllers { @@ -7,20 +12,53 @@ namespace Bit.Billing.Controllers public class StripeController : Controller { private readonly BillingSettings _billingSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IOrganizationService _organizationService; - public StripeController(IOptions billingSettings) + public StripeController( + IOptions billingSettings, + IHostingEnvironment hostingEnvironment, + IOrganizationService organizationService) { _billingSettings = billingSettings?.Value; + _hostingEnvironment = hostingEnvironment; + _organizationService = organizationService; } [HttpPost("webhook")] - public IActionResult PostWebhook([FromBody]dynamic body, [FromQuery] string key) + public async Task PostWebhook([FromBody]dynamic body, [FromQuery] string key) { - if(key != _billingSettings.StripeWebhookKey) + if(body == null || key != _billingSettings.StripeWebhookKey) { return new BadRequestResult(); } + var parsedEvent = StripeEventUtility.ParseEventDataItem(body) as StripeEvent; + if(string.IsNullOrWhiteSpace(parsedEvent?.Id)) + { + return new BadRequestResult(); + } + + if(_hostingEnvironment.IsProduction() && !parsedEvent.LiveMode) + { + return new BadRequestResult(); + } + + if(parsedEvent.Type == "customer.subscription.deleted") + { + var subscription = Mapper.MapFromJson(parsedEvent.Data.Object.ToString()) + as StripeSubscription; + if(subscription?.Status == "canceled" && (subscription.Metadata?.ContainsKey("organizationId") ?? false)) + { + var orgIdGuid = new Guid(subscription.Metadata["organizationId"]); + await _organizationService.DisableAsync(orgIdGuid); + } + } + else + { + // not handling this event type + } + return new OkResult(); } } diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index 9418d7a1a7..ecba2faf81 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -22,7 +22,10 @@ namespace Bit.Core.Models.Table public void SetNewId() { - Id = CoreHelpers.GenerateComb(); + if(Id == default(Guid)) + { + Id = CoreHelpers.GenerateComb(); + } } } } diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 878e097b2f..fb3bec2859 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -17,6 +17,8 @@ namespace Bit.Core.Services Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment); Task> SignUpAsync(OrganizationSignup organizationSignup); Task DeleteAsync(Organization organization); + Task DisableAsync(Guid organizationId); + Task EnableAsync(Guid organizationId); Task UpdateAsync(Organization organization, bool updateBilling = false); Task InviteUserAsync(Guid organizationId, Guid invitingUserId, string email, Enums.OrganizationUserType type, bool accessAllSubvaults, IEnumerable subvaults); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index b1dc93ee49..cb6057ac6b 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -294,6 +294,9 @@ namespace Bit.Core.Services PlanId = newPlan.StripePlanId, Quantity = 1 } + }, + Metadata = new Dictionary { + { "organizationId", organization.Id.ToString() } } }; @@ -478,6 +481,9 @@ namespace Bit.Core.Services $"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); } + // Pre-generate the org id so that we can save it with the Stripe subscription.. + Guid newOrgId = CoreHelpers.GenerateComb(); + if(plan.Type == PlanType.Free) { var ownerExistingOrgCount = @@ -505,6 +511,9 @@ namespace Bit.Core.Services PlanId = plan.StripePlanId, Quantity = 1 } + }, + Metadata = new Dictionary { + { "organizationId", newOrgId.ToString() } } }; @@ -534,6 +543,7 @@ namespace Bit.Core.Services var organization = new Organization { + Id = newOrgId, Name = signup.Name, BillingEmail = signup.BillingEmail, BusinessName = signup.BusinessName, @@ -617,6 +627,28 @@ namespace Bit.Core.Services await _organizationRepository.DeleteAsync(organization); } + public async Task DisableAsync(Guid organizationId) + { + var org = await _organizationRepository.GetByIdAsync(organizationId); + if(org != null && org.Enabled) + { + org.Enabled = false; + await _organizationRepository.ReplaceAsync(org); + + // TODO: send email to owners? + } + } + + public async Task EnableAsync(Guid organizationId) + { + var org = await _organizationRepository.GetByIdAsync(organizationId); + if(org != null && !org.Enabled) + { + org.Enabled = true; + await _organizationRepository.ReplaceAsync(org); + } + } + public async Task UpdateAsync(Organization organization, bool updateBilling = false) { if(organization.Id == default(Guid))