Просмотр исходного кода

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

Display discount information in cost summary

GitOrigin-RevId: 95ff56b21b15e55860968e8ce4519c897b85ebba
Liangjun Song 1 год назад
Родитель
Сommit
b941439461

+ 4 - 1
services/web/app/src/Features/Subscription/RecurlyClient.js

@@ -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,
   })
 }
 

+ 3 - 1
services/web/app/src/Features/Subscription/RecurlyEntities.js

@@ -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
   }
 }
 

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -387,6 +387,7 @@
   "disable_stop_on_first_error": "",
   "disabling": "",
   "disconnected": "",
+  "discount": "",
   "discount_of": "",
   "dismiss_error_popup": "",
   "display_deleted_user": "",

+ 15 - 0
services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx

@@ -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>
           </>
         )}

+ 15 - 0
services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx

@@ -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 - 0
services/web/locales/en.json

@@ -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.",

+ 30 - 17
services/web/test/frontend/features/group-management/components/add-seats.spec.tsx

@@ -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(

+ 40 - 9
services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx

@@ -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 () {

+ 1 - 0
services/web/types/subscription/subscription-change-preview.ts

@@ -9,6 +9,7 @@ export type SubscriptionChangePreview = {
     subtotal: number
     tax: number
     total: number
+    discount: number
   }
   nextInvoice: {
     date: string