feat: add stripe payment
This commit is contained in:
parent
336cc8ffba
commit
3d1361b57f
78
package-lock.json
generated
78
package-lock.json
generated
@ -10,6 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
||||||
"@reduxjs/toolkit": "^1.9.5",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"@stripe/react-stripe-js": "^2.3.1",
|
||||||
|
"@stripe/stripe-js": "^2.1.9",
|
||||||
"apng-js": "^1.1.1",
|
"apng-js": "^1.1.1",
|
||||||
"html-react-parser": "^3.0.16",
|
"html-react-parser": "^3.0.16",
|
||||||
"i18next": "^22.5.0",
|
"i18next": "^22.5.0",
|
||||||
@ -1000,6 +1002,24 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@stripe/react-stripe-js": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vXiwcG2ZjAF4AezjP7DJ8jiwxfCWCen/X2rBhyXaKrfQ7+pwmXhsoUlKRa0eLWioY1oelOQOafauNUiwTwFHgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@stripe/stripe-js": "^1.44.1 || ^2.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@stripe/stripe-js": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-0RSvCJrzEVx52e8hbSAcZ2vv6OzoFj5fe5XC50GSrcev1Y4t2XDE6W5CIhR/Y6l3CPgO/P4luqoLWuvpUkBhig=="
|
||||||
|
},
|
||||||
"node_modules/@types/history": {
|
"node_modules/@types/history": {
|
||||||
"version": "4.7.11",
|
"version": "4.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||||
@ -2606,6 +2626,14 @@
|
|||||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -2765,6 +2793,21 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||||
@ -4012,6 +4055,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz",
|
||||||
"integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA=="
|
"integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA=="
|
||||||
},
|
},
|
||||||
|
"@stripe/react-stripe-js": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vXiwcG2ZjAF4AezjP7DJ8jiwxfCWCen/X2rBhyXaKrfQ7+pwmXhsoUlKRa0eLWioY1oelOQOafauNUiwTwFHgQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@stripe/stripe-js": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-0RSvCJrzEVx52e8hbSAcZ2vv6OzoFj5fe5XC50GSrcev1Y4t2XDE6W5CIhR/Y6l3CPgO/P4luqoLWuvpUkBhig=="
|
||||||
|
},
|
||||||
"@types/history": {
|
"@types/history": {
|
||||||
"version": "4.7.11",
|
"version": "4.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||||
@ -5172,6 +5228,11 @@
|
|||||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||||
|
},
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -5275,6 +5336,23 @@
|
|||||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
||||||
"@reduxjs/toolkit": "^1.9.5",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"@stripe/react-stripe-js": "^2.3.1",
|
||||||
|
"@stripe/stripe-js": "^2.1.9",
|
||||||
"apng-js": "^1.1.1",
|
"apng-js": "^1.1.1",
|
||||||
"html-react-parser": "^3.0.16",
|
"html-react-parser": "^3.0.16",
|
||||||
"i18next": "^22.5.0",
|
"i18next": "^22.5.0",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export interface Response {
|
|||||||
first_open_subscription_popup: boolean
|
first_open_subscription_popup: boolean
|
||||||
runs_before_subscription_popup: number
|
runs_before_subscription_popup: number
|
||||||
appirater_alerts: AppiraterAlertAppiraterAlert[]
|
appirater_alerts: AppiraterAlertAppiraterAlert[]
|
||||||
|
stripe_public_key: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,70 +1,87 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from "react";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from "react-router-dom";
|
||||||
import { selectors } from '@/store'
|
import { selectors } from "@/store";
|
||||||
import { usePayment } from '@/payment'
|
import { usePayment } from "@/payment";
|
||||||
import { actions } from '@/store'
|
import { actions } from "@/store";
|
||||||
import {
|
import {
|
||||||
ApplePayBanner,
|
ApplePayBanner,
|
||||||
ApplePayButton,
|
ApplePayButton,
|
||||||
GooglePayBanner,
|
GooglePayBanner,
|
||||||
GooglePayButton,
|
GooglePayButton,
|
||||||
CardButton,
|
CardButton,
|
||||||
CardModal
|
CardModal,
|
||||||
} from './methods'
|
} from "./methods";
|
||||||
import ErrorModal from './ErrorModal'
|
import ErrorModal from "./ErrorModal";
|
||||||
import UserHeader from '../UserHeader'
|
import UserHeader from "../UserHeader";
|
||||||
import Title from '../Title'
|
import Title from "../Title";
|
||||||
import Loader from '../Loader'
|
import Loader from "../Loader";
|
||||||
import secure from './secure.png'
|
import secure from "./secure.png";
|
||||||
import routes from '@/routes'
|
import routes from "@/routes";
|
||||||
import './styles.css'
|
import "./styles.css";
|
||||||
import Header from '../Header'
|
import Header from "../Header";
|
||||||
|
import { StripeButton, StripeModal } from "./methods/Stripe";
|
||||||
|
|
||||||
function PaymentPage(): JSX.Element {
|
function PaymentPage(): JSX.Element {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
const { applePay } = usePayment()
|
const { applePay } = usePayment();
|
||||||
const [openCardModal, setOpenCardModal] = useState(false)
|
const [openCardModal, setOpenCardModal] = useState(false);
|
||||||
const [openErrorModal, setOpenErrorModal] = useState(false)
|
const [openStripeModal, setOpenStripeModal] = useState(false);
|
||||||
const dispatch = useDispatch()
|
const [openErrorModal, setOpenErrorModal] = useState(false);
|
||||||
const navigate = useNavigate()
|
const dispatch = useDispatch();
|
||||||
const isLoading = applePay === null
|
const navigate = useNavigate();
|
||||||
const isApplePayAvailable = import.meta.env.PROD && applePay?.canMakePayments()
|
const isLoading = applePay === null;
|
||||||
const email = useSelector(selectors.selectEmail)
|
const isApplePayAvailable =
|
||||||
const isDiscount = useSelector(selectors.selectIsDiscount)
|
import.meta.env.PROD && applePay?.canMakePayments();
|
||||||
const selectedPrice = useSelector(selectors.selectSelectedPrice)
|
const email = useSelector(selectors.selectEmail);
|
||||||
const price = isDiscount ? (Math.round(selectedPrice || 0) / 2).toFixed(2) : selectedPrice
|
const isDiscount = useSelector(selectors.selectIsDiscount);
|
||||||
|
const selectedPrice = useSelector(selectors.selectSelectedPrice);
|
||||||
|
const price = isDiscount
|
||||||
|
? (Math.round(selectedPrice || 0) / 2).toFixed(2)
|
||||||
|
: selectedPrice;
|
||||||
const onSuccess = useCallback(() => {
|
const onSuccess = useCallback(() => {
|
||||||
dispatch(actions.status.update('subscribed'))
|
dispatch(actions.status.update("subscribed"));
|
||||||
navigate(routes.client.wallpaper())
|
navigate(routes.client.wallpaper());
|
||||||
}, [dispatch, navigate])
|
}, [dispatch, navigate]);
|
||||||
const onError = useCallback((error: Error) => {
|
const onError = useCallback((error: Error) => {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
setOpenErrorModal(true)
|
setOpenErrorModal(true);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header showCross={true} clickCross={() => navigate(routes.client.home())} />
|
<Header
|
||||||
|
showCross={true}
|
||||||
|
clickCross={() => navigate(routes.client.home())}
|
||||||
|
/>
|
||||||
<UserHeader email={email} />
|
<UserHeader email={email} />
|
||||||
<section className='page'>
|
<section className="page">
|
||||||
{ isLoading ? <Loader /> : (
|
{isLoading ? (
|
||||||
|
<Loader />
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className='page-header'>
|
<div className="page-header">
|
||||||
{ isApplePayAvailable ? <ApplePayBanner /> : <GooglePayBanner /> }
|
{isApplePayAvailable ? <ApplePayBanner /> : <GooglePayBanner />}
|
||||||
<img src={secure} alt='100% Secure' />
|
<img src={secure} alt="100% Secure" />
|
||||||
</div>
|
</div>
|
||||||
<Title variant='h1' className='mb-45'>{t('choose_payment')}</Title>
|
<Title variant="h1" className="mb-45">
|
||||||
{ isApplePayAvailable
|
{t("choose_payment")}
|
||||||
?
|
</Title>
|
||||||
|
{/* {isApplePayAvailable ? (
|
||||||
<ApplePayButton onSuccess={onSuccess} onError={onError} />
|
<ApplePayButton onSuccess={onSuccess} onError={onError} />
|
||||||
:
|
) : (
|
||||||
<GooglePayButton onSuccess={onSuccess} onError={onError} /> }
|
<GooglePayButton onSuccess={onSuccess} onError={onError} />
|
||||||
<div className='payment-divider'>{t('or').toUpperCase()}</div>
|
)}
|
||||||
<CardButton onClick={() => setOpenCardModal(true)} />
|
<div className="payment-divider">{t("or").toUpperCase()}</div>
|
||||||
<p className='payment-warining'>
|
<CardButton onClick={() => setOpenCardModal(true)} /> */}
|
||||||
{t('will_be_charged', { strongText: <strong>{t('trial_price', { price: price })}</strong> })}
|
<StripeButton onClick={() => setOpenStripeModal(true)} />
|
||||||
|
<p className="payment-warining">
|
||||||
|
{t("will_be_charged", {
|
||||||
|
strongText: (
|
||||||
|
<strong>{t("trial_price", { price: price })}</strong>
|
||||||
|
),
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
<CardModal
|
<CardModal
|
||||||
open={openCardModal}
|
open={openCardModal}
|
||||||
@ -72,12 +89,21 @@ function PaymentPage(): JSX.Element {
|
|||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
/>
|
/>
|
||||||
<ErrorModal open={openErrorModal} onClose={() => setOpenErrorModal(false)} />
|
<StripeModal
|
||||||
|
open={openStripeModal}
|
||||||
|
onClose={() => setOpenStripeModal(false)}
|
||||||
|
onSuccess={onSuccess}
|
||||||
|
onError={onError}
|
||||||
|
/>
|
||||||
|
<ErrorModal
|
||||||
|
open={openErrorModal}
|
||||||
|
onClose={() => setOpenErrorModal(false)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PaymentPage
|
export default PaymentPage;
|
||||||
|
|||||||
18
src/components/PaymentPage/methods/Stripe/Button.tsx
Normal file
18
src/components/PaymentPage/methods/Stripe/Button.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import MainButton from '@/components/MainButton'
|
||||||
|
// import card from './card.svg'
|
||||||
|
|
||||||
|
interface IStripeButtonProps {
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StripeButton({ onClick }: IStripeButtonProps): JSX.Element {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<MainButton color='blue' onClick={onClick}>
|
||||||
|
{/* <img className='payment-card' src={card} alt='Credit / Debit Card' /> */}
|
||||||
|
{t('stripe')}
|
||||||
|
</MainButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
56
src/components/PaymentPage/methods/Stripe/CheckoutForm.tsx
Normal file
56
src/components/PaymentPage/methods/Stripe/CheckoutForm.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import {
|
||||||
|
PaymentElement,
|
||||||
|
useElements,
|
||||||
|
useStripe,
|
||||||
|
} from "@stripe/react-stripe-js";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function CheckoutForm() {
|
||||||
|
const stripe = useStripe();
|
||||||
|
const elements = useElements();
|
||||||
|
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!stripe || !elements) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsProcessing(true);
|
||||||
|
|
||||||
|
const { error, paymentIntent } = await stripe.confirmPayment({
|
||||||
|
elements,
|
||||||
|
confirmParams: {
|
||||||
|
return_url: window.location.href,
|
||||||
|
},
|
||||||
|
redirect: "if_required",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setMessage(error?.message || "Oops! Something went wrong.");
|
||||||
|
} else if (paymentIntent && paymentIntent.status === "succeeded") {
|
||||||
|
setMessage("Payment succeeded!");
|
||||||
|
} else {
|
||||||
|
setMessage("Unexpected state");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsProcessing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form id="payment-form" onSubmit={handleSubmit}>
|
||||||
|
<PaymentElement />
|
||||||
|
<MainButton color="blue" disabled={isProcessing} id="submit">
|
||||||
|
<span id="button-text">
|
||||||
|
{isProcessing ? "Processing..." : "Pay now"}
|
||||||
|
</span>
|
||||||
|
</MainButton>
|
||||||
|
<Title variant="h4">{message}</Title>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
src/components/PaymentPage/methods/Stripe/Modal.tsx
Normal file
74
src/components/PaymentPage/methods/Stripe/Modal.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { SubscriptionReceipts, useApi } from "@/api";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
import Loader from "@/components/Loader";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Stripe, loadStripe } from "@stripe/stripe-js";
|
||||||
|
import { Elements } from "@stripe/react-stripe-js";
|
||||||
|
import CheckoutForm from "./CheckoutForm";
|
||||||
|
|
||||||
|
interface StripeModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess: (receipt: SubscriptionReceipts.SubscriptionReceipt) => void;
|
||||||
|
onError: (error: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StripeModal({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
}: StripeModalProps): JSX.Element {
|
||||||
|
const api = useApi();
|
||||||
|
const [stripePromise, setStripePromise] =
|
||||||
|
useState<Promise<Stripe | null> | null>(null);
|
||||||
|
const [clientSecret, setClientSecret] = useState<string>("");
|
||||||
|
const isLoading = false;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const siteConfig = await api.getAppConfig({ bundleId: "auraweb" });
|
||||||
|
setStripePromise(loadStripe(siteConfig.data.stripe_public_key));
|
||||||
|
})();
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("https://aura.wit.life/api/v1/user/subscription_receipts.json", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization:
|
||||||
|
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
way: "stripe",
|
||||||
|
subscription_receipt: {
|
||||||
|
item_interval: "year",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}).then(async (res) => {
|
||||||
|
const { subscription_receipt } = await res.json();
|
||||||
|
const { client_secret } = subscription_receipt.data;
|
||||||
|
setClientSecret(client_secret);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={handleClose}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="payment-loader">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{stripePromise && clientSecret && (
|
||||||
|
<Elements stripe={stripePromise} options={{ clientSecret }}>
|
||||||
|
<CheckoutForm />
|
||||||
|
</Elements>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
2
src/components/PaymentPage/methods/Stripe/index.tsx
Normal file
2
src/components/PaymentPage/methods/Stripe/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Button'
|
||||||
|
export * from './Modal'
|
||||||
@ -80,6 +80,7 @@ export default {
|
|||||||
people_joined_today: "<countPeoples> people joined today",
|
people_joined_today: "<countPeoples> people joined today",
|
||||||
you_and: "You and <user>",
|
you_and: "You and <user>",
|
||||||
sign: "Sign",
|
sign: "Sign",
|
||||||
|
stripe: "Stripe",
|
||||||
'aura-10_breath-button': "Increase up to 10%. Practice for the Energy of Money",
|
'aura-10_breath-button': "Increase up to 10%. Practice for the Energy of Money",
|
||||||
'aura-money_compatibility-button': "low MONEY energy. Determine who drains your energy",
|
'aura-money_compatibility-button': "low MONEY energy. Determine who drains your energy",
|
||||||
"breathe-subtitle": "Breathing practice will help improve your aura. Breath in the positive energy, breathe out the negative...",
|
"breathe-subtitle": "Breathing practice will help improve your aura. Breath in the positive energy, breathe out the negative...",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user