From 162a8c5e4e017067e7f291a8d4f8c58c5b5d16f8 Mon Sep 17 00:00:00 2001 From: Uncled1023 Date: Mon, 15 Nov 2021 20:17:42 -0800 Subject: [PATCH] Fixed billing webhook endpoints --- BillingCore/BillingService.cs | 4 +- BillingCore/StripeService.cs | 12 ++-- Configuration/BillingConfig.cs | 6 +- Teknik/App_Data/endpointMappings.json | 22 +++++++ .../V1/Controllers/BillingAPIv1Controller.cs | 60 +++---------------- Teknik/Areas/Billing/BillingHelper.cs | 56 +++++++++++++++++ Utilities/TagHelpers/VersionHelper.cs | 11 +++- 7 files changed, 108 insertions(+), 63 deletions(-) create mode 100644 Teknik/Areas/Billing/BillingHelper.cs diff --git a/BillingCore/BillingService.cs b/BillingCore/BillingService.cs index d301d16..e9ab2bd 100644 --- a/BillingCore/BillingService.cs +++ b/BillingCore/BillingService.cs @@ -36,8 +36,8 @@ namespace Teknik.BillingCore public abstract CheckoutSession CreateCheckoutSession(string customerId, string priceId, string successUrl, string cancelUrl); public abstract CheckoutSession GetCheckoutSession(string sessionId); - public abstract Task ParseEvent(HttpRequest request); + public abstract Task ParseEvent(HttpRequest request, string apiKey); public abstract CheckoutSession ProcessCheckoutCompletedEvent(Event e); - public abstract Customer ProcessCustomerEvent(Event e); + public abstract Subscription ProcessSubscriptionEvent(Event e); } } diff --git a/BillingCore/StripeService.cs b/BillingCore/StripeService.cs index 9c3996e..06472f9 100644 --- a/BillingCore/StripeService.cs +++ b/BillingCore/StripeService.cs @@ -264,7 +264,7 @@ namespace Teknik.BillingCore return ConvertCheckoutSession(session); } - public override async Task ParseEvent(HttpRequest request) + public override async Task ParseEvent(HttpRequest request, string apiKey) { var json = await new StreamReader(request.Body).ReadToEndAsync(); @@ -273,7 +273,7 @@ namespace Teknik.BillingCore var stripeEvent = EventUtility.ConstructEvent( json, request.Headers["Stripe-Signature"], - Config.StripeWebhookSecret + apiKey ); return ConvertEvent(stripeEvent); @@ -292,12 +292,12 @@ namespace Teknik.BillingCore return ConvertCheckoutSession(session); } - public override Models.Customer ProcessCustomerEvent(Models.Event ev) + public override Models.Subscription ProcessSubscriptionEvent(Models.Event ev) { // Handle the checkout.session.completed event - var customer = ev.Data as Stripe.Customer; + var subscription = ev.Data as Stripe.Subscription; - return ConvertCustomer(customer); + return ConvertSubscription(subscription); } public override CheckoutSession GetCheckoutSession(string sessionId) @@ -433,7 +433,7 @@ namespace Teknik.BillingCore return new CheckoutSession() { PaymentIntentId = session.PaymentIntentId, - CustomerId = session.Customer.Id, + CustomerId = session.Customer?.Id ?? session.CustomerId, SubscriptionId = session.SubscriptionId, PaymentStatus = paymentStatus, Url = session.Url diff --git a/Configuration/BillingConfig.cs b/Configuration/BillingConfig.cs index f5c9eb9..67c3ce7 100644 --- a/Configuration/BillingConfig.cs +++ b/Configuration/BillingConfig.cs @@ -11,7 +11,8 @@ namespace Teknik.Configuration public BillingType Type { get; set; } public string StripePublishApiKey { get; set; } public string StripeSecretApiKey { get; set; } - public string StripeWebhookSecret { get; set; } + public string StripeCheckoutWebhookSecret { get; set; } + public string StripeCustomerWebhookSecret { get; set; } public string UploadProductId { get; set; } public string EmailProductId { get; set; } @@ -21,7 +22,8 @@ namespace Teknik.Configuration Type = BillingType.Stripe; StripePublishApiKey = null; StripeSecretApiKey = null; - StripeWebhookSecret = null; + StripeCheckoutWebhookSecret = null; + StripeCustomerWebhookSecret = null; } } } diff --git a/Teknik/App_Data/endpointMappings.json b/Teknik/App_Data/endpointMappings.json index 60c32c5..db8a90e 100644 --- a/Teknik/App_Data/endpointMappings.json +++ b/Teknik/App_Data/endpointMappings.json @@ -132,6 +132,28 @@ "action": "Dashboard" } }, + { + "Name": "API.v1.HandleCheckoutComplete", + "HostTypes": [ "Full" ], + "SubDomains": [ "api" ], + "Pattern": "v1/Billing/HandleCheckoutComplete", + "Area": "API", + "Defaults": { + "controller": "BillingAPIv1", + "action": "HandleCheckoutCompleteEvent" + } + }, + { + "Name": "API.v1.HandleSubscriptionChange", + "HostTypes": [ "Full" ], + "SubDomains": [ "api" ], + "Pattern": "v1/Billing/HandleSubscriptionChange", + "Area": "API", + "Defaults": { + "controller": "BillingAPIv1", + "action": "HandleSubscriptionChange" + } + }, { "Name": "API.v1.Claims", "HostTypes": [ "Full" ], diff --git a/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs index a78d8bd..6eed219 100644 --- a/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs +++ b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Teknik.Areas.Billing; using Teknik.Areas.Users.Models; using Teknik.Areas.Users.Utility; using Teknik.BillingCore; @@ -24,7 +25,7 @@ namespace Teknik.Areas.API.V1.Controllers { var billingService = BillingFactory.GetBillingService(_config.BillingConfig); - var billingEvent = await billingService.ParseEvent(Request); + var billingEvent = await billingService.ParseEvent(Request, _config.BillingConfig.StripeCheckoutWebhookSecret); if (billingEvent == null) return BadRequest(); @@ -34,7 +35,7 @@ namespace Teknik.Areas.API.V1.Controllers { var subscription = billingService.GetSubscription(session.SubscriptionId); - ProcessSubscription(session.CustomerId, subscription); + BillingHelper.ProcessSubscription(_config, _dbContext, session.CustomerId, subscription); } return Ok(); @@ -44,61 +45,18 @@ namespace Teknik.Areas.API.V1.Controllers { var billingService = BillingFactory.GetBillingService(_config.BillingConfig); - var billingEvent = await billingService.ParseEvent(Request); + var billingEvent = await billingService.ParseEvent(Request, _config.BillingConfig.StripeCustomerWebhookSecret); if (billingEvent == null) return BadRequest(); - var customerEvent = billingService.ProcessCustomerEvent(billingEvent); - - foreach (var subscription in customerEvent.Subscriptions) - { - ProcessSubscription(customerEvent.CustomerId, subscription); - } + var subscriptionEvent = billingService.ProcessSubscriptionEvent(billingEvent); + if (subscriptionEvent == null) + return BadRequest(); + + BillingHelper.ProcessSubscription(_config, _dbContext, subscriptionEvent.CustomerId, subscriptionEvent); return Ok(); } - - private void ProcessSubscription(string customerId, Subscription subscription) - { - // They have paid, so let's get their subscription and update their user settings - var user = _dbContext.Users.FirstOrDefault(u => u.BillingCustomer != null && - u.BillingCustomer.CustomerId == customerId); - if (user != null) - { - var isActive = subscription.Status == SubscriptionStatus.Active; - foreach (var price in subscription.Prices) - { - ProcessPrice(user, price, isActive); - } - } - } - - private void ProcessPrice(User user, Price price, bool active) - { - // What type of subscription is this - if (_config.BillingConfig.UploadProductId == price.ProductId) - { - // Process Upload Settings - user.UploadSettings.MaxUploadStorage = active ? price.Storage : _config.UploadConfig.MaxStorage; - user.UploadSettings.MaxUploadFileSize = active ? price.FileSize : _config.UploadConfig.MaxUploadFileSize; - _dbContext.Entry(user).State = EntityState.Modified; - _dbContext.SaveChanges(); - } - else if (_config.BillingConfig.EmailProductId == price.ProductId) - { - // Process an email subscription - string email = UserHelper.GetUserEmailAddress(_config, user.Username); - if (active) - { - UserHelper.EnableUserEmail(_config, email); - UserHelper.EditUserEmailMaxSize(_config, email, (int)price.Storage); - } - else - { - UserHelper.DisableUserEmail(_config, email); - } - } - } } } diff --git a/Teknik/Areas/Billing/BillingHelper.cs b/Teknik/Areas/Billing/BillingHelper.cs new file mode 100644 index 0000000..956f899 --- /dev/null +++ b/Teknik/Areas/Billing/BillingHelper.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.BillingCore; +using Teknik.BillingCore.Models; +using Teknik.Configuration; +using Teknik.Data; + +namespace Teknik.Areas.Billing +{ + public static class BillingHelper + { + public static void ProcessSubscription(Config config, TeknikEntities db, string customerId, Subscription subscription) + { + // They have paid, so let's get their subscription and update their user settings + var user = db.Users.FirstOrDefault(u => u.BillingCustomer != null && + u.BillingCustomer.CustomerId == customerId); + if (user != null) + { + var isActive = subscription.Status == SubscriptionStatus.Active; + foreach (var price in subscription.Prices) + { + ProcessPrice(config, db, user, price, isActive); + } + } + } + + public static void ProcessPrice(Config config, TeknikEntities db, User user, Price price, bool active) + { + // What type of subscription is this + if (config.BillingConfig.UploadProductId == price.ProductId) + { + // Process Upload Settings + user.UploadSettings.MaxUploadStorage = active ? price.Storage : config.UploadConfig.MaxStorage; + user.UploadSettings.MaxUploadFileSize = active ? price.FileSize : config.UploadConfig.MaxUploadFileSize; + db.Entry(user).State = EntityState.Modified; + db.SaveChanges(); + } + else if (config.BillingConfig.EmailProductId == price.ProductId) + { + // Process an email subscription + string email = UserHelper.GetUserEmailAddress(config, user.Username); + if (active) + { + UserHelper.EnableUserEmail(config, email); + UserHelper.EditUserEmailMaxSize(config, email, (int)price.Storage); + } + else + { + UserHelper.DisableUserEmail(config, email); + } + } + } + } +} diff --git a/Utilities/TagHelpers/VersionHelper.cs b/Utilities/TagHelpers/VersionHelper.cs index 2095f4e..7b9027f 100644 --- a/Utilities/TagHelpers/VersionHelper.cs +++ b/Utilities/TagHelpers/VersionHelper.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Razor.TagHelpers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -11,8 +12,14 @@ namespace Teknik.Utilities.TagHelpers { private const string _verFile = "version.json"; + private readonly IWebHostEnvironment _env; public string Source { get; set; } + public VersionHelper(IWebHostEnvironment env) + { + _env = env; + } + public override void Process(TagHelperContext context, TagHelperOutput output) { // Clear the initial wrap tag @@ -32,7 +39,7 @@ namespace Teknik.Utilities.TagHelpers string commitVer = res["version"].ToString(); string commitHash = res["hash"].ToString(); - output.Content.AppendHtml($"Version: {commitVer} - Hash: {commitHash.Truncate(10)}"); + output.Content.AppendHtml($"Version: {commitVer} - Hash: {commitHash.Truncate(10)} | {_env.EnvironmentName}"); } } }