new-payment-method

This commit is contained in:
Денис Катаев 2024-05-31 10:06:28 +00:00 committed by Daniil Chemerkin
parent d675effed3
commit 8992ed788c
44 changed files with 383 additions and 715 deletions

View File

@ -12,6 +12,9 @@ export interface PayloadGet extends Payload {
export enum EPlacementKeys {
"aura.placement.main" = "aura.placement.main",
"aura.placement.redesign.main" = "aura.placement.redesign.main",
"aura.placement.email.marketing" = "aura.placement.email.marketing",
"aura.placement.secret.discount" = "aura.placement.secret.discount",
"aura.placement.palmistry.main" = "aura.placement.palmistry.main"
}
interface ResponseGetSuccess {

View File

@ -57,7 +57,6 @@ import { Asset } from "@/api/resources/Assets";
import PaymentResultPage from "../PaymentPage/results";
import PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
import PaymentFailPage from "../PaymentPage/results/ErrorPage";
import { StripePage } from "../StripePage";
import AuthPage from "../AuthPage";
import AuthResultPage from "../AuthResultPage";
import MagicBallPage from "../pages/MagicBall";
@ -113,8 +112,8 @@ import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultat
import StepsManager from "@/components/palmistry/steps-manager/steps-manager";
import Advisors from "../pages/Advisors";
import AdvisorChatPage from "../pages/AdvisorChat";
import SuccessPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage";
import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage";
import SuccessPaymentPage from "../pages/SinglePaymentPage/ResultPayment/SuccessPaymentPage";
import FailPaymentPage from "../pages/SinglePaymentPage/ResultPayment/FailPaymentPage";
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
import GetInformationPartnerPage from "../pages/GetInformationPartner";
import BirthPlacePage from "../pages/BirthPlacePage";
@ -594,34 +593,6 @@ function App(): JSX.Element {
</Route>
{/* Advisor short path */}
{/* Single Payment Page Short Path */}
{/* <Route
element={
<ShortPathOutlet
productKey={EProductKeys["chat.aura"]}
redirectUrls={{
data: {
no: routes.client.advisorChatGender(),
force: routes.client.advisorChatBirthdate(),
},
}}
requiredParameters={[
birthdate,
birthPlace,
isForceShortPath || gender,
]}
/>
}
>
<Route
path={routes.client.advisorChatPayment()}
element={<PaymentWithEmailPage />}
>
<Route path=":productId" element={<PaymentWithEmailPage />} />
</Route>
</Route> */}
{/* Single Payment Page Short Path */}
{/* Test Routes Start */}
<Route path={routes.client.notFound()} element={<NotFoundPage />} />
<Route path={routes.client.gender()} element={<GenderPage />}>
@ -809,10 +780,6 @@ function App(): JSX.Element {
/>
<Route path={routes.client.static()} element={<StaticPage />} />
<Route path={routes.client.priceList()} element={<PriceListPage />} />
{/* <Route
path={routes.client.wallpaper()}
element={<ProtectWallpaperPage />}
/> */}
</Route>
<Route element={<AuthorizedUserOutlet />}>
<Route
@ -824,18 +791,10 @@ function App(): JSX.Element {
</Route>
<Route element={<PrivateOutlet />}>
<Route element={<AuthorizedUserOutlet />}>
{/* <Route
path={routes.client.subscription()}
element={<SubscriptionPage />}
/> */}
<Route
path={routes.client.paymentMethod()}
element={<PaymentPage />}
/>
<Route
path={routes.client.paymentStripe()}
element={<StripePage />}
/>
</Route>
<Route element={<PrivateSubscriptionOutlet />}>
<Route path={routes.client.home()} element={<HomePage />} />

View File

@ -1,16 +0,0 @@
import { useTranslation } from 'react-i18next'
import MainButton from '@/components/MainButton'
interface IStripeButtonProps {
onClick: () => void
}
export function StripeButton({ onClick }: IStripeButtonProps): JSX.Element {
const { t } = useTranslation()
return (
<MainButton color='blue' onClick={onClick}>
{t('stripe')}
</MainButton>
)
}

View File

@ -1,120 +0,0 @@
import styles from "./styles.module.css";
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";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import Title from "@/components/Title";
import ApplePayButton from "@/components/StripePage/ApplePayButton";
import SubPlanInformation from "@/components/SubPlanInformation";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { useMakePayment } from "@/hooks/payment/useMakePayment";
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 navigate = useNavigate();
const email = useSelector(selectors.selectUser).email;
const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null);
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.main"],
});
const activeProduct = useSelector(selectors.selectActiveProduct);
if (!activeProduct) {
navigate(routes.client.trialChoice());
}
const {
paymentIntentId,
clientSecret,
returnUrl: checkoutUrl,
paymentType,
publicKey,
isLoading,
error,
} = useMakePayment({
productId: activeProduct?._id || "",
});
if (checkoutUrl?.length) {
window.location.href = checkoutUrl;
}
useEffect(() => {
(async () => {
if (!products?.length || !publicKey) return;
setStripePromise(loadStripe(publicKey));
const isActiveSubPlan = products.find(
(product) => product._id === activeProduct?._id
);
if (!activeProduct || !isActiveSubPlan) {
navigate(routes.client.priceList());
}
})();
}, [activeProduct, navigate, products, publicKey]);
const handleClose = () => {
onClose();
};
if (error?.length) {
return (
<div className={styles["payment-modal"]}>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
return (
<Modal open={open} onClose={handleClose}>
{isLoading ? (
<div className={styles["payment-loader"]}>
<Loader />
</div>
) : null}
{!isLoading && (
<>
<Title variant="h2" className={styles.title}>
Choose payment method
</Title>
<p className={styles.email}>{email}</p>
</>
)}
{stripePromise && clientSecret && paymentIntentId && (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<ApplePayButton
activeProduct={activeProduct}
client_secret={clientSecret}
subscriptionReceiptId={paymentIntentId}
/>
{activeProduct && <SubPlanInformation product={activeProduct} />}
<CheckoutForm
confirmType={paymentType}
subscriptionReceiptId={paymentIntentId}
/>
</Elements>
)}
</Modal>
);
}

View File

@ -1,2 +0,0 @@
export * from './Button'
export * from './Modal'

View File

@ -1,102 +0,0 @@
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 "../PaymentPage/methods/Stripe/CheckoutForm";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import SubPlanInformation from "../SubPlanInformation";
import Title from "../Title";
import ApplePayButton from "./ApplePayButton";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { useMakePayment } from "@/hooks/payment/useMakePayment";
export function StripePage(): JSX.Element {
const navigate = useNavigate();
const activeProduct = useSelector(selectors.selectActiveProduct);
const email = useSelector(selectors.selectUser).email;
const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null);
if (!activeProduct) {
navigate(routes.client.priceList());
}
const {
paymentIntentId,
clientSecret,
returnUrl: checkoutUrl,
paymentType,
publicKey,
isLoading,
error,
} = useMakePayment({
productId: activeProduct?._id || "",
});
if (checkoutUrl?.length) {
window.location.href = checkoutUrl;
}
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.main"],
});
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.priceList());
}
})();
}, [activeProduct, navigate, products, publicKey]);
if (error?.length) {
return (
<div className={styles["payment-modal"]}>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
return (
<div className={`${styles.page} page`}>
{isLoading ? (
<div className={styles["payment-loader"]}>
<Loader />
</div>
) : null}
{!isLoading && (
<>
<Title variant="h2" className={styles.title}>
Pay
</Title>
<p className={styles.email}>{email}</p>
</>
)}
{stripePromise && clientSecret && paymentIntentId && (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<ApplePayButton
activeProduct={activeProduct}
client_secret={clientSecret}
subscriptionReceiptId={paymentIntentId}
/>
{activeProduct && <SubPlanInformation product={activeProduct} />}
<CheckoutForm
confirmType={paymentType}
subscriptionReceiptId={paymentIntentId}
/>
</Elements>
)}
</div>
);
}

View File

@ -1,38 +0,0 @@
.page {
/* position: relative; */
position: static;
/* height: calc(100vh - 50px);
max-height: -webkit-fill-available; */
display: flex;
justify-items: center;
justify-content: center;
gap: 16px;
}
.payment-loader {
display: flex;
justify-content: center;
align-items: center;
}
.cross {
position: absolute;
top: -36px;
right: 28px;
width: 22px;
height: 22px;
cursor: pointer;
z-index: 9;
}
.title {
font-size: 27px;
font-weight: 700;
margin: 0;
}
.email {
font-size: 17px;
font-weight: 500;
margin: 0;
}

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import styles from "./styles.module.css";
import TotalToday from "./TotalToday";
import ApplePayButton from "../StripePage/ApplePayButton";
import ApplePayButton from "../PaymentPage/methods/ApplePayButton";
import { IPaywallProduct } from "@/api/resources/Paywall";
interface ISubPlanInformationProps {

View File

@ -4,8 +4,8 @@ import PaymentMethodsChoice from "../PaymentMethodsChoice";
import { useEffect, useMemo, useState } from "react";
import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods";
import { Elements } from "@stripe/react-stripe-js";
import ApplePayButton from "@/components/StripePage/ApplePayButton";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
import ApplePayButton from "@/components/PaymentPage/methods/ApplePayButton";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import { Stripe, loadStripe } from "@stripe/stripe-js";
import { useSelector } from "react-redux";
import { selectors } from "@/store";

View File

@ -71,6 +71,16 @@ function TrialPaymentPage() {
}
}, [dispatch, subPlan, products, activeProduct]);
useEffect(() => {
if (!products.length) return;
const isActiveProduct = products.find(
(product) => product._id === activeProduct?._id
);
if (!activeProduct || !isActiveProduct) {
navigate(routes.client.trialChoiceV1());
}
}, [activeProduct, navigate, products]);
useEffect(() => {
if (["relationship", "married"].includes(flowChoice)) {
setSingleOrWithPartner("partner");

View File

@ -38,7 +38,7 @@ function PaymentDiscountTable() {
<p>Your cost per 14 days after trial:</p>
<div className={styles.side}>
<span className={styles.discount}>$19</span>
<strong>$9</strong>
<strong>${(activeProduct?.price || 0) / 100}</strong>
</div>
</div>
<p className={styles.save}>You save $30</p>

View File

@ -4,15 +4,38 @@ import MainButton from "@/components/MainButton";
import PaymentDiscountTable from "./PaymentDiscountTable";
import Modal from "@/components/Modal";
import PaymentModal from "../TrialPayment/components/PaymentModal";
import { useState } from "react";
import { useEffect, useState } from "react";
import { actions, selectors } from "@/store";
import { useDispatch, useSelector } from "react-redux";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
function TrialPaymentWithDiscount() {
const dispatch = useDispatch();
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.secret.discount"],
});
const productFromStore = useSelector(selectors.selectActiveProduct);
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
const handleClose = () => {
setIsOpenPaymentModal(false);
};
useEffect(() => {
if (!products.length) return;
const activeProduct = products.find(
(p) => p.trialPrice === productFromStore?.trialPrice
);
if (!activeProduct) {
dispatch(actions.payment.update({ activeProduct: products[0] }));
}
if (activeProduct) {
dispatch(actions.payment.update({ activeProduct }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, products]);
return (
<section className={`${styles.page} page`}>
<Modal open={isOpenPaymentModal} onClose={handleClose}>
@ -35,10 +58,12 @@ function TrialPaymentWithDiscount() {
</MainButton>
<p className={styles.policy}>
By continuing you agree that if you don't cancel prior to the end of the
3-days trial, you will automatically be charged $9 for the introductory
period of 14 days thereafter the standard rate of $9 every 14 days until
you cancel in settings. Learn more about cancellation and refund policy
in Subscription terms.
3-days trial, you will automatically be charged $
{(productFromStore?.price || 0) / 100} for the introductory period of 14
days thereafter the standard rate of $
{(productFromStore?.price || 0) / 100} every 14 days until you cancel in
settings. Learn more about cancellation and refund policy in
Subscription terms.
</p>
</section>
);

View File

@ -15,7 +15,7 @@ import {
} from "@/api/resources/SinglePayment";
import { createSinglePayment } from "@/services/singlePayment";
import Modal from "@/components/Modal";
import PaymentForm from "@/components/pages/PaymentWithEmailPage/PaymentForm";
import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm";
import { getPriceCentsToDollars } from "@/services/price";
import Loader, { LoaderColor } from "@/components/Loader";

View File

@ -19,7 +19,7 @@ import { SinglePayment, useApi, useApiCall } from "@/api";
import Loader, { LoaderColor } from "@/components/Loader";
import { getPriceCentsToDollars } from "@/services/price";
import Modal from "@/components/Modal";
import PaymentForm from "@/components/pages/PaymentWithEmailPage/PaymentForm";
import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm";
function AddReportPage() {
const navigate = useNavigate();

View File

@ -21,7 +21,7 @@ import {
} from "@/api/resources/SinglePayment";
import Modal from "@/components/Modal";
import { getPriceCentsToDollars } from "@/services/price";
import PaymentForm from "@/components/pages/PaymentWithEmailPage/PaymentForm";
import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm";
const sliderSettings = {
dots: false,

View File

@ -1,7 +1,13 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
function ComparePrices() {
function ComparePrices({
oldPrice,
newPrice,
}: {
oldPrice: string;
newPrice: string;
}) {
return (
<div className={styles.container}>
<div className={`${styles["old-price"]} ${styles["price-container"]}`}>
@ -11,7 +17,7 @@ function ComparePrices() {
</Title>
</div>
<div className={styles["main-container"]}>
<p className={styles.text}>up to $13.67</p>
<p className={styles.text}>{oldPrice}</p>
</div>
</div>
<div className={`${styles["new-price"]} ${styles["price-container"]}`}>
@ -22,7 +28,7 @@ function ComparePrices() {
</Title>
</div>
<div className={styles["main-container"]}>
<p className={styles.text}>$0</p>
<p className={styles.text}>${newPrice}</p>
</div>
</div>
</div>

View File

@ -1,13 +1,13 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
function SpecialOfferBanner() {
function SpecialOfferBanner({ title }: { title: string }) {
return (
<div className={styles.container}>
<img src="/wrapped-gift.webp" alt="Wrapped Gift" />
<div className="text-container">
<Title className={styles.title} variant="h3">
Special Offer!
{title}
</Title>
<p className={styles.text}>Everything for free. Trial include!</p>
</div>

View File

@ -15,11 +15,16 @@ import { selectors } from "@/store";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
function MarketingLanding() {
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const navigate = useNavigate();
const { paywall, products, getText } = usePaywall({
placementKey: EPlacementKeys["aura.placement.email.marketing"],
});
const handleNext = () => {
navigate(routes.client.email("marketing-trial-payment"));
@ -27,7 +32,7 @@ function MarketingLanding() {
return (
<section className={`${styles.page} page`}>
<SpecialOfferBanner />
<SpecialOfferBanner title={paywall?.name || ""} />
<div className={styles.wrapper}>
<Title variant="h2" className={`${styles.title} ${styles["hi-title"]}`}>
Hey, {zodiacSign} Sun 👋
@ -66,7 +71,10 @@ function MarketingLanding() {
alt="Understanding"
style={{ minHeight: "323px" }}
/>
<ComparePrices />
<ComparePrices
oldPrice={getText("text.old.price", {}) as string}
newPrice={`${((products[0]?.trialPrice || 0) / 100).toFixed(2)}`}
/>
<PointsList
points={marketingLandingPointsList}
title="Your plan also includes:"

View File

@ -5,31 +5,22 @@ import MainButton from "@/components/MainButton";
import Modal from "@/components/Modal";
import PaymentModal from "../../TrialPayment/components/PaymentModal";
import { useEffect, useState } from "react";
import { useApi } from "@/api";
import { useTranslation } from "react-i18next";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { useDispatch } from "react-redux";
import { actions } from "@/store";
function MarketingTrialPayment() {
const { i18n } = useTranslation();
const locale = i18n.language;
const api = useApi();
const dispatch = useDispatch();
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
const [freeTrialProduct, setFreeTrialProduct] = useState<
IPaywallProduct | undefined
>();
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.main"],
placementKey: EPlacementKeys["aura.placement.email.marketing"],
});
// get free trial plan
useEffect(() => {
(async () => {
const _freeProduct = products.find((product) => product.isFreeTrial);
setFreeTrialProduct(_freeProduct);
})();
}, [api, locale, products]);
dispatch(actions.payment.update({ activeProduct: products[0] }));
}, [dispatch, products]);
const openStripeModal = () => {
setIsOpenPaymentModal(true);
@ -41,13 +32,17 @@ function MarketingTrialPayment() {
return (
<>
<Modal
containerClassName={styles.modal}
open={isOpenPaymentModal}
onClose={handleCloseModal}
>
<PaymentModal activeProduct={freeTrialProduct} />
</Modal>
{products[0] && (
<Modal
containerClassName={styles.modal}
open={isOpenPaymentModal}
onClose={handleCloseModal}
>
<PaymentModal
placementKey={EPlacementKeys["aura.placement.email.marketing"]}
/>
</Modal>
)}
<section className={`${styles.page} page`}>
<div className={styles.wrapper}>
<div className={styles.banner}>Special Offer</div>
@ -57,7 +52,7 @@ function MarketingTrialPayment() {
<p className={styles.description}>No pressure. Cancel anytime</p>
<div className={styles["total-today"]}>
<p className={styles.description}>Total today:</p>
<p className={styles.value}>$0</p>
<p className={styles.value}>${(products[0]?.trialPrice / 100).toFixed(2) || 0}</p>
</div>
<div className={styles.line} />
<div className={styles["code-container"]}>
@ -76,7 +71,7 @@ function MarketingTrialPayment() {
<p className={styles["sale-description"]}>Save $10 every period</p>
<div className={styles.line} />
<p className={styles["text-description"]}>
You will be charged only <b>$0 for your 7-day trial.</b>{" "}
You will be charged only <b>${(products[0]?.trialPrice / 100).toFixed(2) || 0} for your 7-day trial.</b>{" "}
Subscription <b>renews automatically</b> until cancelled. You{" "}
<b>can cancel at any time</b> before the end of the trial.
</p>

View File

@ -1,246 +0,0 @@
import EmailInput from "@/components/EmailEnterPage/EmailInput";
import styles from "./styles.module.css";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { actions, selectors } from "@/store";
import { useDispatch, useSelector } from "react-redux";
import MainButton from "@/components/MainButton";
import Loader, { LoaderColor } from "@/components/Loader";
import { useAuth } from "@/auth";
import { ApiError, extractErrorMessage, useApi } from "@/api";
import { getClientTimezone } from "@/locales";
import ErrorText from "@/components/ErrorText";
import Title from "@/components/Title";
import NameInput from "@/components/EmailEnterPage/NameInput";
import { useParams } from "react-router-dom";
import routes from "@/routes";
import PaymentForm from "./PaymentForm";
import { getPriceCentsToDollars } from "@/services/price";
import { useSinglePayment } from "@/hooks/payment/useSinglePayment";
function PaymentWithEmailPage() {
const { productId } = useParams();
const { t, i18n } = useTranslation();
// const tokenFromStore = useSelector(selectors.selectToken);
const { signUp, user: userFromStore, token: tokenFromStore } = useAuth();
const api = useApi();
const timezone = getClientTimezone();
const dispatch = useDispatch();
const birthday = useSelector(selectors.selectBirthday);
const locale = i18n.language;
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [isValidEmail, setIsValidEmail] = useState(false);
const [isValidName, setIsValidName] = useState(productId !== "chat.aura");
const [isDisabled, setIsDisabled] = useState(true);
const [isAuth, setIsAuth] = useState(false);
const [apiError, setApiError] = useState<ApiError | null>(null);
const [error, setError] = useState<boolean>(false);
const returnUrl = `${window.location.protocol}//${
window.location.host
}${routes.client.paymentResult()}`;
const [isLoadingAuth, setIsLoadingAuth] = useState<boolean>(false);
const {
product,
paymentIntent,
createSinglePayment,
isLoading: isLoadingSinglePayment,
error: errorSinglePayment,
} = useSinglePayment();
useEffect(() => {
if (
isValidName &&
isValidEmail &&
!(error || apiError || errorSinglePayment?.error)
) {
setIsDisabled(false);
} else {
setIsDisabled(true);
}
}, [
isValidEmail,
email,
isValidName,
name,
error,
apiError,
errorSinglePayment?.error,
]);
const handleValidEmail = (email: string) => {
dispatch(actions.form.addEmail(email));
setEmail(email);
setIsValidEmail(true);
};
const handleValidName = (name: string) => {
setName(name);
setIsValidName(true);
};
const authorization = async () => {
try {
setIsLoadingAuth(true);
const auth = await api.auth({ email, timezone, locale });
const {
auth: { token, user },
} = auth;
signUp(token, user);
const payload = {
user: {
profile_attributes: {
birthday,
full_name: name,
},
},
token,
};
const updatedUser = await api.updateUser(payload).catch((error) => {
console.log("Error: ", error);
});
if (updatedUser?.user) {
dispatch(actions.user.update(updatedUser.user));
}
if (name) {
dispatch(
actions.user.update({
username: name,
})
);
}
dispatch(actions.status.update("registred"));
setIsAuth(true);
const userUpdated = await api.getUser({ token });
setIsLoadingAuth(false);
return { user: userUpdated?.user, token };
} catch (error) {
console.error(error);
if (error instanceof ApiError) {
setApiError(error as ApiError);
} else {
setError(true);
}
setIsLoadingAuth(false);
}
};
const handleClick = async () => {
const authData = await authorization();
if (!authData) {
return;
}
const { user, token } = authData;
await createSinglePayment({
user,
token,
targetProductKey: productId || "",
returnUrl,
});
};
const handleAuthUser = useCallback(async () => {
if (!tokenFromStore.length || !userFromStore) {
return;
}
await createSinglePayment({
user: userFromStore,
token: tokenFromStore,
targetProductKey: productId || "",
returnUrl,
});
}, [
createSinglePayment,
productId,
returnUrl,
tokenFromStore,
userFromStore,
]);
useEffect(() => {
handleAuthUser();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className={`${styles.page} page`}>
{(isLoadingSinglePayment || isLoadingSinglePayment) && (
<Loader color={LoaderColor.Black} />
)}
{!isLoadingSinglePayment &&
!isLoadingAuth &&
paymentIntent &&
"paymentIntent" in paymentIntent &&
!!tokenFromStore.length && (
<>
<Title variant="h1" className={styles.title}>
{getPriceCentsToDollars(product?.price || 0)}$
</Title>
<PaymentForm
stripePublicKey={paymentIntent.paymentIntent.data.public_key}
clientSecret={paymentIntent.paymentIntent.data.client_secret}
returnUrl={`${returnUrl}?redirect_type=${product?.key}`}
/>
</>
)}
{(!tokenFromStore || !paymentIntent) &&
// || (productId !== "chat.aura" && !name.length)
!isLoadingSinglePayment &&
!isLoadingAuth && (
<>
<NameInput
value={name}
placeholder="Your name"
onValid={handleValidName}
onInvalid={() => setIsValidName(productId !== "chat.aura")}
/>
<EmailInput
name="email"
value={email}
placeholder={t("your_email")}
onValid={handleValidEmail}
onInvalid={() => setIsValidEmail(false)}
/>
<MainButton
className={styles.button}
onClick={handleClick}
disabled={isDisabled}
>
{isLoadingSinglePayment && <Loader color={LoaderColor.White} />}
{!isLoadingSinglePayment &&
!(!apiError && !error && !isLoadingSinglePayment && isAuth) &&
t("_continue")}
{!apiError && !error && !isLoadingSinglePayment && isAuth && (
<img
className={styles["success-icon"]}
src="/SuccessIcon.png"
alt="Success Icon"
/>
)}
</MainButton>
</>
)}
{(error || apiError || errorSinglePayment?.error) && (
<Title variant="h3" style={{ color: "red", margin: 0 }}>
Something went wrong:{" "}
{errorSinglePayment?.error?.length && errorSinglePayment?.error}
</Title>
)}
{apiError && (
<ErrorText
size="medium"
isShown={Boolean(apiError)}
message={apiError ? extractErrorMessage(apiError) : null}
/>
)}
</div>
);
}
export default PaymentWithEmailPage;

View File

@ -1,61 +0,0 @@
.page {
/* position: relative; */
position: static;
height: fit-content;
min-height: calc(100dvh - 103px);
/* max-height: -webkit-fill-available; */
display: flex;
justify-items: center;
justify-content: center;
align-items: center;
/* gap: 16px; */
}
.button {
border-radius: 12px;
margin-top: 0;
box-shadow: rgba(0, 0, 0, 0.25) 0px 4px 4px 0px;
height: 50px;
min-height: 0;
background: linear-gradient(
165.54deg,
rgb(20, 19, 51) -33.39%,
rgb(32, 34, 97) 15.89%,
rgb(84, 60, 151) 55.84%,
rgb(105, 57, 162) 74.96%
);
font-size: 18px;
line-height: 21px;
}
.payment-loader {
display: flex;
justify-content: center;
align-items: center;
}
.cross {
position: absolute;
top: -36px;
right: 28px;
width: 22px;
height: 22px;
cursor: pointer;
z-index: 9;
}
.title {
font-size: 27px;
font-weight: 700;
margin: 0;
}
.email {
font-size: 17px;
font-weight: 500;
margin: 0;
}
.success-icon {
height: 100%;
}

View File

@ -1,6 +1,6 @@
import { Elements } from "@stripe/react-stripe-js";
import styles from "./styles.module.css";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import { useEffect, useState } from "react";
import { Stripe, loadStripe } from "@stripe/stripe-js";
import SecurityPayments from "../../TrialPayment/components/SecurityPayments";

View File

@ -1,6 +1,6 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import PaymentForm from "../PaymentWithEmailPage/PaymentForm";
import PaymentForm from "./PaymentForm";
import { getPriceCentsToDollars } from "@/services/price";
import { useSinglePayment } from "@/hooks/payment/useSinglePayment";
import routes from "@/routes";

View File

@ -4,13 +4,11 @@ import PaymentMethodsChoice from "../PaymentMethodsChoice";
import { useEffect, useMemo, useState } from "react";
import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods";
import { Elements } from "@stripe/react-stripe-js";
import ApplePayButton from "@/components/StripePage/ApplePayButton";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
import ApplePayButton from "@/components/PaymentPage/methods/ApplePayButton";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import { Stripe, loadStripe } from "@stripe/stripe-js";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import Loader from "@/components/Loader";
import SecurityPayments from "../SecurityPayments";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
@ -21,6 +19,7 @@ interface IPaymentModalProps {
activeProduct?: IPaywallProduct;
noTrial?: boolean;
returnUrl?: string;
placementKey?: EPlacementKeys;
}
const getPrice = (product: IPaywallProduct | null) => {
@ -34,13 +33,13 @@ function PaymentModal({
activeProduct,
noTrial,
returnUrl,
placementKey = EPlacementKeys["aura.placement.main"],
}: IPaymentModalProps) {
const navigate = useNavigate();
const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null);
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.main"],
placementKey,
});
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
@ -81,14 +80,8 @@ function PaymentModal({
(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]);
}, [products, publicKey]);
if (isLoading) {
return (

View File

@ -68,6 +68,16 @@ function TrialPaymentPage() {
}
}, [dispatch, subPlan, products, activeProduct]);
useEffect(() => {
if (!products.length) return;
const isActiveProduct = products.find(
(product) => product._id === activeProduct?._id
);
if (!activeProduct || !isActiveProduct) {
navigate(routes.client.trialChoice());
}
}, [activeProduct, navigate, products]);
useEffect(() => {
if (["relationship", "married"].includes(flowChoice)) {
setMarginTopTitle(460);

View File

@ -38,7 +38,7 @@ function PaymentDiscountTable() {
<p>Your cost per 14 days after trial:</p>
<div className={styles.side}>
<span className={styles.discount}>$19</span>
<strong>$9</strong>
<strong>${(activeProduct?.price || 0) / 100}</strong>
</div>
</div>
<p className={styles.save}>You save $30</p>

View File

@ -4,11 +4,35 @@ import MainButton from "@/components/MainButton";
import PaymentDiscountTable from "./PaymentDiscountTable";
import Modal from "@/components/Modal";
import PaymentModal from "../TrialPayment/components/PaymentModal";
import { useState } from "react";
import { useEffect, useState } from "react";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
function TrialPaymentWithDiscount() {
const dispatch = useDispatch();
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.secret.discount"],
});
const productFromStore = useSelector(selectors.selectActiveProduct);
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
useEffect(() => {
if (!products.length) return;
const activeProduct = products.find(
(p) => p.trialPrice === productFromStore?.trialPrice
);
if (!activeProduct) {
dispatch(actions.payment.update({ activeProduct: products[0] }));
}
if (activeProduct) {
dispatch(actions.payment.update({ activeProduct }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, products]);
const handleClose = () => {
setIsOpenPaymentModal(false);
};
@ -16,7 +40,9 @@ function TrialPaymentWithDiscount() {
return (
<section className={`${styles.page} page`}>
<Modal open={isOpenPaymentModal} onClose={handleClose}>
<PaymentModal />
<PaymentModal
placementKey={EPlacementKeys["aura.placement.secret.discount"]}
/>
</Modal>
<img
className={styles["party-popper"]}
@ -34,11 +60,13 @@ function TrialPaymentWithDiscount() {
Start your 3-day trial
</MainButton>
<p className={styles.policy}>
By continuing you agree that if you don't cancel prior to the end of the
3-days trial, you will automatically be charged $9 for the introductory
period of 14 days thereafter the standard rate of $9 every 14 days until
you cancel in settings. Learn more about cancellation and refund policy
in Subscription terms.
By continuing you agree that if you don`t cancel prior to the end of the
3-days trial, you will automatically be charged $
{(productFromStore?.price || 0) / 100} for the introductory period of 14
days thereafter the standard rate of $
{(productFromStore?.price || 0) / 100} every 14 days until you cancel in
settings. Learn more about cancellation and refund policy in
Subscription terms.
</p>
</section>
);

View File

@ -11,7 +11,7 @@ import routes from "@/routes";
import { useApi } from "@/api";
import { useAuth } from "@/auth";
import HeaderLogo from "@/components/palmistry/header-logo/header-logo";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import { ResponseGet } from "@/api/resources/SinglePayment";
const currentProductKey = "skip.trial.subscription.aura";

View File

@ -9,6 +9,8 @@ import useTimer from "@/hooks/palmistry/use-timer";
import HeaderLogo from "@/components/palmistry/header-logo/header-logo";
import PaymentModal from "@/components/pages/TrialPayment/components/PaymentModal";
import { selectors } from "@/store";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { useSearchParams } from "react-router-dom";
const getFormattedPrice = (price: number) => {
return (price / 100).toFixed(2);
@ -17,7 +19,12 @@ const getFormattedPrice = (price: number) => {
export default function PaymentScreen() {
const time = useTimer();
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const subscriptionStatus = useSelector(selectors.selectStatus);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded"
? "subscribed"
: "lead";
// const subscriptionStatus = useSelector(selectors.selectStatus);
const steps = useSteps();
@ -247,7 +254,10 @@ export default function PaymentScreen() {
}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModal returnUrl={window.location.href} />
<PaymentModal
returnUrl={window.location.href}
placementKey={EPlacementKeys["aura.placement.palmistry.main"]}
/>
)}
{subscriptionStatus === "subscribed" && (

View File

@ -10,7 +10,7 @@ import routes from "@/routes";
import HeaderLogo from "@/components/palmistry/header-logo/header-logo";
import { useApi } from "@/api";
import { useAuth } from "@/auth";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import { ResponseGet } from "@/api/resources/SinglePayment";
const currentProductKey = "premium.bundle.aura";

View File

@ -21,7 +21,7 @@ export default function StepSubscriptionPlan() {
const dispatch = useDispatch();
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.main"],
placementKey: EPlacementKeys["aura.placement.palmistry.main"],
});
const storedEmail = steps.getStoredValue(Step.Email);
@ -35,23 +35,16 @@ export default function StepSubscriptionPlan() {
}
}, [activeProductFromStore]);
React.useEffect(() => {
if (product) {
const targetProduct = products.find(
(_product) => _product._id === product
);
if (targetProduct) {
dispatch(actions.payment.update({ activeProduct: targetProduct }));
}
}
}, [dispatch, product, products]);
React.useEffect(() => {
setEmail(storedEmail || "");
}, [storedEmail]);
const onNext = () => {
const targetProduct = products.find((_product) => _product._id === product);
if (targetProduct) {
dispatch(actions.payment.update({ activeProduct: targetProduct }));
}
steps.saveCurrent(product);
steps.goNext();
};

View File

@ -15,6 +15,8 @@ type Props = {
onOpenModal: (isOpen: boolean) => void;
};
const isProduction = import.meta.env.MODE === "production";
export default function StepUpload(props: Props) {
const steps = useSteps();
const api = useApi();
@ -606,7 +608,11 @@ export default function StepUpload(props: Props) {
className="palmistry-container__take-palm-button"
disabled={isUpladProcessing}
active={!isUpladProcessing}
onClick={() => setPalmCameraModalIsOpen(true)}
onClick={() =>
isProduction
? setPalmCameraModalIsOpen(true)
: setUploadMenuModalIsOpen(true)
}
isProcessing={isUpladProcessing}
>
{(isUpladProcessing && "Loading photo") || "Take a picture now"}

View File

@ -242,5 +242,208 @@ export const defaultPaywalls: { [key in EPlacementKeys]: IPaywall } = {
"_id": "664542bbfe0a8eb4ee0b4f2e"
}
]
},
"aura.placement.email.marketing": {
"_id": "66565b15e4ad13456f7d04f2",
"key": "aura.paywall.email.marketing",
"name": "Special Offer!",
"products": [
{
"_id": "66565aa8e4ad13456f7d02a6",
"key": "compatibility.pdf.trial.5",
"productId": "prod_PnStTEBzrPLgvL",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"name": "Сompatibility AURA | Trial $0.50",
"description": "Description",
"price": 1900,
"discountPrice": null,
"isDiscount": false,
"discountPriceId": null,
"trialDuration": 3,
"trialPrice": 50,
"isFreeTrial": false,
"isTrial": true,
"trialPriceId": "price_1PG1vKIlX4lgwUxrVhBf6eIq",
"type": "subscription"
}
],
"properties": [
{
"key": "text.old.price",
"value": "up to $13.67",
"_id": "66565b15e4ad13456f7d04f3"
}
]
},
"aura.placement.secret.discount": {
"_id": "6658973fef0d180993cdbcc1",
"key": "aura.paywall.secret.discount",
"name": "Secret Discount",
"products": [
{
"_id": "66589439ef0d180993cdb72f",
"key": "compatibility.secret.discount.trial.0",
"productId": "prod_PnStTEBzrPLgvL",
"priceId": "price_1PMAREIlX4lgwUxrmvtHWth1",
"name": "Сompatibility AURA Secret Discount | Trial $0.99",
"description": "Description",
"price": 900,
"discountPrice": null,
"isDiscount": false,
"discountPriceId": null,
"trialDuration": 3,
"trialPrice": 99,
"isFreeTrial": false,
"isTrial": true,
"trialPriceId": "price_1PFiSkIlX4lgwUxrVel0l445",
"type": "subscription"
},
{
"_id": "6658946cef0d180993cdb79f",
"key": "compatibility.secret.discount.trial.1",
"productId": "prod_PnStTEBzrPLgvL",
"priceId": "price_1PMAREIlX4lgwUxrmvtHWth1",
"name": "Сompatibility AURA Secret Discount | Trial $5.00",
"description": "Description",
"price": 900,
"discountPrice": null,
"isDiscount": false,
"discountPriceId": null,
"trialDuration": 3,
"trialPrice": 500,
"isFreeTrial": false,
"isTrial": true,
"trialPriceId": "price_1PFyASIlX4lgwUxriLtzsk05",
"type": "subscription"
},
{
"_id": "66589544ef0d180993cdb7dc",
"key": "compatibility.secret.discount.trial.2",
"productId": "prod_PnStTEBzrPLgvL",
"priceId": "price_1PMAREIlX4lgwUxrmvtHWth1",
"name": "Сompatibility AURA Secret Discount | Trial $9.00",
"description": "Description",
"price": 900,
"discountPrice": null,
"isDiscount": false,
"discountPriceId": null,
"trialDuration": 3,
"trialPrice": 900,
"isFreeTrial": false,
"isTrial": true,
"trialPriceId": "price_1PFyAuIlX4lgwUxr4fFoauCV",
"type": "subscription"
},
{
"_id": "66589591ef0d180993cdb88e",
"key": "compatibility.secret.discount.trial.3",
"productId": "prod_PnStTEBzrPLgvL",
"priceId": "price_1PMAREIlX4lgwUxrmvtHWth1",
"name": "Сompatibility AURA Secret Discount | Trial $13.76",
"description": "Description",
"price": 900,
"discountPrice": null,
"isDiscount": false,
"discountPriceId": null,
"trialDuration": 3,
"trialPrice": 1376,
"isFreeTrial": false,
"isTrial": true,
"trialPriceId": "price_1PFyBQIlX4lgwUxrMnEUkV73",
"type": "subscription"
}
],
"properties": []
},
"aura.placement.palmistry.main": {
"_id": "66565a0ee4ad13456f7d0079",
"key": "aura.paywall.palmistry.main",
"name": "paywall without free trial",
"products": [
{
"_id": "65ff043dfc0fcfc4be550035",
"key": "compatibility.pdf.trial.0",
"productId": "prod_PnStTEBzrPLgvL",
"name": "Сompatibility AURA | Trial $0.99",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"type": "subscription",
"description": "Description",
"discountPrice": null,
"discountPriceId": null,
"isDiscount": false,
"isFreeTrial": false,
"isTrial": true,
"price": 1900,
"trialDuration": 3,
"trialPrice": 99,
"trialPriceId": "price_1PFiSkIlX4lgwUxrVel0l445"
},
{
"_id": "66420b6e859ff1199d3a6e88",
"key": "compatibility.pdf.trial.1",
"productId": "prod_PnStTEBzrPLgvL",
"name": "Сompatibility AURA | Trial $5.00",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"type": "subscription",
"description": "Description",
"discountPrice": null,
"discountPriceId": null,
"isDiscount": false,
"isFreeTrial": false,
"isTrial": true,
"price": 1900,
"trialDuration": 3,
"trialPrice": 500,
"trialPriceId": "price_1PFyASIlX4lgwUxriLtzsk05"
},
{
"_id": "66420be1859ff1199d3a6e89",
"key": "compatibility.pdf.trial.2",
"productId": "prod_PnStTEBzrPLgvL",
"name": "Сompatibility AURA | Trial $9.00",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"type": "subscription",
"description": "Description",
"discountPrice": null,
"discountPriceId": null,
"isDiscount": false,
"isFreeTrial": false,
"isTrial": true,
"price": 1900,
"trialDuration": 3,
"trialPrice": 900,
"trialPriceId": "price_1PFyAuIlX4lgwUxr4fFoauCV"
},
{
"_id": "66420c1c859ff1199d3a6e8a",
"key": "compatibility.pdf.trial.3",
"productId": "prod_PnStTEBzrPLgvL",
"name": "Сompatibility AURA | Trial $13.76",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"type": "subscription",
"description": "Description",
"discountPrice": null,
"discountPriceId": null,
"isDiscount": false,
"isFreeTrial": false,
"isTrial": true,
"price": 1900,
"trialDuration": 3,
"trialPrice": 1376,
"trialPriceId": "price_1PFyBQIlX4lgwUxrMnEUkV73"
}
],
"properties": [
{
"key": "text.0",
"value": "We've helped millions of people to\nreveal the destiny of their love life\nand what the future holds for them\nand their families.",
"_id": "664542bbfe0a8eb4ee0b4f27"
},
{
"key": "text.1",
"value": "It costs us $13.21 to compensate our AURA\nemployees for the trial, but please choose the\namount you are comfortable with.",
"_id": "664542bbfe0a8eb4ee0b4f29"
}
]
}
}

View File

@ -57,7 +57,6 @@ const routes = {
paymentResult: () => [host, "payment", "result"].join("/"),
paymentSuccess: () => [host, "payment", "success"].join("/"),
paymentFail: () => [host, "payment", "fail"].join("/"),
paymentStripe: () => [host, "payment", "stripe"].join("/"),
wallpaper: () => [host, "wallpaper"].join("/"),
static: () => [host, "static", ":typeId"].join("/"),
legal: (type: string) => [host, "static", type].join("/"),
@ -349,7 +348,6 @@ export const withoutFooterRoutes = [
routes.client.paymentResult(),
routes.client.paymentSuccess(),
routes.client.paymentFail(),
routes.client.paymentStripe(),
routes.client.magicBall(),
routes.client.horoscopeBestiesResult(),
routes.client.predictionMoonResult(),

View File

@ -19,9 +19,15 @@ type IPayloadUpdatePaywall = {
const initialState: TPaywalls = {
"aura.placement.main": null,
"aura.placement.redesign.main": null,
"aura.placement.email.marketing": null,
"aura.placement.secret.discount": null,
"aura.placement.palmistry.main": null,
isMustUpdate: {
"aura.placement.main": true,
"aura.placement.redesign.main": true,
"aura.placement.email.marketing": true,
"aura.placement.secret.discount": true,
"aura.placement.palmistry.main": true
},
}