fix: switch to 3D Serure method for card payment
This commit is contained in:
parent
75358b9362
commit
94271bacd0
@ -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 (
|
||||
<>
|
||||
<div id={buttonId} className='pay-btn'>
|
||||
{isPending || isMounting ? <Loader /> : null}
|
||||
<div id={buttonId} className='pay-btn'>
|
||||
{error ? <ErrorText message={extractErrorMessage(error)} isShown={true} size='large'/> : null}
|
||||
</div>
|
||||
</>
|
||||
{error ? <ErrorText message={extractErrorMessage(error)} isShown={true} size='large'/> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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<ChargebeeComponents>(null)
|
||||
const [status, setStatus] = useState<Status>('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<PaymentIntent>(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)
|
||||
</Provider>
|
||||
<p className='payment-inforamtion'>{t('charged_only')}</p>
|
||||
<MainButton color='blue' onClick={payWithCard} disabled={status !== 'ready'}>
|
||||
{ status === 'subscribing' ? <Loader /> : (
|
||||
<>
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5556 6.24219H1.44444C0.6467 6.24219 0 6.97481 0 7.87855V13.6058C0 14.5096 0.6467 15.2422 1.44444 15.2422H11.5556C12.3533 15.2422 13 14.5096 13 13.6058V7.87855C13 6.97481 12.3533 6.24219 11.5556 6.24219Z"></path>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M6.5 0.242188C4.29086 0.242188 2.5 2.03305 2.5 4.24219V8.24219H10.5V4.24219C10.5 2.03305 8.70914 0.242188 6.5 0.242188ZM6.5 1.24219C4.84315 1.24219 3.5 2.58533 3.5 4.24219V7.24219H9.5V4.24219C9.5 2.58533 8.15685 1.24219 6.5 1.24219Z"></path>
|
||||
</svg>
|
||||
{t('start_trial')}
|
||||
</>
|
||||
) }
|
||||
{ status === 'subscribing' ? <Loader /> : <><LockIcon />{t('start_trial')}</> }
|
||||
</MainButton>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
function LockIcon(): JSX.Element {
|
||||
return (
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5556 6.24219H1.44444C0.6467 6.24219 0 6.97481 0 7.87855V13.6058C0 14.5096 0.6467 15.2422 1.44444 15.2422H11.5556C12.3533 15.2422 13 14.5096 13 13.6058V7.87855C13 6.97481 12.3533 6.24219 11.5556 6.24219Z"></path>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M6.5 0.242188C4.29086 0.242188 2.5 2.03305 2.5 4.24219V8.24219H10.5V4.24219C10.5 2.03305 8.70914 0.242188 6.5 0.242188ZM6.5 1.24219C4.84315 1.24219 3.5 2.58533 3.5 4.24219V7.24219H9.5V4.24219C9.5 2.58533 8.15685 1.24219 6.5 1.24219Z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<div id={buttonId} className='pay-btn'>
|
||||
{isPending || isMounting ? <Loader /> : null}
|
||||
<div id={buttonId} className='pay-btn'>
|
||||
{error ? <ErrorText message={extractErrorMessage(error)} isShown={true} size='large'/> : null}
|
||||
</div>
|
||||
</>
|
||||
{error ? <ErrorText message={extractErrorMessage(error)} isShown={true} size='large'/> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user