feat: Change payment window on palmistry/payment

This commit is contained in:
yury 2024-06-25 04:24:00 +03:00
parent 55c2207a41
commit 5ca0a636cc
10 changed files with 486 additions and 188 deletions

View File

@ -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>
)
}

View 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>
)
}

View File

@ -0,0 +1,7 @@
:global(.paymentCardModalContainer) {
background: none;
.p-PaymentMethodSelector {
display: none !important;
}
}

View 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>
);
}

View 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;
}
}

View File

@ -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;
} }

View File

@ -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",

View File

@ -1,228 +1,247 @@
.payment-screen { .payment-screen {
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
max-width: 428px; max-width: 428px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.payment-screen__header { .payment-screen__header {
display: flex; display: flex;
width: 100%; width: 100%;
padding: 24px 0 11px; padding: 24px 0 11px;
justify-content: center; justify-content: center;
} }
.payment-screen__content { .payment-screen__content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.payment-screen__content * { .payment-screen__content * {
font-family: OpenSans Regular; font-family: OpenSans Regular;
} }
.payment-screen__about-us { .payment-screen__about-us {
display: flex; display: flex;
text-align: center; text-align: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
margin-bottom: 16px; margin-bottom: 16px;
} }
.payment-screen__about-us > span { .payment-screen__about-us > span {
padding: 0 60px; padding: 0 60px;
margin-bottom: 6px; margin-bottom: 6px;
} }
.payment-screen__about-us span { .payment-screen__about-us span {
font-size: 14px; font-size: 14px;
line-height: 18px; line-height: 18px;
font-family: Alata Regular !important; font-family: Alata Regular !important;
} }
.payment-screen__about-us-accent { .payment-screen__about-us-accent {
color: #066fde; color: #066fde;
} }
.payment-screen__timer { .payment-screen__timer {
width: 100%; width: 100%;
display: flex; display: flex;
padding: 8px 12px; padding: 8px 12px;
border-radius: 4px; border-radius: 4px;
background: #eff2fd; background: #eff2fd;
margin-bottom: 16px; margin-bottom: 16px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
position: sticky; position: sticky;
top: 0; top: 0;
} }
.payment-screen__timer-title { .payment-screen__timer-title {
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
} }
.payment-screen__timer-time { .payment-screen__timer-time {
gap: 1px; gap: 1px;
display: flex; display: flex;
font-size: 16px; font-size: 16px;
line-height: 22px; line-height: 22px;
align-items: center; align-items: center;
} }
.payment-screen__timer-time span { .payment-screen__timer-time span {
width: 18px; width: 18px;
height: 28px; height: 28px;
display: flex; display: flex;
background: #fff; background: #fff;
font-weight: 700; font-weight: 700;
border-radius: 4px; border-radius: 4px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 1px solid #dee5f9; border: 1px solid #dee5f9;
} }
.payment-screen__title { .payment-screen__title {
width: 100%; width: 100%;
margin-bottom: 16px; margin-bottom: 16px;
font-size: 24px; font-size: 24px;
line-height: 32px; line-height: 32px;
text-align: left; text-align: left;
color: #121620; color: #121620;
font-weight: 400; font-weight: 400;
font-family: Alata Regular !important; font-family: Alata Regular !important;
} }
.payment-screen__total-today { .payment-screen__total-today {
width: 100%; width: 100%;
display: flex; display: flex;
padding: 12px 0; padding: 12px 0;
margin-bottom: 6px; margin-bottom: 6px;
align-items: center; align-items: center;
border-top: 1px solid #dee5f9; border-top: 1px solid #dee5f9;
border-bottom: 1px solid #dee5f9; border-bottom: 1px solid #dee5f9;
justify-content: space-between; justify-content: space-between;
} }
.payment-screen__total-today span { .payment-screen__total-today span {
font-size: 16px; font-size: 16px;
line-height: 18px; line-height: 18px;
font-weight: 600; font-weight: 600;
} }
.payment-screen__total-today .payment-screen__trial-price { .payment-screen__total-today .payment-screen__trial-price {
color: #066fde; color: #066fde;
font-weight: 700; font-weight: 700;
} }
.payment-screen__promocode { .payment-screen__promocode {
gap: 8px; gap: 8px;
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 0 16px; padding: 12px 0 16px;
} }
.payment-screen__promocode span { .payment-screen__promocode span {
color: #04a777; color: #04a777;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
line-height: 18px; line-height: 18px;
} }
.payment-screen__prices { .payment-screen__prices {
width: 100%; width: 100%;
display: flex; display: flex;
margin-bottom: 6px; margin-bottom: 6px;
flex-direction: column; flex-direction: column;
} }
.payment-screen__prices span { .payment-screen__prices span {
color: #4b536a; color: #4b536a;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
} }
.payment-screen__prices s { .payment-screen__prices s {
color: #858da5; color: #858da5;
} }
.payment-screen__guarantees { .payment-screen__guarantees {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 30px; gap: 30px;
margin-bottom: 30px; margin-bottom: 30px;
} }
.payment-screen__guarantee { .payment-screen__guarantee {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 8px; gap: 8px;
max-width: 155px; max-width: 155px;
align-items: center; align-items: center;
} }
.payment-screen__guarantee svg { .payment-screen__guarantee svg {
min-width: 24px; min-width: 24px;
min-height: 24px; min-height: 24px;
max-width: 24px; max-width: 24px;
max-height: 24px; max-height: 24px;
} }
.payment-screen__guarantee span { .payment-screen__guarantee span {
color: #4b536a; color: #4b536a;
font-weight: 600; font-weight: 600;
font-size: 12px; font-size: 12px;
line-height: 18px; line-height: 18px;
} }
.payment-screen__widget { .payment-screen__widget {
background: #fff; position: fixed;
bottom: 0; background: #fff;
box-shadow: 0 -2px 16px rgba(18, 22, 32, .1); bottom: 0;
max-width: 428px; box-shadow: 0 -2px 16px rgba(18, 22, 32, .1);
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 {
height: 400px; height: 400px;
} }
.payment-screen__success { .payment-screen__success {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; background: #fff;
left: 0; z-index: 99;
top: 0; display: flex;
background: #fff; flex-direction: column;
z-index: 99; align-items: center;
display: flex; justify-content: center;
flex-direction: column; gap: 30px;
align-items: center; padding: 40px;
justify-content: center;
gap: 30px;
padding: 40px;
} }
.payment-screen__success-icon { .payment-screen__success-icon {
width: 100px; width: 100px;
height: 100px; height: 100px;
max-width: 50%; max-width: 50%;
flex-shrink: 0; flex-shrink: 0;
} }
.payment-screen__success-text { .payment-screen__success-text {
font-size: 24px; font-size: 24px;
line-height: 32px; line-height: 32px;
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;
}
}

View File

@ -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();
@ -52,7 +49,7 @@ export default function PaymentScreen() {
return ( return (
<div className="payment-screen"> <div className="payment-screen">
<div className="payment-screen__header"> <div className="payment-screen__header">
<HeaderLogo /> <HeaderLogo/>
</div> </div>
<div className="payment-screen__content"> <div className="payment-screen__content">
@ -246,40 +243,45 @@ 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 <div className="payment-screen__widget_modal_container"
className={`payment-screen__widget${ style={{minHeight: `${height}px`}}>
subscriptionStatus === "subscribed" <div
? " payment-screen__widget_success" className={`payment-screen__widget${
: "" subscriptionStatus === "subscribed"
}`} ? " payment-screen__widget_success"
> : ""
{subscriptionStatus !== "subscribed" && ( }`}
<PaymentModal >
returnUrl={window.location.href} {subscriptionStatus !== "subscribed" && (
placementKey={EPlacementKeys["aura.placement.palmistry.main"]} <PaymentModalNew
/> setHeight={setHeight}
)} activeProduct={activeProductFromStore}
returnUrl={window.location.href}
placementKey={EPlacementKeys["aura.placement.palmistry.main"]}
/>
)}
{subscriptionStatus === "subscribed" && ( {subscriptionStatus === "subscribed" && (
<div className="payment-screen__success"> <div className="payment-screen__success">
<svg <svg
className="payment-screen__success-icon" className="payment-screen__success-icon"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="512" width="512"
height="512" height="512"
viewBox="0 0 52 52" viewBox="0 0 52 52"
> >
<path <path
fill="#4ec794" fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z" d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/> />
</svg> </svg>
<div className="payment-screen__success-text"> <div className="payment-screen__success-text">
Payment success Payment success
</div>
</div> </div>
</div> )}
)} </div>
</div> </div>
)} )}
</div> </div>

View File

@ -0,0 +1,3 @@
export const getFormattedPrice = (price: number) => {
return (price / 100).toFixed(2);
};