From 27544d726a3a1e7ccd588b33e773ec04424f9082 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Mar 2024 12:00:19 -0600 Subject: [PATCH] Website: update Stripe webhook to handle changes to subscriptions made in the Stripe UI. (#17225) Closes: #16945 Changes: - Updated the receive-from-stripe` webhook to update the database records of subscriptions that have been changed in the Stripe UI. --- .../webhooks/receive-from-stripe.js | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/website/api/controllers/webhooks/receive-from-stripe.js b/website/api/controllers/webhooks/receive-from-stripe.js index 0af2abd168..394c17851e 100644 --- a/website/api/controllers/webhooks/receive-from-stripe.js +++ b/website/api/controllers/webhooks/receive-from-stripe.js @@ -95,7 +95,9 @@ module.exports = { } let userForThisSubscription = subscriptionForThisEvent.user; - + // ┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌┐┌┌─┐ ┬─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬ + // │ │├─┘│ │ │││││││││ ┬ ├┬┘├┤ │││├┤ │││├─┤│ + // └─┘┴ └─┘└─┘┴ ┴┴┘└┘└─┘ ┴└─└─┘┘└┘└─┘└┴┘┴ ┴┴─┘ // If stripe thinks this subscription renews in 7 days, we'll send the user an subscription reminder email. if(type === 'invoice.upcoming' && stripeEventData.billing_reason === 'upcoming') { // Get the subscription cost per host for the Subscription renewal notification email. @@ -117,7 +119,9 @@ module.exports = { nextBillingAt: upcomingBillingAt, } }); - + // ┌─┐┬ ┬┌┐ ┌─┐┌─┐┬─┐┬┌─┐┌┬┐┬┌─┐┌┐┌ ┬─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┌┬┐ + // └─┐│ │├┴┐└─┐│ ├┬┘│├─┘ │ ││ ││││ ├┬┘├┤ │││├┤ │││├┤ ││ + // └─┘└─┘└─┘└─┘└─┘┴└─┴┴ ┴ ┴└─┘┘└┘ ┴└─└─┘┘└┘└─┘└┴┘└─┘─┴┘ } else if(type === 'invoice.paid' && stripeEventData.billing_reason === 'subscription_cycle') { // If the event was triggered by a user's card successfully being charged by Stripe, we'll generate a new license key, update the subscription's database record, and send the user a renewal confirmation email. @@ -156,7 +160,46 @@ module.exports = { lastName: userForThisSubscription.lastName, } }); + // ┬┌┐┌┬ ┬┌─┐┬┌─┐┌─┐ ┌─┐┌─┐┬─┐ ┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐ ┌─┐┬ ┬┌┐ ┌─┐┌─┐┬─┐┬┌┬┐┌─┐┌┐┌ ┌─┐┌─┐┬┌┬┐ + // ││││└┐┌┘│ │││ ├┤ ├┤ │ │├┬┘ │ │├─┘ ││├─┤ │ ├┤ ││ └─┐│ │├┴┐└─┐│ ├┬┘│ │ │ ││││ ├─┘├─┤│ ││ + // ┴┘└┘ └┘ └─┘┴└─┘└─┘ └ └─┘┴└─ └─┘┴ ─┴┘┴ ┴ ┴ └─┘─┴┘ └─┘└─┘└─┘└─┘└─┘┴└─┴ ┴ └─┘┘└┘ ┴ ┴ ┴┴─┴┘ + } else if (type === 'invoice.paid' && stripeEventData.billing_reason === 'subscription_update') { + // If the event was triggered by a customer paying an invoice that was sent to them after their subscription was updated, we'll generate a new license key with their updated informaton. + // Get the information about the paid invoice from the stripe event. + let itemsOnThisInvoice = stripeEventData.lines.data; + + // Find the line item in the new invoice that contains the new information about this subscription. + let updatedSubscriptionInfo = _.find(itemsOnThisInvoice, (item)=>{ + // Invoices for updated subscriptions list the new number of hosts with the remaining subscription period + // e.g., 'Remaining time on 9 × Fleet premium hosts after 17 Feb 2024' + return _.startsWith(item.description, 'Remaining'); + }); + // Convert the subscription cycle's period end timestamp from Stripe into a JS timestamp. + // Note: with most subscription changes, this value will be indentical to the existing license key's expiration + // timestamp. We do this here to handle situations where the subscription period has been adjusted in the Stripe UI. + let nextBillingAt = updatedSubscriptionInfo.period.end * 1000; + + // Use information from the nested plan object to determine the new price of this subscription. + let pricePerHost = updatedSubscriptionInfo.plan.amount / 100; + + // Get the updated number of hosts from the quantity of the invoice. + let newNumberOfHosts = updatedSubscriptionInfo.quantity; + + // Generate a new license key for this subscription + let newLicenseKeyForThisSubscription = await sails.helpers.createLicenseKey.with({ + numberOfHosts: newNumberOfHosts, + organization: subscriptionForThisEvent.user.organization, + expiresAt: nextBillingAt, + }); + + // Update the subscription record + await Subscription.updateOne({id: subscriptionForThisEvent.id}).set({ + numberOfHosts: newNumberOfHosts, + subscriptionPrice: Math.floor(pricePerHost * newNumberOfHosts), + fleetLicenseKey: newLicenseKeyForThisSubscription, + nextBillingAt: nextBillingAt + }); } // FUTURE: send emails about failed payments. (type === 'invoice.payment_failed' && stripeEventData.billing_reason === 'subscription_cycle')