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

Merge pull request #11573 from overleaf/ii-payment-page-migration-three-d-secure

[web] Payment page migration 3DS

GitOrigin-RevId: b1049882c0f9fd17af2d2974e3f59b01af353c8f
ilkin-overleaf 3 лет назад
Родитель
Сommit
f6ddb95ae5

+ 0 - 2
services/web/app/views/subscriptions/new-react.pug

@@ -36,5 +36,3 @@ block content
 	)
 		p !{translate("for_visa_mastercard_and_discover", {}, ['strong', 'strong', 'strong'])}
 		p !{translate("for_american_express", {}, ['strong', 'strong', 'strong'])}
-
-	+studentCheckModal

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

@@ -1,9 +1,9 @@
 import { useState, useEffect, useRef } from 'react'
 import { useTranslation } from 'react-i18next'
 import { FormGroup, ControlLabel } from 'react-bootstrap'
-import classnames from 'classnames'
 import { CardElementChangeState } from '../../../../../../../types/recurly/elements'
 import { ElementsInstance } from 'recurly__recurly-js'
+import classnames from 'classnames'
 
 type CardElementProps = {
   className?: string

+ 24 - 0
services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx

@@ -15,6 +15,7 @@ import CompanyDetails from './company-details'
 import CouponCode from './coupon-code'
 import TosAgreementNotice from './tos-agreement-notice'
 import SubmitButton from './submit-button'
+import ThreeDSecure from './three-d-secure'
 import getMeta from '../../../../../utils/meta'
 import { postJSON } from '../../../../../infrastructure/fetch-json'
 import * as eventTracking from '../../../../../infrastructure/event-tracking'
@@ -215,6 +216,22 @@ function CheckoutPanel() {
     )
   }
 
+  const handleThreeDToken = (token: TokenPayload) => {
+    // on SCA verification success: show payment UI in processing mode and
+    // resubmit the payment with the new token final success or error will be
+    // handled by `completeSubscription`
+    completeSubscription(null, undefined, token)
+    setGenericError('')
+    setThreeDSecureActionTokenId(undefined)
+    setIsProcessing(true)
+  }
+
+  const handleThreeDError = (error: RecurlyError) => {
+    // on SCA verification error: show payment UI with the error message
+    setGenericError(`Error: ${error.message}`)
+    setThreeDSecureActionTokenId(undefined)
+  }
+
   const handlePaymentMethod = (e: React.ChangeEvent<HTMLInputElement>) => {
     setPaymentMethod(e.target.value)
   }
@@ -265,6 +282,13 @@ function CheckoutPanel() {
 
   return (
     <>
+      {threeDSecureActionTokenId && (
+        <ThreeDSecure
+          actionTokenId={threeDSecureActionTokenId}
+          onToken={handleThreeDToken}
+          onError={handleThreeDError}
+        />
+      )}
       <div className={classnames({ hidden: threeDSecureActionTokenId })}>
         <PriceSwitchHeader
           planCode={plan.planCode}

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

@@ -1,18 +1,52 @@
+import { useEffect, useRef } from 'react'
 import { useTranslation } from 'react-i18next'
 import { Alert } from 'react-bootstrap'
+import { RecurlyError, TokenPayload } from 'recurly__recurly-js'
 
-function ThreeDSecure() {
+type ThreeDSecureProps = {
+  actionTokenId: string
+  onToken: (token: TokenPayload) => void
+  onError: (error: RecurlyError) => void
+}
+
+function ThreeDSecure({ actionTokenId, onToken, onError }: ThreeDSecureProps) {
   const { t } = useTranslation()
+  const container = useRef<HTMLDivElement>(null)
+  const recurlyContainer = useRef<HTMLDivElement>(null)
+
+  useEffect(() => {
+    // scroll the UI into view (timeout needed to make sure the element is
+    // visible)
+    const timeout = setTimeout(() => {
+      container.current?.scrollIntoView()
+    }, 0)
+
+    return () => {
+      clearTimeout(timeout)
+    }
+  }, [])
+
+  useEffect(() => {
+    if (!recurly || !recurlyContainer.current) return
+
+    // instanciate and configure Recurly 3DSecure flow
+    const risk = recurly.Risk()
+    const threeDSecure = risk.ThreeDSecure({ actionTokenId })
+
+    threeDSecure.on('token', onToken)
+    threeDSecure.on('error', onError)
+    threeDSecure.attach(recurlyContainer.current)
+  }, [actionTokenId, onToken, onError])
 
   return (
-    <div className="three-d-secure-container--react">
+    <div className="three-d-secure-container--react" ref={container}>
       <Alert bsStyle="info" className="small" aria-live="assertive">
         <strong>{t('card_must_be_authenticated_by_3dsecure')}</strong>
       </Alert>
-      <div className="three-d-secure-recurly-container">
-        {/* {threeDSecureFlowError && <>{threeDSecureFlowError.message}</>} */}
-        {/* <ThreeDSecureAction {...props} /> */}
-      </div>
+      <div
+        className="three-d-secure-recurly-container"
+        ref={recurlyContainer}
+      />
     </div>
   )
 }