Show Recurly's line items breakdown in subscription change preview (#27809)

* Show Recurly's line items breakdown in subscription change preview

* fix rounding, filter items that cancel each other out

GitOrigin-RevId: 0f5d71b3917ce8a52ff36608a6ec6280fe7d38ce
This commit is contained in:
Domagoj Kriskovic
2025-08-19 08:05:02 +00:00
committed by Copybot
parent 532f9b6549
commit eac4a5cb13
5 changed files with 101 additions and 12 deletions
@@ -9,7 +9,7 @@ const AI_ADD_ON_CODE = 'assistant'
/**
* Returns whether the given plan code is a standalone AI plan
*
* @param {string} planCode
* @param {string | null | undefined} planCode
* @return {boolean}
*/
function isStandaloneAiAddOnPlanCode(planCode) {
@@ -6,6 +6,16 @@
* @import { AddOn } from '../../../../types/subscription/plan'
*/
/**
* @typedef {object} ImmediateChargeLineItem
* @property {string | null | undefined} planCode
* @property {string} description
* @property {number} subtotal
* @property {number} discount
* @property {number} tax
* @property {boolean} isAiAssist
*/
const OError = require('@overleaf/o-error')
const { DuplicateAddOnError, AddOnNotPresentError } = require('./Errors')
const PlansLocator = require('./PlansLocator')
@@ -539,12 +549,14 @@ class PaymentProviderImmediateCharge {
* @param {number} props.tax
* @param {number} props.total
* @param {number} props.discount
* @param {ImmediateChargeLineItem[]} [props.lineItems]
*/
constructor(props) {
this.subtotal = props.subtotal
this.tax = props.tax
this.total = props.total
this.discount = props.discount
this.lineItems = props.lineItems ?? []
}
}
@@ -23,6 +23,7 @@ const {
SubtotalLimitExceededError,
} = require('./Errors')
const RecurlyMetrics = require('./RecurlyMetrics')
const { isStandaloneAiAddOnPlanCode, AI_ADD_ON_CODE } = require('./AiHelper')
/**
* @import { PaymentProviderSubscriptionChangeRequest } from './PaymentProviderEntities'
@@ -585,6 +586,23 @@ function computeImmediateCharge(subscriptionChange) {
let total = subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0
let discount =
subscriptionChange.invoiceCollection?.chargeInvoice?.discount ?? 0
const lineItems = []
for (const lineItem of subscriptionChange.invoiceCollection?.chargeInvoice
?.lineItems || []) {
lineItems.push({
planCode: lineItem.planCode,
isAiAssist:
lineItem.addOnCode === AI_ADD_ON_CODE ||
isStandaloneAiAddOnPlanCode(lineItem.planCode),
description: lineItem.description ?? '',
subtotal: roundToTwoDecimal(lineItem.subtotal ?? 0),
discount: roundToTwoDecimal(lineItem.discount ?? 0),
tax: roundToTwoDecimal(lineItem.tax ?? 0),
})
}
for (const creditInvoice of subscriptionChange.invoiceCollection
?.creditInvoices ?? []) {
// The credit invoice numbers are already negative
@@ -593,12 +611,26 @@ function computeImmediateCharge(subscriptionChange) {
// Tax rate can be different in credit invoice if a user relocates
tax = roundToTwoDecimal(tax + (creditInvoice.tax ?? 0))
discount = roundToTwoDecimal(discount + (creditInvoice.discount ?? 0))
for (const lineItem of creditInvoice.lineItems || []) {
lineItems.push({
planCode: lineItem.planCode,
isAiAssist:
lineItem.addOnCode === AI_ADD_ON_CODE ||
isStandaloneAiAddOnPlanCode(lineItem.planCode),
description: lineItem.description ?? '',
subtotal: roundToTwoDecimal(lineItem.subtotal ?? 0),
discount: roundToTwoDecimal(lineItem.discount ?? 0),
tax: roundToTwoDecimal(lineItem.tax ?? 0),
})
}
}
return new PaymentProviderImmediateCharge({
subtotal,
total,
tax,
discount,
lineItems,
})
}