diff --git a/services/web/app/src/Features/Subscription/RecurlyClient.js b/services/web/app/src/Features/Subscription/RecurlyClient.js index b7ee96b1c4..b094eac3ab 100644 --- a/services/web/app/src/Features/Subscription/RecurlyClient.js +++ b/services/web/app/src/Features/Subscription/RecurlyClient.js @@ -14,6 +14,7 @@ const { CreditCardPaymentMethod, RecurlyAddOn, RecurlyPlan, + RecurlyImmediateCharge, } = require('./RecurlyEntities') /** @@ -315,21 +316,42 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) { subscriptionAddOnFromApi ) - let immediateCharge = - subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0 - for (const creditInvoice of subscriptionChange.invoiceCollection - ?.creditInvoices ?? []) { - // The credit invoice totals are already negative - immediateCharge += creditInvoice.total ?? 0 - } - return new RecurlySubscriptionChange({ subscription, nextPlanCode: subscriptionChange.plan.code, nextPlanName: subscriptionChange.plan.name, nextPlanPrice: subscriptionChange.unitAmount, nextAddOns, - immediateCharge, + immediateCharge: computeImmediateCharge(subscriptionChange), + }) +} + +/** + * Compute immediate charge based on invoice collection + * + * @param {recurly.SubscriptionChange} subscriptionChange - the subscription change returned from the API + * @return {RecurlyImmediateCharge} + */ +function computeImmediateCharge(subscriptionChange) { + const roundToTwoDecimal = (/** @type {number} */ num) => + Math.round(num * 100) / 100 + let subtotal = + subscriptionChange.invoiceCollection?.chargeInvoice?.subtotal ?? 0 + let tax = subscriptionChange.invoiceCollection?.chargeInvoice?.tax ?? 0 + let total = subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0 + for (const creditInvoice of subscriptionChange.invoiceCollection + ?.creditInvoices ?? []) { + // The credit invoice numbers are already negative + subtotal = roundToTwoDecimal(subtotal + (creditInvoice.subtotal ?? 0)) + total = roundToTwoDecimal(total + (creditInvoice.total ?? 0)) + // Tax rate can be different in credit invoice if a user relocates + tax = roundToTwoDecimal(tax + (creditInvoice.tax ?? 0)) + } + + return new RecurlyImmediateCharge({ + subtotal, + total, + tax, }) } diff --git a/services/web/app/src/Features/Subscription/RecurlyEntities.js b/services/web/app/src/Features/Subscription/RecurlyEntities.js index a80db92321..c04baaee8a 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEntities.js +++ b/services/web/app/src/Features/Subscription/RecurlyEntities.js @@ -252,7 +252,7 @@ class RecurlySubscriptionChange { * @param {string} props.nextPlanName * @param {number} props.nextPlanPrice * @param {RecurlySubscriptionAddOn[]} props.nextAddOns - * @param {number} [props.immediateCharge] + * @param {RecurlyImmediateCharge} [props.immediateCharge] */ constructor(props) { this.subscription = props.subscription @@ -260,7 +260,9 @@ class RecurlySubscriptionChange { this.nextPlanName = props.nextPlanName this.nextPlanPrice = props.nextPlanPrice this.nextAddOns = props.nextAddOns - this.immediateCharge = props.immediateCharge ?? 0 + this.immediateCharge = + props.immediateCharge ?? + new RecurlyImmediateCharge({ subtotal: 0, tax: 0, total: 0 }) this.subtotal = this.nextPlanPrice for (const addOn of this.nextAddOns) { @@ -299,6 +301,20 @@ class CreditCardPaymentMethod { } } +class RecurlyImmediateCharge { + /** + * @param {object} props + * @param {number} props.subtotal + * @param {number} props.tax + * @param {number} props.total + */ + constructor(props) { + this.subtotal = props.subtotal + this.tax = props.tax + this.total = props.total + } +} + /** * An add-on configuration, independent of any subscription */ @@ -350,4 +366,5 @@ module.exports = { RecurlyAddOn, RecurlyPlan, isStandaloneAiAddOnPlanCode, + RecurlyImmediateCharge, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 4f6e3d4287..af5d49a1de 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -915,7 +915,7 @@ function makeChangePreview( return { change: subscriptionChangeDescription, currency: subscription.currency, - immediateCharge: subscriptionChange.immediateCharge, + immediateCharge: { ...subscriptionChange.immediateCharge }, paymentMethod: paymentMethod.toString(), nextPlan: { annual: nextPlan.annual ?? false, @@ -966,6 +966,7 @@ module.exports = { previewAddonPurchase: expressify(previewAddonPurchase), purchaseAddon, removeAddon, + makeChangePreview, promises: { getRecommendedCurrency: _getRecommendedCurrency, getLatamCountryBannerDetails, diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index bcf51e7891..28eb5be9e5 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -65,7 +65,7 @@ function PreviewSubscriptionChange() {