From 94271bacd09950edb091dd69c49e60c8f8f47d89 Mon Sep 17 00:00:00 2001 From: "Aidar Shaikhutdin @makeweb.space" Date: Tue, 20 Jun 2023 16:26:11 +0300 Subject: [PATCH] fix: switch to 3D Serure method for card payment --- .../PaymentPage/methods/ApplePay/Button.tsx | 12 ++--- .../PaymentPage/methods/Card/Modal.tsx | 47 +++++++++++-------- .../PaymentPage/methods/GooglePay/Button.tsx | 12 ++--- src/components/PaymentPage/styles.css | 4 ++ 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/components/PaymentPage/methods/ApplePay/Button.tsx b/src/components/PaymentPage/methods/ApplePay/Button.tsx index a6af2b8..1d4d188 100644 --- a/src/components/PaymentPage/methods/ApplePay/Button.tsx +++ b/src/components/PaymentPage/methods/ApplePay/Button.tsx @@ -9,6 +9,7 @@ import ErrorText from '../../../ErrorText' const currencyCode = 'USD' const paymentMethod = 'apple_pay' +const buttonId = 'apple-pay-btn' interface ApplePayButtonProps { onSuccess: (receipt: SubscriptionReceipts.SubscriptionReceipt) => void @@ -17,7 +18,6 @@ interface ApplePayButtonProps { export function ApplePayButton({ onSuccess, onError }: ApplePayButtonProps): JSX.Element { const api = useApi() - const buttonId = 'apple-pay-btn' const { i18n } = useTranslation() const { token } = useAuth() const { applePay } = usePayment() @@ -48,14 +48,12 @@ export function ApplePayButton({ onSuccess, onError }: ApplePayButtonProps): JSX }) .then(({ subscription_receipt }: SubscriptionReceipts.Response) => onSuccess(subscription_receipt)) .catch((error: Error) => onError(error)) - }, [data, applePay, buttonId, i18n.language, api, token, onSuccess, onError]) + }, [data, applePay, i18n.language, api, token, onSuccess, onError]) return ( - <> +
{isPending || isMounting ? : null} -
- {error ? : null} -
- + {error ? : null} +
) } diff --git a/src/components/PaymentPage/methods/Card/Modal.tsx b/src/components/PaymentPage/methods/Card/Modal.tsx index cc27d36..a4001ce 100644 --- a/src/components/PaymentPage/methods/Card/Modal.tsx +++ b/src/components/PaymentPage/methods/Card/Modal.tsx @@ -1,11 +1,12 @@ -import { useEffect, useRef, useState, ChangeEvent } from 'react' +import { useCallback, useEffect, useRef, useState, ChangeEvent } from 'react' import { useTranslation } from 'react-i18next' import { CardCVV, CardComponent, CardExpiry, CardNumber, Provider } from '@chargebee/chargebee-js-react-wrapper' import ChargebeeComponents from '@chargebee/chargebee-js-react-wrapper/dist/components/ComponentGroup' +import { PaymentIntent } from '@chargebee/chargebee-js-types' import { usePayment } from '../../../../payment' -import { useApi, SubscriptionReceipts } from '../../../../api' +import { useApi, SubscriptionReceipts, useApiCall } from '../../../../api' import { useAuth } from '../../../../auth' import Modal from '../../../Modal' import Title from '../../../Title' @@ -35,11 +36,6 @@ interface Field { type: string } -interface ChargebeeTokenizeResult { - token: string - vaultToken: string -} - type Status = 'idle' | 'loading' | 'filling' | 'subscribing' | 'ready' | 'success' | 'error' const initCompletedFields = { @@ -52,6 +48,8 @@ type CompletedFields = typeof initCompletedFields const isReady = (fields: CompletedFields) => Object.values(fields).every((complete: boolean) => complete) +const currencyCode = 'USD' +const paymentMethod = 'card' const itemPriceId = 'aura-membership-2-week-USD' export function CardModal({ open, onClose, onSuccess, onError }: CardModalProps): JSX.Element { @@ -59,12 +57,17 @@ export function CardModal({ open, onClose, onSuccess, onError }: CardModalProps) const cardRef = useRef(null) const [status, setStatus] = useState('idle') const [fields, setFields] = useState(initCompletedFields) - const { token } = useAuth() + const { token, user } = useAuth() const { t, i18n } = useTranslation() const locale = i18n.language const isInit = status === 'idle' const isLoading = status === 'loading' const { chargebee } = usePayment() + const loadData = useCallback(() => { + return api.createPaymentIntent({ token, paymentMethod, currencyCode }) + .then(({ payment_intent }) => payment_intent) + }, [api, token]) + const { data, error } = useApiCall(loadData) const handleReady = () => setStatus('filling') const handleClose = () => { setStatus('loading') @@ -74,10 +77,11 @@ export function CardModal({ open, onClose, onSuccess, onError }: CardModalProps) setFields((state) => ({ ...state, [field]: complete && !error })) } const payWithCard = () => { + if (data === null) return setStatus('subscribing') - cardRef.current?.tokenize({}) - .then((result: ChargebeeTokenizeResult) => { - return api.createSubscriptionReceipt({ token, itemPriceId, gwToken: result.vaultToken }) + cardRef.current?.authorizeWith3ds(data, { email: user?.email }, {}) + .then((paymentIntent: PaymentIntent) => { + return api.createSubscriptionReceipt({ token, itemPriceId, gwToken: paymentIntent.id }) }) .then(({ subscription_receipt }: SubscriptionReceipts.Response) => { setStatus('success') @@ -89,6 +93,8 @@ export function CardModal({ open, onClose, onSuccess, onError }: CardModalProps) }) } + if (error) console.error(error) + useEffect(() => { if (status !== 'filling' && status !== 'ready' && status !== 'error') return setStatus(isReady(fields) ? 'ready' : 'filling') @@ -123,17 +129,18 @@ export function CardModal({ open, onClose, onSuccess, onError }: CardModalProps)

{t('charged_only')}

- { status === 'subscribing' ? : ( - <> - - - - - {t('start_trial')} - - ) } + { status === 'subscribing' ? : <>{t('start_trial')} } ) } + +function LockIcon(): JSX.Element { + return ( + + + + + ) +} diff --git a/src/components/PaymentPage/methods/GooglePay/Button.tsx b/src/components/PaymentPage/methods/GooglePay/Button.tsx index 2931e7b..64cda19 100644 --- a/src/components/PaymentPage/methods/GooglePay/Button.tsx +++ b/src/components/PaymentPage/methods/GooglePay/Button.tsx @@ -9,6 +9,7 @@ import ErrorText from '../../../ErrorText' const currencyCode = 'USD' const paymentMethod = 'google_pay' +const buttonId = 'google-pay-btn' interface GooglePayButtonProps { onSuccess: (receipt: SubscriptionReceipts.SubscriptionReceipt) => void @@ -17,7 +18,6 @@ interface GooglePayButtonProps { export function GooglePayButton({ onSuccess, onError }: GooglePayButtonProps): JSX.Element { const api = useApi() - const buttonId = 'google-pay-btn' const { i18n } = useTranslation() const { token } = useAuth() const { googlePay } = usePayment() @@ -48,14 +48,12 @@ export function GooglePayButton({ onSuccess, onError }: GooglePayButtonProps): J }) .then(() => onSuccess({} as SubscriptionReceipts.SubscriptionReceipt)) .catch((error: Error) => onError(error)) - }, [data, googlePay, buttonId, i18n.language, onSuccess, onError]) + }, [data, googlePay, i18n.language, onSuccess, onError]) return ( - <> +
{isPending || isMounting ? : null} -
- {error ? : null} -
- + {error ? : null} +
) } diff --git a/src/components/PaymentPage/styles.css b/src/components/PaymentPage/styles.css index 5eb33d9..c6c06bb 100644 --- a/src/components/PaymentPage/styles.css +++ b/src/components/PaymentPage/styles.css @@ -101,6 +101,10 @@ } .pay-btn { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; height: 60px; max-width: 400px; min-width: 250px;