From f998b988ca1b15310377d13781c8b4bc33f6a1b3 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 12 Aug 2017 23:02:18 -0400 Subject: [PATCH] braintree webhooks --- src/Billing/BillingSettings.cs | 1 + .../Controllers/BraintreeController.cs | 142 ++++++++++++++++++ src/Billing/settings.json | 3 +- 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/Billing/Controllers/BraintreeController.cs diff --git a/src/Billing/BillingSettings.cs b/src/Billing/BillingSettings.cs index 358a1c807b..c04bc95aa8 100644 --- a/src/Billing/BillingSettings.cs +++ b/src/Billing/BillingSettings.cs @@ -4,5 +4,6 @@ { public virtual string StripeWebhookKey { get; set; } public virtual string StripeWebhookSecret { get; set; } + public virtual string BraintreeWebhookKey { get; set; } } } diff --git a/src/Billing/Controllers/BraintreeController.cs b/src/Billing/Controllers/BraintreeController.cs new file mode 100644 index 0000000000..0ba7cac04e --- /dev/null +++ b/src/Billing/Controllers/BraintreeController.cs @@ -0,0 +1,142 @@ +using Bit.Core; +using Bit.Core.Services; +using Braintree; +using Braintree.Exceptions; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace Bit.Billing.Controllers +{ + [Route("braintree")] + public class BraintreeController : Controller + { + private static BraintreeGateway _gateway; + private readonly BillingSettings _billingSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IOrganizationService _organizationService; + private readonly IUserService _userService; + + public BraintreeController( + IOptions billingSettings, + GlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment, + IOrganizationService organizationService, + IUserService userService) + { + if(_gateway == null) + { + _gateway = new BraintreeGateway + { + Environment = globalSettings.Braintree.Production ? + Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX, + MerchantId = globalSettings.Braintree.MerchantId, + PublicKey = globalSettings.Braintree.PublicKey, + PrivateKey = globalSettings.Braintree.PrivateKey + }; + } + + _billingSettings = billingSettings?.Value; + _hostingEnvironment = hostingEnvironment; + _organizationService = organizationService; + _userService = userService; + } + + [HttpPost("webhook")] + public async Task PostWebhook([FromQuery] string key) + { + if(key != _billingSettings.BraintreeWebhookKey) + { + return new BadRequestResult(); + } + + var payload = Request.Form["bt_payload"]; + var signature = Request.Form["bt_signature"]; + + WebhookNotification notification; + try + { + notification = _gateway.WebhookNotification.Parse(signature, payload); + } + catch(InvalidSignatureException) + { + return new BadRequestResult(); + } + + if(notification.Kind == WebhookKind.SUBSCRIPTION_CANCELED || + notification.Kind == WebhookKind.SUBSCRIPTION_WENT_ACTIVE || + notification.Kind == WebhookKind.SUBSCRIPTION_WENT_PAST_DUE || + notification.Kind == WebhookKind.SUBSCRIPTION_CHARGED_SUCCESSFULLY || + notification.Kind == WebhookKind.SUBSCRIPTION_CHARGED_UNSUCCESSFULLY || + notification.Kind == WebhookKind.SUBSCRIPTION_EXPIRED || + notification.Kind == WebhookKind.SUBSCRIPTION_TRIAL_ENDED) + { + var ids = GetIdsFromId(notification.Subscription.Id); + + if((notification.Kind == WebhookKind.SUBSCRIPTION_CANCELED || + notification.Kind == WebhookKind.SUBSCRIPTION_EXPIRED) && + (notification.Subscription.Status == SubscriptionStatus.CANCELED || + notification.Subscription.Status == SubscriptionStatus.EXPIRED)) + { + // org + if(ids.Item1.HasValue) + { + await _organizationService.DisableAsync(ids.Item1.Value, + notification.Subscription.BillingPeriodEndDate); + } + // user + else if(ids.Item2.HasValue) + { + await _userService.DisablePremiumAsync(ids.Item2.Value, + notification.Subscription.BillingPeriodEndDate); + } + } + else + { + // org + if(ids.Item1.HasValue) + { + await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value, + notification.Subscription.BillingPeriodEndDate); + } + // user + else if(ids.Item2.HasValue) + { + await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value, + notification.Subscription.BillingPeriodEndDate); + } + } + } + + return new OkResult(); + } + + private Tuple GetIdsFromId(string id) + { + Guid? orgId = null; + Guid? userId = null; + + if(id.Length >= 33) + { + var type = id[0]; + var entityIdString = id.Substring(1, 32); + Guid entityId; + if(Guid.TryParse(entityIdString, out entityId)) + { + if(type == 'o') + { + orgId = entityId; + } + else if(type == 'u') + { + userId = entityId; + } + } + } + + return new Tuple(orgId, userId); + } + } +} diff --git a/src/Billing/settings.json b/src/Billing/settings.json index 935233c0df..f4cf9fb956 100644 --- a/src/Billing/settings.json +++ b/src/Billing/settings.json @@ -37,7 +37,8 @@ }, "billingSettings": { "stripeWebhookKey": "SECRET", - "stripeWebhookSecret": "SECRET" + "stripeWebhookSecret": "SECRET", + "braintreeWebhookKey": "SECRET" }, "braintree": { "production": false,