Browse Source

Merge pull request #12031 from overleaf/ii-payment-page-migration-three-d-secure-currency-change

[web] 3DS currency change fix

GitOrigin-RevId: e88c773c6576e55803df3b2ec6a6acb1df80e8f2
ilkin-overleaf 3 years ago
parent
commit
8616c53839

+ 5 - 1
services/web/frontend/js/features/subscription/components/new/checkout/card-element.tsx

@@ -15,7 +15,7 @@ function CardElement({ className, elements, onChange }: CardElementProps) {
   const { t } = useTranslation()
   const [showCardElementInvalid, setShowCardElementInvalid] =
     useState<boolean>()
-  const cardRef = useRef<HTMLDivElement>(null)
+  const cardRef = useRef<HTMLDivElement | null>(null)
 
   // Card initialization
   useEffect(() => {
@@ -38,6 +38,10 @@ function CardElement({ className, elements, onChange }: CardElementProps) {
       setShowCardElementInvalid(!state.focus && !state.empty && !state.valid)
       onChange(state)
     })
+
+    return () => {
+      cardRef.current = null
+    }
   }, [elements, onChange])
 
   return (

+ 5 - 1
services/web/frontend/js/features/subscription/components/new/checkout/three-d-secure.tsx

@@ -12,7 +12,7 @@ type ThreeDSecureProps = {
 function ThreeDSecure({ actionTokenId, onToken, onError }: ThreeDSecureProps) {
   const { t } = useTranslation()
   const container = useRef<HTMLDivElement>(null)
-  const recurlyContainer = useRef<HTMLDivElement>(null)
+  const recurlyContainer = useRef<HTMLDivElement | null>(null)
 
   useEffect(() => {
     // scroll the UI into view (timeout needed to make sure the element is
@@ -36,6 +36,10 @@ function ThreeDSecure({ actionTokenId, onToken, onError }: ThreeDSecureProps) {
     threeDSecure.on('token', onToken)
     threeDSecure.on('error', onError)
     threeDSecure.attach(recurlyContainer.current)
+
+    return () => {
+      recurlyContainer.current = null
+    }
   }, [actionTokenId, onToken, onError])
 
   return (

+ 6 - 18
services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx

@@ -6,6 +6,7 @@ import {
   defaultSubscription,
   ElementsBase,
 } from '../../fixtures/recurly-mock'
+import { fillForm } from '../../helpers/payment'
 import { cloneDeep } from 'lodash'
 import { TokenHandler, RecurlyError } from 'recurly__recurly-js'
 
@@ -17,19 +18,6 @@ function CheckoutPanelWithPaymentProvider() {
   )
 }
 
-function fillForm() {
-  cy.findByTestId('test-card-element').within(() => {
-    cy.get('input').each(el => {
-      cy.wrap(el).type('123', { delay: 0 })
-    })
-  })
-  cy.findByLabelText(/first name/i).type('123', { delay: 0 })
-  cy.findByLabelText(/last name/i).type('123', { delay: 0 })
-  cy.findByLabelText('Address').type('123', { delay: 0 })
-  cy.findByLabelText(/postal code/i).type('123', { delay: 0 })
-  cy.findByLabelText(/country/i).select('Bulgaria')
-}
-
 describe('checkout panel', function () {
   const itmCampaign = 'fake_itm_campaign'
   const itmContent = 'fake_itm_content'
@@ -158,10 +146,10 @@ describe('checkout panel', function () {
       expect(win.recurly.token).to.be.calledOnceWith(
         Cypress.sinon.match.instanceOf(ElementsBase),
         {
-          first_name: '123',
-          last_name: '123',
-          postal_code: '123',
-          address1: '123',
+          first_name: '1',
+          last_name: '1',
+          postal_code: '1',
+          address1: '1',
           address2: '',
           state: '',
           city: '',
@@ -330,7 +318,7 @@ describe('checkout panel', function () {
     })
   })
 
-  describe('3D challenge', function () {
+  describe('3DS challenge', function () {
     it('shows three d secure challenge', function () {
       cy.intercept('POST', 'user/subscription/create', {
         statusCode: 404,

+ 26 - 0
services/web/test/frontend/features/subscription/components/new/common.spec.tsx

@@ -4,6 +4,9 @@ import {
 } from '../../fixtures/recurly-mock'
 import { PaymentProvider } from '../../../../../../frontend/js/features/subscription/context/payment-context'
 import { plans } from '../../fixtures/plans'
+import PaymentPreviewPanel from '../../../../../../frontend/js/features/subscription/components/new/payment-preview/payment-preview-panel'
+import CheckoutPanel from '../../../../../../frontend/js/features/subscription/components/new/checkout/checkout-panel'
+import { fillForm } from '../../helpers/payment'
 
 describe('common recurly validations', function () {
   beforeEach(function () {
@@ -39,4 +42,27 @@ describe('common recurly validations', function () {
       expect(win.recurly.Pricing.Subscription).to.be.calledOnce
     })
   })
+
+  it('shows three d secure challenge content only once when changing currency', function () {
+    cy.intercept('POST', 'user/subscription/create', {
+      statusCode: 404,
+      body: {
+        threeDSecureActionTokenId: '123',
+      },
+    })
+
+    cy.mount(
+      <PaymentProvider publicKey="0000">
+        <PaymentPreviewPanel />
+        <CheckoutPanel />
+      </PaymentProvider>
+    )
+    cy.findByTestId('checkout-form').within(() => fillForm())
+    cy.findByRole('button', { name: /upgrade now/i }).click()
+    cy.findByRole('button', { name: /change currency/i }).click()
+    cy.findByRole('menu').within(() => {
+      cy.findByRole('menuitem', { name: /gbp/i }).click()
+    })
+    cy.findAllByText('3D challenge content').should('have.length', 1)
+  })
 })

+ 3 - 1
services/web/test/frontend/features/subscription/fixtures/recurly-mock.ts

@@ -154,7 +154,9 @@ export class ThreeDSecureBase {
   on(_eventName = 'change', _callback: () => void) {}
 
   attach(el: HTMLElement) {
-    el.textContent = '3D challenge content'
+    const div = document.createElement('div')
+    div.appendChild(document.createTextNode('3D challenge content'))
+    el.appendChild(div)
   }
 }
 

+ 12 - 0
services/web/test/frontend/features/subscription/helpers/payment.ts

@@ -0,0 +1,12 @@
+export function fillForm() {
+  cy.findByTestId('test-card-element').within(() => {
+    cy.get('input').each(el => {
+      cy.wrap(el).type('1', { delay: 0 })
+    })
+  })
+  cy.findByLabelText(/first name/i).type('1', { delay: 0 })
+  cy.findByLabelText(/last name/i).type('1', { delay: 0 })
+  cy.findByLabelText('Address').type('1', { delay: 0 })
+  cy.findByLabelText(/postal code/i).type('1', { delay: 0 })
+  cy.findByLabelText(/country/i).select('Bulgaria')
+}