Website: Add support for promotional codes on the self-service license dispenser (#26280)

Changes:
- Added the ability to use coupons on the Fleet premium license
dispenser
- Updated the stripe webhook to support coupons on Fleet premium
subscriptions
- Updated the customer dashboard on the website to show decimal places
in subscription/host prices.
This commit is contained in:
Eric 2025-02-11 17:11:53 -06:00 committed by GitHub
parent 606df3f349
commit 01c95370b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 7 deletions

View file

@ -54,6 +54,7 @@ module.exports = {
],
mode: 'subscription',
billing_address_collection: 'required',// eslint-disable-line camelcase
allow_promotion_codes: true,// eslint-disable-line camelcase
tax_id_collection: {// eslint-disable-line camelcase
enabled: true,
required: 'if_supported'

View file

@ -103,7 +103,7 @@ module.exports = {
// 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.
let subscriptionCostPerHost = Math.floor(subscriptionForThisEvent.subscriptionPrice / subscriptionForThisEvent.numberOfHosts / 12);
let subscriptionCostPerHost = (subscriptionForThisEvent.subscriptionPrice / subscriptionForThisEvent.numberOfHosts / 12).toFixed(2);
let upcomingBillingAt = stripeEventData.next_payment_attempt * 1000;
// Send a upcoming subscription renewal email.
await sails.helpers.sendTemplateEmail.with({
@ -188,6 +188,21 @@ module.exports = {
// Get the updated number of hosts from the quantity of the invoice.
let newNumberOfHosts = updatedSubscriptionInfo.quantity;
let subscriptionPrice = Math.floor(pricePerHost * newNumberOfHosts);
// (Optionally) adjust the price of this subscription if a coupon was applied.
if(stripeEventData.discount){
if(stripeEventData.discount.coupon){
if(stripeEventData.discount.coupon.amount_off){
// If the coupon applied takes a fixed dollar amount off of the total price, subtact the amoutn fro mthe subscriptionPrice
subscriptionPrice = _.round(subscriptionPrice - (stripeEventData.discount.coupon.amount_off / 100), 2); // Note: coupon.amount_off contains the discounted amount in cents.
} else if(stripeEventData.discount.coupon.percent_off){
// Otherwise if it is a percent discount,
let discountAmount = subscriptionPrice * (stripeEventData.discount.coupon.percent_off / 100);
subscriptionPrice = _.round(subscriptionPrice - discountAmount, 2);
}
}
}
// Generate a new license key for this subscription
let newLicenseKeyForThisSubscription = await sails.helpers.createLicenseKey.with({
numberOfHosts: newNumberOfHosts,
@ -198,7 +213,7 @@ module.exports = {
// Update the subscription record
await Subscription.updateOne({id: subscriptionForThisEvent.id}).set({
numberOfHosts: newNumberOfHosts,
subscriptionPrice: Math.floor(pricePerHost * newNumberOfHosts),
subscriptionPrice,
fleetLicenseKey: newLicenseKeyForThisSubscription,
nextBillingAt: nextBillingAt
});
@ -214,10 +229,11 @@ module.exports = {
let nextBillingAt = newSubscriptionDetails.current_period_end * 1000;
// Get the number of Hosts.
let numberOfHosts = newSubscriptionDetails.quantity;
// Get the whole dollar price per host.
// Get the whole dollar price per host by subtracting the discount amount from the plan amount.
// [?]: https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-total_details
let subscriptionPricePerHost = newSubscriptionDetails.plan.amount / 100;
// Determine the annual cost of this user's subscription
let subscriptionPrice = subscriptionPricePerHost * numberOfHosts;
let subscriptionPrice = (subscriptionPricePerHost * numberOfHosts) - (stripeEventData.total_details.amount_discount / 100);
// Generate a new license key.
let newLicenseKey = await sails.helpers.createLicenseKey.with({
numberOfHosts,

View file

@ -31,7 +31,7 @@
</div>
<div class="col-12 col-md-6 col-lg-2 pt-3 pt-md-0">
<strong>Cost</strong>
<p>${{thisSubscription.subscriptionPrice}}.00/year</p>
<p>${{thisSubscription.subscriptionPrice.toFixed(2)}}/year</p>
</div>
<div class="col-12 col-md-6 col-lg-2 pt-3 pt-lg-0">
<strong>No. of devices</strong>
@ -89,8 +89,8 @@
<div class="row pb-3 mx-0">
<div style="max-width: 16px;" class="col-1 px-0"><img style="margin-top: 5px; height: 16px; width: 16px;" src="/images/icon-calendar-32x32@2x.png" alt="A calendar icon"></div>
<div class="col pl-3">
<p>{{thisSubscription.numberOfHosts}} devices @ ${{thisSubscription.subscriptionPrice / thisSubscription.numberOfHosts / 12}}.00/device/month</p>
<p>Billed annually at ${{thisSubscription.subscriptionPrice}}.00/yr</p>
<p>{{thisSubscription.numberOfHosts}} devices @ ${{(thisSubscription.subscriptionPrice / thisSubscription.numberOfHosts / 12).toFixed(2)}}/device/month</p>
<p>Billed annually at ${{thisSubscription.subscriptionPrice.toFixed(2)}}/yr</p>
<p v-if="willSubscriptionRenew">Next payment on <js-timestamp :at="thisSubscription.nextBillingAt" always-show-year format="billing"></js-timestamp></p>
<p v-else>Your subscription will expire on <js-timestamp :at="thisSubscription.nextBillingAt" always-show-year format="billing"></js-timestamp></p>
</div>