mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-30 12:24:25 +02:00
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:
committed by
Copybot
parent
532f9b6549
commit
eac4a5cb13
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,22 @@ function PreviewSubscriptionChange() {
|
||||
const location = useLocation()
|
||||
const aiAssistEnabled = useFeatureFlag('overleaf-assist-bundle')
|
||||
|
||||
// Filter out items that cancel each other out (AI assist items with subtotals that sum to 0)
|
||||
const filteredLineItems = preview.immediateCharge.lineItems.filter(
|
||||
(item, index, arr) => {
|
||||
if (!item.isAiAssist) return true
|
||||
|
||||
const isCanceledByAnotherItem = arr.some(
|
||||
(otherItem, otherIndex) =>
|
||||
otherIndex !== index &&
|
||||
otherItem.isAiAssist &&
|
||||
otherItem.subtotal + item.subtotal === 0
|
||||
)
|
||||
|
||||
return !isCanceledByAnotherItem
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (preview.change.type === 'add-on-purchase') {
|
||||
eventTracking.sendMB('preview-subscription-change-view', {
|
||||
@@ -139,17 +155,38 @@ function PreviewSubscriptionChange() {
|
||||
|
||||
<OLCard className="payment-summary-card mt-5">
|
||||
<h3>{t('due_today')}:</h3>
|
||||
<OLRow>
|
||||
<OLCol xs={9}>{changeName}</OLCol>
|
||||
<OLCol xs={3} className="text-end">
|
||||
<strong>
|
||||
{formatCurrency(
|
||||
preview.immediateCharge.subtotal,
|
||||
preview.currency
|
||||
)}
|
||||
</strong>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
{filteredLineItems.length > 1 ? (
|
||||
<>
|
||||
{filteredLineItems.map((item, index) => (
|
||||
<OLRow key={index}>
|
||||
<OLCol xs={9}>
|
||||
{item.subtotal < 0
|
||||
? `Refund: ${item.description}`
|
||||
: item.description}
|
||||
</OLCol>
|
||||
<OLCol xs={3} className="text-end">
|
||||
<strong>
|
||||
{formatCurrency(item.subtotal, preview.currency)}
|
||||
</strong>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<OLRow>
|
||||
<OLCol xs={9}>{changeName}</OLCol>
|
||||
<OLCol xs={3} className="text-end">
|
||||
<strong>
|
||||
{formatCurrency(
|
||||
preview.immediateCharge.subtotal,
|
||||
preview.currency
|
||||
)}
|
||||
</strong>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{preview.immediateCharge.tax > 0 && (
|
||||
<OLRow className="mt-1">
|
||||
|
||||
@@ -11,6 +11,14 @@ export type SubscriptionChangePreview = {
|
||||
tax: number
|
||||
total: number
|
||||
discount: number
|
||||
lineItems: {
|
||||
planCode: string | null | undefined
|
||||
description: string
|
||||
subtotal: number
|
||||
discount: number
|
||||
tax: number
|
||||
isAiAssist: boolean
|
||||
}[]
|
||||
}
|
||||
nextInvoice: {
|
||||
date: string
|
||||
|
||||
Reference in New Issue
Block a user