diff --git a/services/web/app/views/subscriptions/new-react.pug b/services/web/app/views/subscriptions/new-react.pug index c4226b145e..e34754b738 100644 --- a/services/web/app/views/subscriptions/new-react.pug +++ b/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 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 28eff1f4ed..c6ea5d245b 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 @@ -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 diff --git a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx b/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx index f3271a6d66..6b20653094 100644 --- a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx +++ b/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) => { setPaymentMethod(e.target.value) } @@ -265,6 +282,13 @@ function CheckoutPanel() { return ( <> + {threeDSecureActionTokenId && ( + + )}
void + onError: (error: RecurlyError) => void +} + +function ThreeDSecure({ actionTokenId, onToken, onError }: ThreeDSecureProps) { const { t } = useTranslation() + const container = useRef(null) + const recurlyContainer = useRef(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 ( -
+
{t('card_must_be_authenticated_by_3dsecure')} -
- {/* {threeDSecureFlowError && <>{threeDSecureFlowError.message}} */} - {/* */} -
+
) }