feat: apple pay, google pay buttons and card tokenize
This commit is contained in:
parent
0cf8f2898d
commit
888b081393
@ -1,6 +1,7 @@
|
||||
import routes from "../../routes"
|
||||
import { AuthPayload } from "../types"
|
||||
import { getAuthHeaders } from "../utils"
|
||||
import { PaymentIntent } from '@chargebee/chargebee-js-types'
|
||||
import { AuthPayload } from '../types'
|
||||
import { getAuthHeaders } from '../utils'
|
||||
import routes from '../../routes'
|
||||
|
||||
export interface Payload extends AuthPayload {
|
||||
paymentMethod: PaymentMethod
|
||||
@ -14,18 +15,7 @@ export interface Response {
|
||||
|
||||
export type PaymentMethod = 'apple_pay' | 'google_pay' | 'card'
|
||||
export type CurrencyCode = 'USD'
|
||||
export interface PaymentIntent {
|
||||
id: string
|
||||
status: 'inited' | 'in_progress' | 'authorized' | 'consumed' | 'expired'
|
||||
amount: number
|
||||
gateway_account_id: string
|
||||
payment_method_type: PaymentMethod
|
||||
expires_at: number
|
||||
created_at: number
|
||||
modified_at: number
|
||||
currency_code: CurrencyCode
|
||||
gateway: string
|
||||
}
|
||||
|
||||
export interface Customer {
|
||||
id: string
|
||||
email: string
|
||||
|
||||
@ -9,13 +9,13 @@ export interface GetPayload extends AuthPayload {
|
||||
export interface ChargebeeReceiptPayload extends AuthPayload {
|
||||
itemPriceId: string
|
||||
gwToken: string
|
||||
referenceId: string
|
||||
referenceId?: string
|
||||
}
|
||||
|
||||
export interface AppleReceiptPayload extends AuthPayload {
|
||||
receiptData: string
|
||||
autorenewable: boolean
|
||||
sandbox: boolean
|
||||
autorenewable?: boolean
|
||||
sandbox?: boolean
|
||||
}
|
||||
|
||||
export type Payload = ChargebeeReceiptPayload | AppleReceiptPayload
|
||||
@ -48,7 +48,7 @@ export interface Response {
|
||||
}
|
||||
|
||||
function createRequest({ token, itemPriceId, gwToken, referenceId }: ChargebeeReceiptPayload): Request
|
||||
function createRequest({ token, receiptData, autorenewable, sandbox }: AppleReceiptPayload): Request
|
||||
function createRequest({ token, receiptData, autorenewable = true, sandbox = true }: AppleReceiptPayload): Request
|
||||
function createRequest(payload: Payload): Request
|
||||
function createRequest(payload: Payload): Request {
|
||||
const url = new URL(routes.server.subscriptionReceipts())
|
||||
|
||||
@ -12,7 +12,7 @@ export interface ApiError {
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
errors: AuthError[] | ApiError
|
||||
errors: AuthError[] | ApiError | string[]
|
||||
}
|
||||
|
||||
export type AuthToken = string
|
||||
|
||||
@ -3,13 +3,13 @@ import { useState, useEffect } from "react";
|
||||
interface HookResult<T> {
|
||||
isPending: boolean
|
||||
error: Error | null
|
||||
data: T
|
||||
data: T | null
|
||||
}
|
||||
|
||||
type ApiMethod<T> = () => Promise<T>
|
||||
|
||||
export function useApiCall<T>(apiMethod: ApiMethod<T>): HookResult<T> {
|
||||
const [data, setData] = useState<T>({} as T)
|
||||
const [data, setData] = useState<T | null>(null)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const [isPending, setIsPending] = useState<boolean>(true)
|
||||
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { AuthToken } from "./types"
|
||||
import { ErrorResponse } from "./types"
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
}
|
||||
}
|
||||
|
||||
export function createMethod<P, R>(createRequest: (payload: P) => Request) {
|
||||
return async (payload: P): Promise<R> => {
|
||||
const request = createRequest(payload)
|
||||
@ -9,9 +16,10 @@ export function createMethod<P, R>(createRequest: (payload: P) => Request) {
|
||||
|
||||
if (response.ok) return data
|
||||
|
||||
const error = Array.isArray(data.errors) ? data.errors[0]?.title : data.errors.base[0]
|
||||
const error = Array.isArray(data.errors) ? data.errors[0] : data.errors.base[0]
|
||||
const errorMessage = typeof error === 'object' ? error.title : error
|
||||
|
||||
throw new Error(error)
|
||||
throw new ApiError(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { selectors } from '../../store'
|
||||
@ -12,10 +13,9 @@ import {
|
||||
} from './methods'
|
||||
import UserHeader from '../UserHeader'
|
||||
import Title from '../Title'
|
||||
import Loader from '../Loader'
|
||||
import secure from './secure.png'
|
||||
import './styles.css'
|
||||
import { useState } from 'react'
|
||||
import Loader from '../Loader'
|
||||
|
||||
function PaymentPage(): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" viewBox="0 0 512 210.2" xml:space="preserve">
|
||||
<path fill="#fff" id="XMLID_34_" d="M93.6,27.1C87.6,34.2,78,39.8,68.4,39c-1.2-9.6,3.5-19.8,9-26.1c6-7.3,16.5-12.5,25-12.9
|
||||
C103.4,10,99.5,19.8,93.6,27.1 M102.3,40.9c-13.9-0.8-25.8,7.9-32.4,7.9c-6.7,0-16.8-7.5-27.8-7.3c-14.3,0.2-27.6,8.3-34.9,21.2
|
||||
c-15,25.8-3.9,64,10.6,85c7.1,10.4,15.6,21.8,26.8,21.4c10.6-0.4,14.8-6.9,27.6-6.9c12.9,0,16.6,6.9,27.8,6.7
|
||||
c11.6-0.2,18.9-10.4,26-20.8c8.1-11.8,11.4-23.3,11.6-23.9c-0.2-0.2-22.4-8.7-22.6-34.3c-0.2-21.4,17.5-31.6,18.3-32.2
|
||||
C123.3,42.9,107.7,41.3,102.3,40.9 M182.6,11.9v155.9h24.2v-53.3h33.5c30.6,0,52.1-21,52.1-51.4c0-30.4-21.1-51.2-51.3-51.2H182.6z
|
||||
M206.8,32.3h27.9c21,0,33,11.2,33,30.9c0,19.7-12,31-33.1,31h-27.8V32.3z M336.6,169c15.2,0,29.3-7.7,35.7-19.9h0.5v18.7h22.4V90.2
|
||||
c0-22.5-18-37-45.7-37c-25.7,0-44.7,14.7-45.4,34.9h21.8c1.8-9.6,10.7-15.9,22.9-15.9c14.8,0,23.1,6.9,23.1,19.6v8.6l-30.2,1.8
|
||||
c-28.1,1.7-43.3,13.2-43.3,33.2C298.4,155.6,314.1,169,336.6,169z M343.1,150.5c-12.9,0-21.1-6.2-21.1-15.7c0-9.8,7.9-15.5,23-16.4
|
||||
l26.9-1.7v8.8C371.9,140.1,359.5,150.5,343.1,150.5z M425.1,210.2c23.6,0,34.7-9,44.4-36.3L512,54.7h-24.6l-28.5,92.1h-0.5
|
||||
l-28.5-92.1h-25.3l41,113.5l-2.2,6.9c-3.7,11.7-9.7,16.2-20.4,16.2c-1.9,0-5.6-0.2-7.1-0.4v18.7C417.3,210,423.3,210.2,425.1,210.2z
|
||||
"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,14 +1,55 @@
|
||||
import { useCallback, useEffect, useId } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import MainButton from '../../../MainButton'
|
||||
import { PaymentIntent } from '@chargebee/chargebee-js-types'
|
||||
import { useApi, useApiCall } from '../../../../api'
|
||||
import { usePayment, ApplePayButtonOptions } from '../../../../payment'
|
||||
import { useAuth } from '../../../../auth'
|
||||
import Loader from '../../../Loader'
|
||||
import ErrorText from '../../../ErrorText'
|
||||
import routes from '../../../../routes'
|
||||
import ApplePay from './Apple-Pay.svg'
|
||||
|
||||
const currencyCode = 'USD'
|
||||
const paymentMethod = 'apple_pay'
|
||||
|
||||
export function ApplePayButton(): JSX.Element {
|
||||
const api = useApi()
|
||||
const navigate = useNavigate()
|
||||
const handleClick = () => navigate(routes.client.wallpaper())
|
||||
return (
|
||||
<MainButton onClick={handleClick}>
|
||||
<img className='payment-btn' src={ApplePay} alt='Apple Pay' />
|
||||
</MainButton>
|
||||
const buttonId = useId()
|
||||
const { i18n } = useTranslation()
|
||||
const { token } = useAuth()
|
||||
const { applePay } = usePayment()
|
||||
const loadData = useCallback(() => {
|
||||
return api.createPaymentIntent({ token, paymentMethod, currencyCode })
|
||||
.then(({ payment_intent }) => payment_intent)
|
||||
}, [api, token])
|
||||
const { data, error, isPending } = useApiCall<PaymentIntent>(loadData)
|
||||
|
||||
if (error) console.error(error)
|
||||
|
||||
useEffect(() => {
|
||||
if (data === null) return
|
||||
const buttonOptions: ApplePayButtonOptions = {
|
||||
buttonColor: 'black',
|
||||
buttonType: 'pay',
|
||||
locale: i18n.language
|
||||
}
|
||||
applePay?.setPaymentIntent(data)
|
||||
applePay?.mountPaymentButton(`#${buttonId}`, buttonOptions)
|
||||
.then(() => applePay?.handlePayment())
|
||||
.then((paymentIntent) => {
|
||||
console.log('Success payment by ApplePay', paymentIntent)
|
||||
return api.createSubscriptionReceipt({
|
||||
token, receiptData: paymentIntent.id, autorenewable: true, sandbox: true,
|
||||
})
|
||||
})
|
||||
.then(() => navigate(routes.client.wallpaper()))
|
||||
.catch(console.error)
|
||||
}, [data, applePay, buttonId, navigate, i18n.language, api, token])
|
||||
|
||||
return isPending ? <Loader /> : (
|
||||
<div id={buttonId} style={{ height: 60 }}>{
|
||||
error ? <ErrorText message={error.message} isShown={true} size='large'/> : null
|
||||
}</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect, useRef, useState, ChangeEvent } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
CardCVV, CardComponent, CardExpiry, CardNumber, Provider
|
||||
} from '@chargebee/chargebee-js-react-wrapper'
|
||||
import ChargebeeComponents from '@chargebee/chargebee-js-react-wrapper/dist/components/ComponentGroup'
|
||||
import { usePayment } from '../../../../payment'
|
||||
import { useApi } from '../../../../api'
|
||||
import { useAuth } from '../../../../auth'
|
||||
import Modal from '../../../Modal'
|
||||
import Title from '../../../Title'
|
||||
import MainButton from '../../../MainButton'
|
||||
@ -15,6 +18,7 @@ import amex from './amex.svg'
|
||||
import diners from './diners.svg'
|
||||
import discover from './discover.svg'
|
||||
import { cardStyles } from './styles'
|
||||
import routes from '../../../../routes'
|
||||
|
||||
interface CardModalProps {
|
||||
open: boolean
|
||||
@ -31,6 +35,11 @@ interface Field {
|
||||
type: string
|
||||
}
|
||||
|
||||
interface ChargebeeTokenizeResult {
|
||||
token: string
|
||||
vaultToken: string
|
||||
}
|
||||
|
||||
type Status = 'idle' | 'loading' | 'filling' | 'tokenizing' | 'ready' | 'success' | 'error'
|
||||
|
||||
const initCompletedFields = {
|
||||
@ -43,10 +52,15 @@ type CompletedFields = typeof initCompletedFields
|
||||
|
||||
const isReady = (fields: CompletedFields) => Object.values(fields).every((complete: boolean) => complete)
|
||||
|
||||
const itemPriceId = 'aura-membership-2-week-USD'
|
||||
|
||||
export function CardModal({ open, onClose }: CardModalProps): JSX.Element {
|
||||
const api = useApi()
|
||||
const navigate = useNavigate()
|
||||
const cardRef = useRef<ChargebeeComponents>(null)
|
||||
const [status, setStatus] = useState<Status>('idle')
|
||||
const [fields, setFields] = useState(initCompletedFields)
|
||||
const { token } = useAuth()
|
||||
const { t, i18n } = useTranslation()
|
||||
const locale = i18n.language
|
||||
const isInit = status === 'idle'
|
||||
@ -65,7 +79,11 @@ export function CardModal({ open, onClose }: CardModalProps): JSX.Element {
|
||||
const payWithCard = () => {
|
||||
setStatus('tokenizing')
|
||||
cardRef.current?.tokenize({})
|
||||
.then(console.log)
|
||||
.then((result: ChargebeeTokenizeResult) => {
|
||||
return api.createSubscriptionReceipt({ token, itemPriceId, gwToken: result.token })
|
||||
})
|
||||
.then(() => console.log('Success payment by Card'))
|
||||
.then(() => navigate(routes.client.wallpaper()))
|
||||
.catch(console.error)
|
||||
.finally(() => setStatus('success'))
|
||||
}
|
||||
|
||||
@ -1,14 +1,54 @@
|
||||
import { useCallback, useEffect, useId } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { PaymentIntent } from '@chargebee/chargebee-js-types'
|
||||
import { useApi, useApiCall } from '../../../../api'
|
||||
import { usePayment, GooglePayButtonOptions } from '../../../../payment'
|
||||
import { useAuth } from '../../../../auth'
|
||||
import Loader from '../../../Loader'
|
||||
import ErrorText from '../../../ErrorText'
|
||||
import routes from '../../../../routes'
|
||||
import MainButton from '../../../MainButton'
|
||||
import GooglePay from './G-Pay.svg'
|
||||
|
||||
const currencyCode = 'USD'
|
||||
const paymentMethod = 'google_pay'
|
||||
|
||||
export function GooglePayButton(): JSX.Element {
|
||||
const api = useApi()
|
||||
const navigate = useNavigate()
|
||||
const handleClick = () => navigate(routes.client.wallpaper())
|
||||
return (
|
||||
<MainButton onClick={handleClick}>
|
||||
<img className='payment-btn' src={GooglePay} alt='Google Pay' />
|
||||
</MainButton>
|
||||
const buttonId = useId()
|
||||
const { i18n } = useTranslation()
|
||||
const { token } = useAuth()
|
||||
const { googlePay } = usePayment()
|
||||
const loadData = useCallback(() => {
|
||||
return api.createPaymentIntent({ token, paymentMethod, currencyCode })
|
||||
.then(({ payment_intent }) => payment_intent)
|
||||
}, [api, token])
|
||||
const { data, error, isPending } = useApiCall<PaymentIntent>(loadData)
|
||||
|
||||
if (error) console.error(error)
|
||||
|
||||
useEffect(() => {
|
||||
if (data === null) return
|
||||
const buttonOptions: GooglePayButtonOptions = {
|
||||
buttonColor: 'black',
|
||||
buttonType: 'pay',
|
||||
buttonLocale: i18n.language,
|
||||
buttonSizeMode: 'fill',
|
||||
}
|
||||
googlePay?.setPaymentIntent(data)
|
||||
googlePay?.mountPaymentButton(`#${buttonId}`, buttonOptions)
|
||||
.then(() => googlePay?.handlePayment())
|
||||
.then((result) => {
|
||||
console.log('Success payment by GooglePay', result)
|
||||
// TODO: implement api.createSubscriptionReceipt for GooglePay
|
||||
})
|
||||
.then(() => navigate(routes.client.wallpaper()))
|
||||
.catch(console.error)
|
||||
}, [data, googlePay, buttonId, navigate, i18n.language])
|
||||
|
||||
return isPending ? <Loader /> : (
|
||||
<div id={buttonId} style={{ height: 60 }}>{
|
||||
error ? <ErrorText message={error.message} isShown={true} size='large'/> : null
|
||||
}</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 80 38.1" style="enable-background:new 0 0 80 38.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFF;}
|
||||
.st1{fill:#4285F4;}
|
||||
.st2{fill:#34A853;}
|
||||
.st3{fill:#FBBC04;}
|
||||
.st4{fill:#EA4335;}
|
||||
</style>
|
||||
<path class="st0" d="M37.8,19.7V29h-3V6h7.8c1.9,0,3.7,0.7,5.1,2c1.4,1.2,2.1,3,2.1,4.9c0,1.9-0.7,3.6-2.1,4.9c-1.4,1.3-3.1,2-5.1,2
|
||||
L37.8,19.7L37.8,19.7z M37.8,8.8v8h5c1.1,0,2.2-0.4,2.9-1.2c1.6-1.5,1.6-4,0.1-5.5c0,0-0.1-0.1-0.1-0.1c-0.8-0.8-1.8-1.3-2.9-1.2
|
||||
L37.8,8.8L37.8,8.8z"/>
|
||||
<path class="st0" d="M56.7,12.8c2.2,0,3.9,0.6,5.2,1.8s1.9,2.8,1.9,4.8V29H61v-2.2h-0.1c-1.2,1.8-2.9,2.7-4.9,2.7
|
||||
c-1.7,0-3.2-0.5-4.4-1.5c-1.1-1-1.8-2.4-1.8-3.9c0-1.6,0.6-2.9,1.8-3.9c1.2-1,2.9-1.4,4.9-1.4c1.8,0,3.2,0.3,4.3,1v-0.7
|
||||
c0-1-0.4-2-1.2-2.6c-0.8-0.7-1.8-1.1-2.9-1.1c-1.7,0-3,0.7-3.9,2.1l-2.6-1.6C51.8,13.8,53.9,12.8,56.7,12.8z M52.9,24.2
|
||||
c0,0.8,0.4,1.5,1,1.9c0.7,0.5,1.5,0.8,2.3,0.8c1.2,0,2.4-0.5,3.3-1.4c1-0.9,1.5-2,1.5-3.2c-0.9-0.7-2.2-1.1-3.9-1.1
|
||||
c-1.2,0-2.2,0.3-3,0.9C53.3,22.6,52.9,23.3,52.9,24.2z"/>
|
||||
<path class="st0" d="M80,13.3l-9.9,22.7h-3l3.7-7.9l-6.5-14.7h3.2l4.7,11.3h0.1l4.6-11.3H80z"/>
|
||||
<path class="st1" d="M25.9,17.7c0-0.9-0.1-1.8-0.2-2.7H13.2v5.1h7.1c-0.3,1.6-1.2,3.1-2.6,4v3.3H22C24.5,25.1,25.9,21.7,25.9,17.7z"
|
||||
/>
|
||||
<path class="st2" d="M13.2,30.6c3.6,0,6.6-1.2,8.8-3.2l-4.3-3.3c-1.2,0.8-2.7,1.3-4.5,1.3c-3.4,0-6.4-2.3-7.4-5.5H1.4v3.4
|
||||
C3.7,27.8,8.2,30.6,13.2,30.6z"/>
|
||||
<path class="st3" d="M5.8,19.9c-0.6-1.6-0.6-3.4,0-5.1v-3.4H1.4c-1.9,3.7-1.9,8.1,0,11.9L5.8,19.9z"/>
|
||||
<path class="st4" d="M13.2,9.4c1.9,0,3.7,0.7,5.1,2l0,0l3.8-3.8c-2.4-2.2-5.6-3.5-8.8-3.4c-5,0-9.6,2.8-11.8,7.3l4.4,3.4
|
||||
C6.8,11.7,9.8,9.4,13.2,9.4z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@ -17,11 +17,12 @@ function StaticPage(): JSX.Element {
|
||||
return api.getElement({ type, locale })
|
||||
.then((resp: Element.Response) => resp.data.element)
|
||||
}, [api, typeId, locale])
|
||||
const { data, isPending, error } = useApiCall(loadData)
|
||||
const { data, isPending, error } = useApiCall<Element.Element>(loadData)
|
||||
const content = data ? parse(data.body) : null
|
||||
|
||||
return (
|
||||
<section className='page page-static'>
|
||||
{isPending ? <Loader /> : <div className='page-static__content'>{parse(data?.body)}</div>}
|
||||
{isPending ? <Loader /> : <div className='page-static__content'>{content}</div>}
|
||||
{error && <NotFoundPage />}
|
||||
</section>
|
||||
)
|
||||
|
||||
@ -10,21 +10,22 @@ import UserHeader from '../UserHeader'
|
||||
import CallToAction from '../CallToAction'
|
||||
import routes from '../../routes'
|
||||
|
||||
const itemPriceId = 'aura-membership-2-week-USD'
|
||||
const paymentItems = [
|
||||
{
|
||||
title: 'Per 7-Day Trial For',
|
||||
price: 1.00,
|
||||
description: '2-Week Plan',
|
||||
},
|
||||
]
|
||||
|
||||
function SubscriptionPage(): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const email = useSelector(selectors.selectEmail)
|
||||
const itemPriceId = 'aura-membership-2-week-USD'
|
||||
const itemPrice = useSelector(selectors.selectPlanById(itemPriceId))
|
||||
const currency = Currency.USD
|
||||
const locale = Locale.EN
|
||||
const paymentItems = [
|
||||
{
|
||||
title: 'Per 7-Day Trial For',
|
||||
price: 1.00,
|
||||
description: '2-Week Plan',
|
||||
},
|
||||
]
|
||||
const handleClick = () => navigate(routes.client.paymentMethod())
|
||||
console.log({ itemPrice })
|
||||
return (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useApi, useApiCall, Assets, DailyForecasts } from '../../api'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuth } from '../../auth'
|
||||
import { useApi, useApiCall, Assets, DailyForecasts } from '../../api'
|
||||
import { saveFile, buildFilename } from './utils'
|
||||
import Loader, { LoaderColor } from '../Loader'
|
||||
import './styles.css'
|
||||
@ -14,6 +15,7 @@ interface WallpaperData {
|
||||
|
||||
function WallpaperPage(): JSX.Element {
|
||||
const api = useApi()
|
||||
const { t } = useTranslation()
|
||||
const { user, token } = useAuth()
|
||||
const category = user?.profile.sign?.sign || ''
|
||||
const loadData = useCallback(() => {
|
||||
@ -27,8 +29,8 @@ function WallpaperPage(): JSX.Element {
|
||||
}))
|
||||
}, [api, category, token])
|
||||
const { data, isPending } = useApiCall<WallpaperData>(loadData)
|
||||
const { assets, forecasts } = data
|
||||
const asset = assets?.at(0)
|
||||
const forecasts = data ? data.forecasts : []
|
||||
const asset = data ? data.assets.at(0) : null
|
||||
|
||||
const handleClick = () => asset && saveFile(asset.url, buildFilename(category))
|
||||
|
||||
@ -42,7 +44,7 @@ function WallpaperPage(): JSX.Element {
|
||||
<div className='wallpaper-content'>
|
||||
{isPending ? null : (
|
||||
<>
|
||||
<h1 className='wallpaper-title'>Analysis of personal background</h1>
|
||||
<h1 className='wallpaper-title'>{t('analysis_background')}</h1>
|
||||
{forecasts.map((forecast) => (
|
||||
<div key={forecast.category_name} className='wallpaper-forecast'>
|
||||
<h2 className='wallpaper-subtitle'>{forecast.category}</h2>
|
||||
|
||||
@ -43,5 +43,6 @@ export default {
|
||||
will_be_charged: "You will be charged only <strongText>. We'll email your a reminder before your trial period ends.",
|
||||
trial_price: "$1 for your 7-day trial",
|
||||
start_trial: "Start 7-Day Trial",
|
||||
analysis_background: "Analysis of personal background",
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './PaymentContext'
|
||||
export * from './usePayment'
|
||||
export * from './types'
|
||||
|
||||
@ -1,18 +1,42 @@
|
||||
import { PaymentIntent } from '@chargebee/chargebee-js-types'
|
||||
|
||||
interface Handler {
|
||||
mountPaymentButton: (id: string) => Promise<void>
|
||||
handlePayment: () => Promise<PaymentIntent>
|
||||
setPaymentIntent: (paymentIntent: PaymentIntent) => void
|
||||
}
|
||||
|
||||
export interface GooglePayButtonOptions {
|
||||
buttonColor: 'default' | 'black' | 'white'
|
||||
buttonType: 'long' | 'short' | 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe'
|
||||
buttonSizeMode: 'static' | 'fill'
|
||||
buttonLocale: string
|
||||
}
|
||||
|
||||
export interface PaymentOptions {
|
||||
requestPayerEmail: boolean
|
||||
requestBillingAddress: boolean
|
||||
requestShippingAddress: boolean
|
||||
}
|
||||
|
||||
export interface ApplePayButtonOptions {
|
||||
locale: string
|
||||
buttonColor: 'black' | 'white' | 'white-outline'
|
||||
buttonType: 'add-money' | 'book' | 'buy' | 'check-out' | 'continue' | 'contribute' | 'donate' | 'order' | 'pay' | 'plain' | 'reload' | 'rent' | 'set-up' | 'subscribe' | 'support' | 'tip' | 'top-up'
|
||||
}
|
||||
|
||||
interface ApplePay extends Handler {
|
||||
canMakePayments: () => boolean
|
||||
mountPaymentButton: (selector: string, options?: ApplePayButtonOptions) => Promise<void>
|
||||
}
|
||||
|
||||
interface GooglePay extends Handler {
|
||||
getPaymentIntent: () => PaymentIntent
|
||||
setPaymentIntent: (paymentIntent: PaymentIntent) => void
|
||||
updatePaymentIntent: (paymentIntent: PaymentIntent) => void
|
||||
mountPaymentButton: (
|
||||
selector: string,
|
||||
buttonOptions?: GooglePayButtonOptions,
|
||||
paymentOptions?: PaymentOptions
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export type ApplePayHandler = ApplePay | null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user