From 8616c5383958518e6013dcf09a7451326d6b0321 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Wed, 1 Mar 2023 11:33:10 +0200 Subject: [PATCH] Merge pull request #12031 from overleaf/ii-payment-page-migration-three-d-secure-currency-change [web] 3DS currency change fix GitOrigin-RevId: e88c773c6576e55803df3b2ec6a6acb1df80e8f2 --- .../components/new/checkout/card-element.tsx | 6 ++++- .../new/checkout/three-d-secure.tsx | 6 ++++- .../components/new/checkout.spec.tsx | 24 +++++------------ .../components/new/common.spec.tsx | 26 +++++++++++++++++++ .../subscription/fixtures/recurly-mock.ts | 4 ++- .../features/subscription/helpers/payment.ts | 12 +++++++++ 6 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 services/web/test/frontend/features/subscription/helpers/payment.ts diff --git a/services/web/frontend/js/features/subscription/components/new/checkout/card-element.tsx b/services/web/frontend/js/features/subscription/components/new/checkout/card-element.tsx index c6ea5d245b..24d58b1410 100644 --- a/services/web/frontend/js/features/subscription/components/new/checkout/card-element.tsx +++ b/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() - const cardRef = useRef(null) + const cardRef = useRef(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 ( diff --git a/services/web/frontend/js/features/subscription/components/new/checkout/three-d-secure.tsx b/services/web/frontend/js/features/subscription/components/new/checkout/three-d-secure.tsx index 33328f89b2..c349310783 100644 --- a/services/web/frontend/js/features/subscription/components/new/checkout/three-d-secure.tsx +++ b/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(null) - const recurlyContainer = useRef(null) + const recurlyContainer = useRef(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 ( diff --git a/services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx b/services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx index 057334db09..f614f71514 100644 --- a/services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx +++ b/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, diff --git a/services/web/test/frontend/features/subscription/components/new/common.spec.tsx b/services/web/test/frontend/features/subscription/components/new/common.spec.tsx index 0461ca05ea..6728642b9c 100644 --- a/services/web/test/frontend/features/subscription/components/new/common.spec.tsx +++ b/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( + + + + + ) + 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) + }) }) diff --git a/services/web/test/frontend/features/subscription/fixtures/recurly-mock.ts b/services/web/test/frontend/features/subscription/fixtures/recurly-mock.ts index 80f45e18b6..44ad2f18ba 100644 --- a/services/web/test/frontend/features/subscription/fixtures/recurly-mock.ts +++ b/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) } } diff --git a/services/web/test/frontend/features/subscription/helpers/payment.ts b/services/web/test/frontend/features/subscription/helpers/payment.ts new file mode 100644 index 0000000000..e467a13ac2 --- /dev/null +++ b/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') +}