Merge pull request #23395 from overleaf/ls-display-discount-in-cost-summary

Display discount information in cost summary

GitOrigin-RevId: 95ff56b21b15e55860968e8ce4519c897b85ebba
This commit is contained in:
Liangjun Song
2025-02-06 12:30:20 +00:00
committed by Copybot
parent e0e3786ce7
commit e2062b4218
9 changed files with 110 additions and 28 deletions
@@ -366,6 +366,8 @@ function computeImmediateCharge(subscriptionChange) {
subscriptionChange.invoiceCollection?.chargeInvoice?.subtotal ?? 0
let tax = subscriptionChange.invoiceCollection?.chargeInvoice?.tax ?? 0
let total = subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0
let discount =
subscriptionChange.invoiceCollection?.chargeInvoice?.discount ?? 0
for (const creditInvoice of subscriptionChange.invoiceCollection
?.creditInvoices ?? []) {
// The credit invoice numbers are already negative
@@ -373,12 +375,13 @@ function computeImmediateCharge(subscriptionChange) {
total = roundToTwoDecimal(total + (creditInvoice.total ?? 0))
// Tax rate can be different in credit invoice if a user relocates
tax = roundToTwoDecimal(tax + (creditInvoice.tax ?? 0))
discount = roundToTwoDecimal(discount + (creditInvoice.discount ?? 0))
}
return new RecurlyImmediateCharge({
subtotal,
total,
tax,
discount,
})
}
@@ -341,7 +341,7 @@ class RecurlySubscriptionChange {
this.nextAddOns = props.nextAddOns
this.immediateCharge =
props.immediateCharge ??
new RecurlyImmediateCharge({ subtotal: 0, tax: 0, total: 0 })
new RecurlyImmediateCharge({ subtotal: 0, tax: 0, total: 0, discount: 0 })
this.subtotal = this.nextPlanPrice
for (const addOn of this.nextAddOns) {
@@ -386,11 +386,13 @@ class RecurlyImmediateCharge {
* @param {number} props.subtotal
* @param {number} props.tax
* @param {number} props.total
* @param {number} props.discount
*/
constructor(props) {
this.subtotal = props.subtotal
this.tax = props.tax
this.total = props.total
this.discount = props.discount
}
}
@@ -387,6 +387,7 @@
"disable_stop_on_first_error": "",
"disabling": "",
"disconnected": "",
"discount": "",
"discount_of": "",
"dismiss_error_popup": "",
"display_deleted_user": "",
@@ -74,6 +74,19 @@ function CostSummary({ subscriptionChange, totalLicenses }: CostSummaryProps) {
)}
</span>
</ListGroup.Item>
{subscriptionChange.immediateCharge.discount !== 0 && (
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
<span className="me-auto">{t('discount')}</span>
<span data-testid="discount">
(
{formatCurrency(
subscriptionChange.immediateCharge.discount,
subscriptionChange.currency
)}
)
</span>
</ListGroup.Item>
)}
<ListGroup.Item
className="bg-transparent border-0 px-0 gap-3 card-description-secondary"
data-testid="tax"
@@ -134,6 +147,8 @@ function CostSummary({ subscriptionChange, totalLicenses }: CostSummaryProps) {
),
}
)}
{subscriptionChange.immediateCharge.discount !== 0 &&
` ${t('coupons_not_included')}.`}
</div>
</>
)}
@@ -45,6 +45,19 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
)}
</span>
</ListGroup.Item>
{subscriptionChange.immediateCharge.discount !== 0 && (
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
<span className="me-auto">{t('discount')}</span>
<span data-testid="discount">
(
{formatCurrency(
subscriptionChange.immediateCharge.discount,
subscriptionChange.currency
)}
)
</span>
</ListGroup.Item>
)}
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
<span className="me-auto">{t('sales_tax')}</span>
<span data-testid="tax">
@@ -90,6 +103,8 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
date: formatTime(subscriptionChange.nextInvoice.date, 'MMMM D'),
}
)}
{subscriptionChange.immediateCharge.discount !== 0 &&
` ${t('coupons_not_included')}.`}
</div>
</Card.Body>
</Card>
+1
View File
@@ -508,6 +508,7 @@
"disable_stop_on_first_error": "Disable “Stop on first error”",
"disabling": "Disabling",
"disconnected": "Disconnected",
"discount": "Discount",
"discount_of": "Discount of __amount__",
"discover_latex_templates_and_examples": "Discover LaTeX templates and examples to help with everything from writing a journal article to using a specific LaTeX package.",
"discover_why_people_worldwide_trust_overleaf": "Discover why __count__ million people worldwide trust Overleaf with their work.",
@@ -2,7 +2,6 @@ import '../../../helpers/bootstrap-5'
import AddSeats, {
MAX_NUMBER_OF_USERS,
} from '@/features/group-management/components/add-seats/add-seats'
import { SplitTestProvider } from '@/shared/context/split-test-context'
describe('<AddSeats />', function () {
beforeEach(function () {
@@ -15,11 +14,7 @@ describe('<AddSeats />', function () {
win.metaAttributesCache.set('ol-isProfessional', false)
})
cy.mount(
<SplitTestProvider>
<AddSeats />
</SplitTestProvider>
)
cy.mount(<AddSeats />)
cy.findByRole('button', { name: /add users/i })
cy.findByTestId('add-more-users-group-form')
@@ -88,11 +83,7 @@ describe('<AddSeats />', function () {
win.metaAttributesCache.set('ol-isProfessional', true)
})
cy.mount(
<SplitTestProvider>
<AddSeats />
</SplitTestProvider>
)
cy.mount(<AddSeats />)
cy.findByRole('link', { name: /upgrade my plan/i }).should('not.exist')
})
@@ -216,12 +207,6 @@ describe('<AddSeats />', function () {
describe('entered less than the maximum allowed number of users', function () {
beforeEach(function () {
this.adding = 1
cy.findByRole('button', { name: /add users/i }).as('addUsersBtn')
cy.findByRole('button', { name: /send request/i }).should('not.exist')
})
it('renders the preview data', function () {
this.body = {
change: {
type: 'add-on-update',
@@ -236,6 +221,7 @@ describe('<AddSeats />', function () {
subtotal: 100,
tax: 20,
total: 120,
discount: 0,
},
nextInvoice: {
date: '2025-12-01T00:00:00.000Z',
@@ -252,6 +238,11 @@ describe('<AddSeats />', function () {
},
}
cy.findByRole('button', { name: /add users/i }).as('addUsersBtn')
cy.findByRole('button', { name: /send request/i }).should('not.exist')
})
it('renders the preview data', function () {
cy.intercept('POST', '/user/subscription/group/add-users/preview', {
statusCode: 200,
body: this.body,
@@ -289,6 +280,8 @@ describe('<AddSeats />', function () {
)
})
cy.findByTestId('discount').should('not.exist')
cy.findByTestId('total').within(() => {
cy.findByText(/total due today/i)
cy.findByTestId('price').should(
@@ -306,6 +299,26 @@ describe('<AddSeats />', function () {
})
})
it('renders the preview data with discount', function () {
this.body.immediateCharge.discount = 50
cy.intercept('POST', '/user/subscription/group/add-users/preview', {
statusCode: 200,
body: this.body,
}).as('addUsersRequest')
cy.get('@input').type(this.adding.toString())
cy.findByTestId('cost-summary').within(() => {
cy.findByTestId('discount').within(() => {
cy.findByText(`($${this.body.immediateCharge.discount}.00)`)
})
cy.findByText(
/This does not include your current discounts, which will be applied automatically before your next payment/i
)
})
})
describe('request', function () {
afterEach(function () {
cy.findByRole('button', { name: /go to subscriptions/i }).should(
@@ -1,8 +1,15 @@
import '../../../helpers/bootstrap-5'
import UpgradeSubscription from '@/features/group-management/components/upgrade-subscription/upgrade-subscription'
import { SplitTestProvider } from '@/shared/context/split-test-context'
import { SubscriptionChangePreview } from '../../../../../types/subscription/subscription-change-preview'
describe('<UpgradeSubscription />', function () {
const resetPreviewAndRemount = (preview: SubscriptionChangePreview) => {
cy.window().then(win => {
win.metaAttributesCache.set('ol-subscriptionChangePreview', preview)
})
cy.mount(<UpgradeSubscription />)
}
beforeEach(function () {
this.totalLicenses = 2
this.preview = {
@@ -11,7 +18,12 @@ describe('<UpgradeSubscription />', function () {
prevPlan: { name: 'Overleaf Standard Group' },
},
currency: 'USD',
immediateCharge: { subtotal: 353.99, tax: 70.8, total: 424.79 },
immediateCharge: {
subtotal: 353.99,
tax: 70.8,
total: 424.79,
discount: 0,
},
paymentMethod: 'Visa **** 1111',
nextPlan: { annual: true },
nextInvoice: {
@@ -35,14 +47,8 @@ describe('<UpgradeSubscription />', function () {
cy.window().then(win => {
win.metaAttributesCache.set('ol-groupName', 'My Awesome Team')
win.metaAttributesCache.set('ol-totalLicenses', this.totalLicenses)
win.metaAttributesCache.set('ol-subscriptionChangePreview', this.preview)
})
cy.mount(
<SplitTestProvider>
<UpgradeSubscription />
</SplitTestProvider>
)
resetPreviewAndRemount(this.preview)
})
it('shows the group name', function () {
@@ -93,6 +99,31 @@ describe('<UpgradeSubscription />', function () {
cy.findByTestId('total').within(() => {
cy.findByText('$424.79')
})
cy.findByTestId('discount').should('not.exist')
})
it('shows subtotal, discount, tax and total price', function () {
resetPreviewAndRemount({
...this.preview,
immediateCharge: {
subtotal: 353.99,
tax: 70.8,
total: 424.79,
discount: 50,
},
})
cy.findByTestId('subtotal').within(() => {
cy.findByText('$353.99')
})
cy.findByTestId('tax').within(() => {
cy.findByText('$70.80')
})
cy.findByTestId('total').within(() => {
cy.findByText('$424.79')
})
cy.findByTestId('discount').within(() => {
cy.findByText('($50.00)')
})
})
it('shows total users', function () {
@@ -9,6 +9,7 @@ export type SubscriptionChangePreview = {
subtotal: number
tax: number
total: number
discount: number
}
nextInvoice: {
date: string