feat: Change payment window on palmistry/payment
This commit is contained in:
parent
55c2207a41
commit
5ca0a636cc
@ -0,0 +1,9 @@
|
|||||||
|
export default function CreditCardIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0.5" width="22" height="16" rx="2" fill="white"></rect>
|
||||||
|
<rect x="0.5" y="2.66406" width="22" height="2.66667" fill="#9FB8FF"></rect>
|
||||||
|
<rect x="3" y="7.35938" width="17" height="2" rx="1" fill="#CEDBFF"></rect>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
39
src/components/PaymentModalNew/PaymentCardModal/index.tsx
Normal file
39
src/components/PaymentModalNew/PaymentCardModal/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
|
import CheckoutForm, { TConfirmType } from '@/components/PaymentPage/methods/CheckoutForm';
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import { Stripe } from '@stripe/stripe-js';
|
||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
interface IPaymentCardModalProps {
|
||||||
|
clientSecret?: string;
|
||||||
|
stripePromise: Promise<Stripe | null> | null;
|
||||||
|
paymentType?: TConfirmType;
|
||||||
|
paymentIntentId?: string;
|
||||||
|
returnUrl?: string;
|
||||||
|
isOpen: boolean;
|
||||||
|
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PaymentCardModal({
|
||||||
|
clientSecret,
|
||||||
|
stripePromise,
|
||||||
|
paymentType,
|
||||||
|
paymentIntentId,
|
||||||
|
returnUrl,
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
}: IPaymentCardModalProps) {
|
||||||
|
return (
|
||||||
|
<Modal open={isOpen} onClose={() => setIsOpen(false)}>
|
||||||
|
<Elements stripe={stripePromise} options={{clientSecret}}>
|
||||||
|
<CheckoutForm
|
||||||
|
confirmType={paymentType}
|
||||||
|
subscriptionReceiptId={paymentIntentId}
|
||||||
|
returnUrl={returnUrl}
|
||||||
|
/>
|
||||||
|
</Elements>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
:global(.paymentCardModalContainer) {
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
.p-PaymentMethodSelector {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/components/PaymentModalNew/index.tsx
Normal file
155
src/components/PaymentModalNew/index.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import Loader from '@/components/Loader';
|
||||||
|
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
import cn from 'classnames';
|
||||||
|
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Dispatch, LegacyRef, SetStateAction, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||||
|
import { usePaywall } from '@/hooks/paywall/usePaywall';
|
||||||
|
import { useMakePayment } from '@/hooks/payment/useMakePayment';
|
||||||
|
import { getFormattedPrice } from '@/utils/price.utils';
|
||||||
|
import routes from '@/routes';
|
||||||
|
import Title from '@/components/Title';
|
||||||
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
|
import ExpressCheckoutStripe from '@/components/PaymentPage/methods/ExpressCheckoutStripe';
|
||||||
|
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
|
||||||
|
import PaymentCardModal from '@/components/PaymentModalNew/PaymentCardModal';
|
||||||
|
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
|
||||||
|
|
||||||
|
interface IPaymentModalNewProps {
|
||||||
|
returnUrl: string;
|
||||||
|
placementKey: EPlacementKeys;
|
||||||
|
activeProduct: IPaywallProduct;
|
||||||
|
setHeight?: Dispatch<SetStateAction<number>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PaymentModalNew({
|
||||||
|
returnUrl,
|
||||||
|
activeProduct,
|
||||||
|
placementKey,
|
||||||
|
setHeight,
|
||||||
|
}: IPaymentModalNewProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
const [stripePromise, setStripePromise] =
|
||||||
|
useState<Promise<Stripe | null> | null>(null);
|
||||||
|
|
||||||
|
const {products, placementId, paywallId} = usePaywall({
|
||||||
|
placementKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isOpenCardModal, setIsOpenCardModal] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
paymentIntentId,
|
||||||
|
clientSecret,
|
||||||
|
returnUrl: checkoutUrl,
|
||||||
|
paymentType,
|
||||||
|
publicKey,
|
||||||
|
isLoading: isLoadingPayment,
|
||||||
|
error,
|
||||||
|
} = useMakePayment({
|
||||||
|
productId: activeProduct?._id || '',
|
||||||
|
placementId,
|
||||||
|
paywallId,
|
||||||
|
returnPaidUrl: returnUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] =
|
||||||
|
useState(true);
|
||||||
|
|
||||||
|
const isLoading = useMemo(() => {
|
||||||
|
return isLoadingPayment || isLoadingExpressCheckout;
|
||||||
|
}, [isLoadingPayment, isLoadingExpressCheckout]);
|
||||||
|
|
||||||
|
if (checkoutUrl?.length) {
|
||||||
|
window.location.href = checkoutUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (!products?.length || !publicKey) return;
|
||||||
|
setStripePromise(loadStripe(publicKey));
|
||||||
|
const isActiveProduct = products.find(
|
||||||
|
(product) => product._id === activeProduct?._id,
|
||||||
|
);
|
||||||
|
if (!activeProduct || !isActiveProduct) {
|
||||||
|
navigate(routes.client.trialChoice());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [activeProduct, navigate, products, publicKey]);
|
||||||
|
|
||||||
|
const resizeHandler = () => {
|
||||||
|
setHeight?.(ref?.current?.clientHeight || 32);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error?.length) {
|
||||||
|
setTimeout(resizeHandler, 300);
|
||||||
|
return (
|
||||||
|
<div ref={ref as LegacyRef<HTMLDivElement>} className={styles['payment-modal']}>
|
||||||
|
<Title variant="h3" className={styles.title}>
|
||||||
|
Something went wrong
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref as LegacyRef<HTMLDivElement>}
|
||||||
|
className={cn(styles.paymentModalContainer, isLoading && styles.paymentModalContainerLoading)}>
|
||||||
|
{isLoading && <div className={cn(styles.paymentModalLoader)}>
|
||||||
|
<Loader/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className={styles.paymentModalPrice}>Total due today:
|
||||||
|
${getFormattedPrice(activeProduct.trialPrice)}</div>
|
||||||
|
|
||||||
|
{!isLoadingPayment &&
|
||||||
|
<>
|
||||||
|
{!isLoading &&
|
||||||
|
<div className={styles.paymentCreditCard} onClick={() => setIsOpenCardModal(true)}>
|
||||||
|
<CreditCardIcon/>
|
||||||
|
<div>Credit / Debit Card</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<Elements stripe={stripePromise} options={{clientSecret}}>
|
||||||
|
<ExpressCheckoutStripe
|
||||||
|
clientSecret={clientSecret!}
|
||||||
|
returnUrl={returnUrl}
|
||||||
|
paymentMethodOrderList={['google_pay', 'apple_pay', 'link']}
|
||||||
|
onChangeLoading={(isLoading) => {
|
||||||
|
setIsLoadingExpressCheckout(isLoading);
|
||||||
|
setTimeout(resizeHandler, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Elements>
|
||||||
|
|
||||||
|
<PaymentCardModal
|
||||||
|
isOpen={isOpenCardModal}
|
||||||
|
setIsOpen={setIsOpenCardModal}
|
||||||
|
clientSecret={clientSecret}
|
||||||
|
stripePromise={stripePromise}
|
||||||
|
paymentType={paymentType}
|
||||||
|
paymentIntentId={paymentIntentId}
|
||||||
|
returnUrl={returnUrl}
|
||||||
|
/>
|
||||||
|
{!isLoading &&
|
||||||
|
<>
|
||||||
|
<div className={styles.infoContainer}>
|
||||||
|
<SecurityPayments/>
|
||||||
|
<p className={styles.address}>1123 Rimer Dr Moraga, California 94556</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
60
src/components/PaymentModalNew/styles.module.scss
Normal file
60
src/components/PaymentModalNew/styles.module.scss
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
.paymentModalContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
margin: -12px -20px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
transition: height 1s ease-out;
|
||||||
|
|
||||||
|
.address {
|
||||||
|
color: gray;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer > * {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paymentCreditCard {
|
||||||
|
background: #066fde;
|
||||||
|
color: #fff !important;
|
||||||
|
gap: 6px;
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 400;
|
||||||
|
min-height: 48px;
|
||||||
|
border-radius: 5px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&Loading {
|
||||||
|
background: rgba(215, 213, 213, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paymentModalLoader {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 25px;
|
||||||
|
color: #2f2e37;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paymentModalPrice {
|
||||||
|
color: #066fde;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 25px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,11 +12,13 @@ import { useDispatch } from "react-redux";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
|
export type TConfirmType = "payment" | "setup";
|
||||||
|
|
||||||
interface ICheckoutFormProps {
|
interface ICheckoutFormProps {
|
||||||
children?: JSX.Element | null;
|
children?: JSX.Element | null;
|
||||||
subscriptionReceiptId?: string;
|
subscriptionReceiptId?: string;
|
||||||
returnUrl?: string;
|
returnUrl?: string;
|
||||||
confirmType?: "payment" | "setup";
|
confirmType?: TConfirmType;
|
||||||
isHide?: boolean;
|
isHide?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ interface IExpressCheckoutStripeProps {
|
|||||||
availableMethods: AvailablePaymentMethods | undefined
|
availableMethods: AvailablePaymentMethods | undefined
|
||||||
) => void;
|
) => void;
|
||||||
onChangeLoading?: (isLoading: boolean) => void;
|
onChangeLoading?: (isLoading: boolean) => void;
|
||||||
|
paymentMethodOrderList?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExpressCheckoutStripe({
|
function ExpressCheckoutStripe({
|
||||||
@ -29,6 +30,7 @@ function ExpressCheckoutStripe({
|
|||||||
isHide = false,
|
isHide = false,
|
||||||
onAvailable,
|
onAvailable,
|
||||||
onChangeLoading,
|
onChangeLoading,
|
||||||
|
paymentMethodOrderList
|
||||||
}: IExpressCheckoutStripeProps) {
|
}: IExpressCheckoutStripeProps) {
|
||||||
const stripe = useStripe();
|
const stripe = useStripe();
|
||||||
const elements = useElements();
|
const elements = useElements();
|
||||||
@ -104,7 +106,7 @@ function ExpressCheckoutStripe({
|
|||||||
maxColumns: 1,
|
maxColumns: 1,
|
||||||
overflow: "never",
|
overflow: "never",
|
||||||
},
|
},
|
||||||
paymentMethodOrder: ["apple_pay", "google_pay", "amazon_pay", "link"],
|
paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"],
|
||||||
wallets: {
|
wallets: {
|
||||||
googlePay: "always",
|
googlePay: "always",
|
||||||
applePay: "always",
|
applePay: "always",
|
||||||
|
|||||||
@ -184,13 +184,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.payment-screen__widget {
|
.payment-screen__widget {
|
||||||
|
position: fixed;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0 -2px 16px rgba(18, 22, 32, .1);
|
box-shadow: 0 -2px 16px rgba(18, 22, 32, .1);
|
||||||
max-width: 428px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px;
|
padding: 12px 20px;
|
||||||
position: relative;
|
text-align: center;
|
||||||
|
text-align: -webkit-center;
|
||||||
|
transition: .5s height;
|
||||||
|
max-width: 560px;
|
||||||
|
margin-left: -66px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-screen__widget_success {
|
.payment-screen__widget_success {
|
||||||
@ -200,9 +204,6 @@
|
|||||||
.payment-screen__success {
|
.payment-screen__success {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -226,3 +227,21 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #121620;
|
color: #121620;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payment-screen__widget_modal_container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 560px) {
|
||||||
|
.payment-screen__widget {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 20px;
|
||||||
|
text-align: center;
|
||||||
|
text-align: -webkit-center;
|
||||||
|
transition: height 1s linear;
|
||||||
|
max-width: 560px;
|
||||||
|
margin-left: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,17 @@
|
|||||||
import React from "react";
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import "./payment-screen.css";
|
import './payment-screen.css';
|
||||||
|
|
||||||
import useSteps, { Step } from "@/hooks/palmistry/use-steps";
|
import useSteps, { Step } from '@/hooks/palmistry/use-steps';
|
||||||
import useTimer from "@/hooks/palmistry/use-timer";
|
import useTimer from '@/hooks/palmistry/use-timer';
|
||||||
import HeaderLogo from "@/components/palmistry/header-logo/header-logo";
|
import HeaderLogo from '@/components/palmistry/header-logo/header-logo';
|
||||||
import PaymentModal from "@/components/PaymentModal";
|
import { selectors } from '@/store';
|
||||||
import { selectors } from "@/store";
|
import { EPlacementKeys } from '@/api/resources/Paywall';
|
||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useSearchParams } from "react-router-dom";
|
import PaymentModalNew from '@/components/PaymentModalNew';
|
||||||
|
import { getFormattedPrice } from '@/utils/price.utils';
|
||||||
const getFormattedPrice = (price: number) => {
|
|
||||||
return (price / 100).toFixed(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function PaymentScreen() {
|
export default function PaymentScreen() {
|
||||||
const time = useTimer();
|
const time = useTimer();
|
||||||
@ -24,7 +21,7 @@ export default function PaymentScreen() {
|
|||||||
searchParams.get("redirect_status") === "succeeded"
|
searchParams.get("redirect_status") === "succeeded"
|
||||||
? "subscribed"
|
? "subscribed"
|
||||||
: "lead";
|
: "lead";
|
||||||
// const subscriptionStatus = useSelector(selectors.selectStatus);
|
const [height, setHeight] = useState(subscriptionStatus === "subscribed" ? 246 : 146);
|
||||||
|
|
||||||
const steps = useSteps();
|
const steps = useSteps();
|
||||||
|
|
||||||
@ -246,6 +243,8 @@ export default function PaymentScreen() {
|
|||||||
<style>{`.palmistry-payment-modal { max-height: calc(100dvh - 40px) }`}</style>
|
<style>{`.palmistry-payment-modal { max-height: calc(100dvh - 40px) }`}</style>
|
||||||
|
|
||||||
{activeProductFromStore && (
|
{activeProductFromStore && (
|
||||||
|
<div className="payment-screen__widget_modal_container"
|
||||||
|
style={{minHeight: `${height}px`}}>
|
||||||
<div
|
<div
|
||||||
className={`payment-screen__widget${
|
className={`payment-screen__widget${
|
||||||
subscriptionStatus === "subscribed"
|
subscriptionStatus === "subscribed"
|
||||||
@ -254,7 +253,9 @@ export default function PaymentScreen() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{subscriptionStatus !== "subscribed" && (
|
{subscriptionStatus !== "subscribed" && (
|
||||||
<PaymentModal
|
<PaymentModalNew
|
||||||
|
setHeight={setHeight}
|
||||||
|
activeProduct={activeProductFromStore}
|
||||||
returnUrl={window.location.href}
|
returnUrl={window.location.href}
|
||||||
placementKey={EPlacementKeys["aura.placement.palmistry.main"]}
|
placementKey={EPlacementKeys["aura.placement.palmistry.main"]}
|
||||||
/>
|
/>
|
||||||
@ -281,6 +282,7 @@ export default function PaymentScreen() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
3
src/utils/price.utils.tsx
Normal file
3
src/utils/price.utils.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const getFormattedPrice = (price: number) => {
|
||||||
|
return (price / 100).toFixed(2);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user