Merge branch 'develop' into 'main'

Develop

See merge request witapp/aura-webapp!109
This commit is contained in:
Daniil Chemerkin 2024-04-25 14:35:49 +00:00
commit 062bea2f7f
16 changed files with 103 additions and 277 deletions

View File

@ -19,17 +19,9 @@ export interface StripeReceiptPayload extends AuthPayload {
}; };
} }
export interface PayPalReceiptPayload extends AuthPayload {
subscription_receipt: {
sub_plan_id: string;
};
way: "paypal";
}
export type Payload = export type Payload =
| AppleReceiptPayload | AppleReceiptPayload
| StripeReceiptPayload | StripeReceiptPayload
| PayPalReceiptPayload;
export interface Response { export interface Response {
subscription_receipt: SubscriptionReceipt; subscription_receipt: SubscriptionReceipt;
@ -58,19 +50,12 @@ export interface SubscriptionReceipt {
app_bundle_id: string; app_bundle_id: string;
autorenewable: boolean; autorenewable: boolean;
error: string; error: string;
links?: IPayPalLink[];
stripe_status?: string; stripe_status?: string;
checkout_url?: string; checkout_url?: string;
checkout_session?: unknown; checkout_session?: unknown;
}; };
} }
interface IPayPalLink {
href: string;
rel: "approve" | "edit" | "self";
method: "GET" | "PATCH";
}
function createRequest({ function createRequest({
token, token,
receiptData, receiptData,
@ -101,14 +86,6 @@ function getDataPayload(payload: Payload) {
}, },
}; };
} }
if ("way" in payload && payload.way === "paypal") {
return {
way: "paypal",
subscription_receipt: {
sub_plan_id: payload.subscription_receipt.sub_plan_id,
},
};
}
if ("way" in payload && payload.way === "stripe") { if ("way" in payload && payload.way === "stripe") {
return { return {
way: "stripe", way: "stripe",

View File

@ -1,15 +0,0 @@
import { useTranslation } from "react-i18next";
import MainButton from "@/components/MainButton";
interface IPayPalButtonProps {
onClick: () => void;
}
export function PayPalButton({ onClick }: IPayPalButtonProps): JSX.Element {
const { t } = useTranslation();
return (
<MainButton color="blue" onClick={onClick}>
{t("payPal")}
</MainButton>
);
}

View File

@ -14,7 +14,6 @@ import ApplePayButton from "@/components/StripePage/ApplePayButton";
import SubPlanInformation from "@/components/SubPlanInformation"; import SubPlanInformation from "@/components/SubPlanInformation";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans";
import routes from "@/routes"; import routes from "@/routes";
interface StripeModalProps { interface StripeModalProps {
@ -39,7 +38,6 @@ StripeModalProps): JSX.Element {
const email = useSelector(selectors.selectUser).email; const email = useSelector(selectors.selectUser).email;
const [stripePromise, setStripePromise] = const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null); useState<Promise<Stripe | null> | null>(null);
const [subPlans, setSubPlans] = useState<ISubscriptionPlan[] | null>(null);
const [clientSecret, setClientSecret] = useState<string>(""); const [clientSecret, setClientSecret] = useState<string>("");
const [subscriptionReceiptId, setSubscriptionReceiptId] = const [subscriptionReceiptId, setSubscriptionReceiptId] =
useState<string>(""); useState<string>("");
@ -53,7 +51,6 @@ StripeModalProps): JSX.Element {
const siteConfig = await api.getAppConfig({ bundleId: "auraweb" }); const siteConfig = await api.getAppConfig({ bundleId: "auraweb" });
setStripePromise(loadStripe(siteConfig.data.stripe_public_key)); setStripePromise(loadStripe(siteConfig.data.stripe_public_key));
const { sub_plans } = await api.getSubscriptionPlans({ locale }); const { sub_plans } = await api.getSubscriptionPlans({ locale });
setSubPlans(sub_plans);
const isActiveSubPlan = sub_plans.find( const isActiveSubPlan = sub_plans.find(
(subPlan) => subPlan.id === activeSubPlan?.id (subPlan) => subPlan.id === activeSubPlan?.id
); );
@ -78,6 +75,7 @@ StripeModalProps): JSX.Element {
setClientSecret(client_secret); setClientSecret(client_secret);
setIsLoading(false); setIsLoading(false);
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, token]); }, [api, token]);
const handleClose = () => { const handleClose = () => {
@ -107,7 +105,7 @@ StripeModalProps): JSX.Element {
subscriptionReceiptId={subscriptionReceiptId} subscriptionReceiptId={subscriptionReceiptId}
/> />
{activeSubPlan && ( {activeSubPlan && (
<SubPlanInformation subPlan={activeSubPlan} subPlans={subPlans} /> <SubPlanInformation subPlan={activeSubPlan} />
)} )}
<CheckoutForm subscriptionReceiptId={subscriptionReceiptId} /> <CheckoutForm subscriptionReceiptId={subscriptionReceiptId} />
</Elements> </Elements>

View File

@ -16,6 +16,7 @@ interface ApplePayButtonProps {
client_secret: string; client_secret: string;
subscriptionReceiptId?: string; subscriptionReceiptId?: string;
returnUrl?: string; returnUrl?: string;
setCanMakePayment?: (isCanMakePayment: boolean) => void;
} }
function ApplePayButton({ function ApplePayButton({
@ -23,6 +24,7 @@ function ApplePayButton({
client_secret, client_secret,
subscriptionReceiptId, subscriptionReceiptId,
returnUrl, returnUrl,
setCanMakePayment,
}: ApplePayButtonProps) { }: ApplePayButtonProps) {
const stripe = useStripe(); const stripe = useStripe();
const elements = useElements(); const elements = useElements();
@ -58,6 +60,7 @@ function ApplePayButton({
pr.canMakePayment().then((result) => { pr.canMakePayment().then((result) => {
if (result) { if (result) {
setPaymentRequest(pr); setPaymentRequest(pr);
setCanMakePayment?.(true);
} }
}); });
@ -80,7 +83,8 @@ function ApplePayButton({
return e.complete("fail"); return e.complete("fail");
} }
navigate( navigate(
returnUrl || `${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=succeeded` returnUrl ||
`${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=succeeded`
); );
e.complete("success"); e.complete("success");
// Show a success message to your customer // Show a success message to your customer
@ -89,6 +93,7 @@ function ApplePayButton({
// payment_intent.succeeded event that handles any business critical // payment_intent.succeeded event that handles any business critical
// post-payment actions. // post-payment actions.
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
activeSubPlan, activeSubPlan,
client_secret, client_secret,

View File

@ -13,7 +13,6 @@ import routes from "@/routes";
import SubPlanInformation from "../SubPlanInformation"; import SubPlanInformation from "../SubPlanInformation";
import Title from "../Title"; import Title from "../Title";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans";
import ApplePayButton from "./ApplePayButton"; import ApplePayButton from "./ApplePayButton";
export function StripePage(): JSX.Element { export function StripePage(): JSX.Element {
@ -26,7 +25,6 @@ export function StripePage(): JSX.Element {
const email = useSelector(selectors.selectUser).email; const email = useSelector(selectors.selectUser).email;
const [stripePromise, setStripePromise] = const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null); useState<Promise<Stripe | null> | null>(null);
const [subPlans, setSubPlans] = useState<ISubscriptionPlan[] | null>(null);
const [clientSecret, setClientSecret] = useState<string>(""); const [clientSecret, setClientSecret] = useState<string>("");
const [subscriptionReceiptId, setSubscriptionReceiptId] = const [subscriptionReceiptId, setSubscriptionReceiptId] =
useState<string>(""); useState<string>("");
@ -40,7 +38,6 @@ export function StripePage(): JSX.Element {
const siteConfig = await api.getAppConfig({ bundleId: "auraweb" }); const siteConfig = await api.getAppConfig({ bundleId: "auraweb" });
setStripePromise(loadStripe(siteConfig.data.stripe_public_key)); setStripePromise(loadStripe(siteConfig.data.stripe_public_key));
const { sub_plans } = await api.getSubscriptionPlans({ locale }); const { sub_plans } = await api.getSubscriptionPlans({ locale });
setSubPlans(sub_plans);
const isActiveSubPlan = sub_plans.find( const isActiveSubPlan = sub_plans.find(
(subPlan) => subPlan.id === activeSubPlan?.id (subPlan) => subPlan.id === activeSubPlan?.id
); );
@ -65,6 +62,7 @@ export function StripePage(): JSX.Element {
setClientSecret(client_secret); setClientSecret(client_secret);
setIsLoading(false); setIsLoading(false);
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, token]); }, [api, token]);
return ( return (
@ -90,7 +88,7 @@ export function StripePage(): JSX.Element {
subscriptionReceiptId={subscriptionReceiptId} subscriptionReceiptId={subscriptionReceiptId}
/> />
{activeSubPlan && ( {activeSubPlan && (
<SubPlanInformation subPlan={activeSubPlan} subPlans={subPlans} /> <SubPlanInformation subPlan={activeSubPlan} />
)} )}
<CheckoutForm subscriptionReceiptId={subscriptionReceiptId} /> <CheckoutForm subscriptionReceiptId={subscriptionReceiptId} />
</Elements> </Elements>

View File

@ -2,16 +2,10 @@ import { useTranslation } from "react-i18next";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans"; import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans";
import TotalToday from "./TotalToday"; import TotalToday from "./TotalToday";
import MainButton from "../MainButton";
import { useApi } from "@/api";
import { useAuth } from "@/auth";
import { useEffect, useState } from "react";
import Loader from "../Loader";
import ApplePayButton from "../StripePage/ApplePayButton"; import ApplePayButton from "../StripePage/ApplePayButton";
interface ISubPlanInformationProps { interface ISubPlanInformationProps {
subPlan: ISubscriptionPlan; subPlan: ISubscriptionPlan;
subPlans: ISubscriptionPlan[] | null;
client_secret?: string; client_secret?: string;
} }
@ -23,58 +17,9 @@ const getPrice = (plan: ISubscriptionPlan): string => {
function SubPlanInformation({ function SubPlanInformation({
subPlan, subPlan,
subPlans,
client_secret, client_secret,
}: ISubPlanInformationProps): JSX.Element { }: ISubPlanInformationProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const api = useApi();
const { token } = useAuth();
const [payPalSubPlan, setPayPalSubPlan] = useState<ISubscriptionPlan>();
const [errors, setErrors] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
if (!subPlans) return;
const paypalPlan = subPlans
.filter((plan: ISubscriptionPlan) => plan.provider === "paypal")
.filter((plan: ISubscriptionPlan) => {
if (subPlan?.trial && plan?.trial) return true;
if (!subPlan?.trial && !plan?.trial) return true;
return false;
})
.find((plan: ISubscriptionPlan) => {
if (subPlan?.trial && plan?.trial) {
return plan?.trial?.price_cents === subPlan?.trial?.price_cents;
}
if (!subPlan?.trial && !plan?.trial) {
return plan?.name === subPlan?.name;
}
return false;
});
setPayPalSubPlan(paypalPlan);
}, [subPlan?.name, subPlan?.trial, subPlans]);
const handlePayPalButton = async () => {
setIsLoading(true);
const {
subscription_receipt: { data },
} = await api.createSubscriptionReceipt({
token,
way: "paypal",
subscription_receipt: {
sub_plan_id: payPalSubPlan?.id || "paypal.6",
},
});
if (!data?.links) {
return setErrors("Something went wrong. Please try again later.");
}
const link = data.links.find((link) => link.rel === "approve");
if (!link) {
return setErrors("Something went wrong. Please try again later.");
}
setIsLoading(false);
window.location.href = link.href;
};
return ( return (
<div className={styles.container}> <div className={styles.container}>
@ -82,21 +27,9 @@ function SubPlanInformation({
{client_secret && ( {client_secret && (
<ApplePayButton activeSubPlan={subPlan} client_secret={client_secret} /> <ApplePayButton activeSubPlan={subPlan} client_secret={client_secret} />
)} )}
{payPalSubPlan && (
<MainButton
type="button"
className={styles["pay-pal-button"]}
onClick={handlePayPalButton}
>
{!isLoading && <img src="/paypal-logo.svg" alt="PayPal Button" />}
{isLoading && <Loader />}
</MainButton>
)}
{/* <ApplePayButton activeSubPlan={subPlan} /> */}
<p className={styles.description}> <p className={styles.description}>
{t("auweb.pay.information").replaceAll("%@", getPrice(subPlan))}. {t("auweb.pay.information").replaceAll("%@", getPrice(subPlan))}.
</p> </p>
{!!errors.length && <p className={styles.errors}>{errors}</p>}
</div> </div>
); );
} }

View File

@ -1,13 +1,15 @@
import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods"; import { EPaymentMethod, IPaymentMethod } from "@/data/paymentMethods";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
interface IPaymentMethodsChoiceProps { interface IPaymentMethodsChoiceProps {
selectedPaymentMethod: EPaymentMethod; selectedPaymentMethod: EPaymentMethod;
onSelectPaymentMethod: (method: EPaymentMethod) => void; onSelectPaymentMethod: (method: EPaymentMethod) => void;
paymentMethods: IPaymentMethod[];
} }
function PaymentMethodsChoice({ function PaymentMethodsChoice({
selectedPaymentMethod, selectedPaymentMethod,
paymentMethods,
onSelectPaymentMethod, onSelectPaymentMethod,
}: IPaymentMethodsChoiceProps) { }: IPaymentMethodsChoiceProps) {
return ( return (

View File

@ -1,7 +1,7 @@
.payment-methods { .payment-methods {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-around;
gap: 8px; gap: 8px;
} }

View File

@ -1,28 +0,0 @@
import MainButton from "@/components/MainButton";
import styles from "./styles.module.css";
import Loader from "@/components/Loader";
interface IPayPalButton {
isLoading: boolean;
handlePayPalButton: () => void;
}
function PayPalButton({ isLoading, handlePayPalButton }: IPayPalButton) {
return (
<MainButton
type="button"
className={styles["pay-pal-button"]}
onClick={handlePayPalButton}
>
{!isLoading && (
<div className={styles.content}>
<img src="/paypal-logo.svg" alt="PayPal Button" />
<p>Buy Now</p>
</div>
)}
{isLoading && <Loader />}
</MainButton>
);
}
export default PayPalButton;

View File

@ -1,36 +0,0 @@
.pay-pal-button {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffc43a;
border-radius: 7px;
max-width: 300px;
}
.content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.content > p {
margin-top: 6px;
font-size: 18px;
color: #2f2e37;
}
.content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.content > p {
margin-top: 6px;
font-size: 18px;
color: #2f2e37;
}

View File

@ -1,8 +1,8 @@
import Title from "@/components/Title"; import Title from "@/components/Title";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import PaymentMethodsChoice from "../PaymentMethodsChoice"; import PaymentMethodsChoice from "../PaymentMethodsChoice";
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { EPaymentMethod } from "@/data/paymentMethods"; import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods";
import { Elements } from "@stripe/react-stripe-js"; import { Elements } from "@stripe/react-stripe-js";
import ApplePayButton from "@/components/StripePage/ApplePayButton"; import ApplePayButton from "@/components/StripePage/ApplePayButton";
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm"; import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
@ -18,7 +18,6 @@ import { useAuth } from "@/auth";
import Loader from "@/components/Loader"; import Loader from "@/components/Loader";
import { getPriceFromTrial } from "@/services/price"; import { getPriceFromTrial } from "@/services/price";
import SecurityPayments from "../SecurityPayments"; import SecurityPayments from "../SecurityPayments";
import PayPalButton from "./components/PayPalButton";
interface IPaymentModalProps { interface IPaymentModalProps {
activeSubscriptionPlan?: ISubscriptionPlan; activeSubscriptionPlan?: ISubscriptionPlan;
@ -26,7 +25,11 @@ interface IPaymentModalProps {
returnUrl?: string; returnUrl?: string;
} }
function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentModalProps) { function PaymentModal({
activeSubscriptionPlan,
noTrial,
returnUrl,
}: IPaymentModalProps) {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const locale = i18n.language; const locale = i18n.language;
const api = useApi(); const api = useApi();
@ -36,20 +39,23 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
const activeSubPlan = activeSubscriptionPlan const activeSubPlan = activeSubscriptionPlan
? activeSubscriptionPlan ? activeSubscriptionPlan
: activeSubPlanFromStore; : activeSubPlanFromStore;
const [payPalSubPlan, setPayPalSubPlan] = useState<ISubscriptionPlan>();
const [stripePromise, setStripePromise] = const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null); useState<Promise<Stripe | null> | null>(null);
const [clientSecret, setClientSecret] = useState<string>(""); const [clientSecret, setClientSecret] = useState<string>("");
const [subscriptionReceiptId, setSubscriptionReceiptId] = const [subscriptionReceiptId, setSubscriptionReceiptId] =
useState<string>(""); useState<string>("");
const [subPlans, setSubPlans] = useState<ISubscriptionPlan[] | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isLoadingPayPal, setIsLoadingPayPal] = useState(false);
const [errors, setErrors] = useState<string>("");
const [isError, setIsError] = useState<boolean>(false); const [isError, setIsError] = useState<boolean>(false);
const paymentMethodsButtons = useMemo(() => {
// return paymentMethods.filter(
// (method) => method.id !== EPaymentMethod.PAYMENT_BUTTONS
// );
return paymentMethods;
}, []);
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(
EPaymentMethod.PAYPAL_OR_APPLE_PAY EPaymentMethod.PAYMENT_BUTTONS
); );
const onSelectPaymentMethod = (method: EPaymentMethod) => { const onSelectPaymentMethod = (method: EPaymentMethod) => {
@ -59,12 +65,8 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const siteConfig = await api.getAppConfig({ bundleId: "auraweb" }); const siteConfig = await api.getAppConfig({ bundleId: "auraweb" });
// const isProduction = import.meta.env.MODE === "production";
// const stripePublicKey = isProduction ? siteConfig.data.stripe_public_key : "pk_test_51Ndqf4IlX4lgwUxrlLWqfYWpo0Ic0BV7DfiZxfMYy838IZP8NLrwwZ5i0HhhbOQBGoQZe4Rrel1ziEk8mhQ2TE3500ETWZPBva";
// setStripePromise(loadStripe(stripePublicKey));
setStripePromise(loadStripe(siteConfig.data.stripe_public_key)); setStripePromise(loadStripe(siteConfig.data.stripe_public_key));
const { sub_plans } = await api.getSubscriptionPlans({ locale }); const { sub_plans } = await api.getSubscriptionPlans({ locale });
setSubPlans(sub_plans);
const isActiveSubPlan = sub_plans.find( const isActiveSubPlan = sub_plans.find(
(subPlan) => subPlan.id === activeSubPlan?.id (subPlan) => subPlan.id === activeSubPlan?.id
); );
@ -100,50 +102,6 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
})(); })();
}, [activeSubPlan?.id, api, token]); }, [activeSubPlan?.id, api, token]);
useEffect(() => {
if (!subPlans) return;
const paypalPlan = subPlans
.filter((plan: ISubscriptionPlan) => plan.provider === "paypal")
.filter((plan: ISubscriptionPlan) => {
if (activeSubPlan?.trial && plan?.trial) return true;
if (!activeSubPlan?.trial && !plan?.trial) return true;
return false;
})
.find((plan: ISubscriptionPlan) => {
if (activeSubPlan?.trial && plan?.trial) {
return plan?.trial?.price_cents === activeSubPlan?.trial?.price_cents;
}
if (!activeSubPlan?.trial && !plan?.trial) {
return plan?.name === activeSubPlan?.name;
}
return false;
});
setPayPalSubPlan(paypalPlan);
}, [activeSubPlan?.name, activeSubPlan?.trial, subPlans]);
const handlePayPalButton = async () => {
setIsLoadingPayPal(true);
const {
subscription_receipt: { data },
} = await api.createSubscriptionReceipt({
token,
way: "paypal",
subscription_receipt: {
sub_plan_id: payPalSubPlan?.id || "paypal.6",
},
});
if (!data?.links) {
return setErrors("Something went wrong. Please try again later.");
}
const link = data.links.find((link) => link.rel === "approve");
if (!link) {
return setErrors("Something went wrong. Please try again later.");
}
setIsLoadingPayPal(false);
window.location.href = link.href;
// window.open(link.href, '_blank');
};
if (isLoading) { if (isLoading) {
return ( return (
<div className={styles["payment-modal"]}> <div className={styles["payment-modal"]}>
@ -170,6 +128,7 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
Choose payment method Choose payment method
</Title> </Title>
<PaymentMethodsChoice <PaymentMethodsChoice
paymentMethods={paymentMethodsButtons}
selectedPaymentMethod={selectedPaymentMethod} selectedPaymentMethod={selectedPaymentMethod}
onSelectPaymentMethod={onSelectPaymentMethod} onSelectPaymentMethod={onSelectPaymentMethod}
/> />
@ -180,7 +139,8 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
<p className={styles["sub-plan-description"]}> <p className={styles["sub-plan-description"]}>
You will be charged only{" "} You will be charged only{" "}
<b> <b>
${getPriceFromTrial(activeSubPlan?.trial)} for your 3-day trial. ${getPriceFromTrial(activeSubPlan?.trial)} for your 3-day
trial.
</b> </b>
</p> </p>
<p className={styles["sub-plan-description"]}> <p className={styles["sub-plan-description"]}>
@ -197,26 +157,22 @@ function PaymentModal({ activeSubscriptionPlan, noTrial, returnUrl }: IPaymentMo
<div className={styles["payment-method-container"]}> <div className={styles["payment-method-container"]}>
{stripePromise && clientSecret && ( {stripePromise && clientSecret && (
<Elements stripe={stripePromise} options={{ clientSecret }}> <Elements stripe={stripePromise} options={{ clientSecret }}>
{selectedPaymentMethod === EPaymentMethod.PAYPAL_OR_APPLE_PAY && ( {selectedPaymentMethod === EPaymentMethod.PAYMENT_BUTTONS && (
<div className={styles["payment-method"]}> <div className={styles["payment-method"]}>
{payPalSubPlan && (
<PayPalButton
isLoading={isLoadingPayPal}
handlePayPalButton={handlePayPalButton}
/>
)}
<ApplePayButton <ApplePayButton
activeSubPlan={activeSubPlan} activeSubPlan={activeSubPlan}
client_secret={clientSecret} client_secret={clientSecret}
subscriptionReceiptId={subscriptionReceiptId} subscriptionReceiptId={subscriptionReceiptId}
returnUrl={window.location.href} returnUrl={window.location.href}
/> />
{!!errors.length && <p className={styles.errors}>{errors}</p>}
</div> </div>
)} )}
{selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && (
<CheckoutForm subscriptionReceiptId={subscriptionReceiptId} returnUrl={returnUrl} /> <CheckoutForm
subscriptionReceiptId={subscriptionReceiptId}
returnUrl={returnUrl}
/>
)} )}
</Elements> </Elements>
)} )}

View File

@ -24,6 +24,9 @@
.payment-method-container { .payment-method-container {
width: 100%; width: 100%;
display: flex;
flex-direction: column;
gap: 24px;
} }
.address { .address {
@ -41,4 +44,4 @@
.address { .address {
color: gray; color: gray;
font-size: 10px; font-size: 10px;
} }

View File

@ -1,21 +1,21 @@
import React from 'react'; import React, { useMemo } from "react";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { selectors } from "@/store"; import { selectors } from "@/store";
import useSteps, { Step } from '@/hooks/palmistry/use-steps'; import useSteps, { Step } from "@/hooks/palmistry/use-steps";
import Button from '@/components/palmistry/button/button'; import Button from "@/components/palmistry/button/button";
import EmailHeader from '@/components/palmistry/email-header/email-header'; import EmailHeader from "@/components/palmistry/email-header/email-header";
import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans"; import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans";
import { actions } from "@/store"; import { actions } from "@/store";
import { useApi } from "@/api"; import { useApi } from "@/api";
const bestPlanId = 'stripe.15'; const bestPlanId = "stripe.15";
const getFormattedPrice = (plan: ISubscriptionPlan) => { const getFormattedPrice = (plan: ISubscriptionPlan) => {
return (plan.trial!.price_cents / 100).toFixed(2); return (plan.trial!.price_cents / 100).toFixed(2);
} };
export default function StepSubscriptionPlan() { export default function StepSubscriptionPlan() {
const steps = useSteps(); const steps = useSteps();
@ -23,11 +23,14 @@ export default function StepSubscriptionPlan() {
const api = useApi(); const api = useApi();
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const activeSubPlanFromStore = useSelector(selectors.selectActiveSubPlan); const activeSubPlanFromStore = useSelector(selectors.selectActiveSubPlan);
const allowedPlans = useMemo(() => ["stripe.37"], []);
const storedEmail = steps.getStoredValue(Step.Email); const storedEmail = steps.getStoredValue(Step.Email);
const [subscriptionPlan, setSubscriptionPlan] = React.useState(''); const [subscriptionPlan, setSubscriptionPlan] = React.useState("");
const [subscriptionPlans, setSubscriptionPlans] = React.useState<ISubscriptionPlan[]>([]); const [subscriptionPlans, setSubscriptionPlans] = React.useState<
ISubscriptionPlan[]
>([]);
const [email, setEmail] = React.useState(steps.getStoredValue(Step.Email)); const [email, setEmail] = React.useState(steps.getStoredValue(Step.Email));
const locale = i18n.language; const locale = i18n.language;
@ -42,7 +45,10 @@ export default function StepSubscriptionPlan() {
(async () => { (async () => {
const { sub_plans } = await api.getSubscriptionPlans({ locale }); const { sub_plans } = await api.getSubscriptionPlans({ locale });
const plans = sub_plans const plans = sub_plans
.filter((plan: ISubscriptionPlan) => plan.provider === "stripe") .filter(
(plan: ISubscriptionPlan) =>
plan.provider === "stripe" && !plan.name.includes("(test)")
)
.sort((a, b) => { .sort((a, b) => {
if (!a.trial || !b.trial) { if (!a.trial || !b.trial) {
return 0; return 0;
@ -55,13 +61,19 @@ export default function StepSubscriptionPlan() {
} }
return 0; return 0;
}); });
setSubscriptionPlans(plans.filter((plan) => plan.trial?.price_cents)); setSubscriptionPlans(
plans.filter(
(plan) => plan.trial?.price_cents || allowedPlans.includes(plan.id)
)
);
})(); })();
}, [api, locale]); }, [allowedPlans, api, locale]);
React.useEffect(() => { React.useEffect(() => {
if (subscriptionPlan) { if (subscriptionPlan) {
const targetSubPlan = subscriptionPlans.find((sub_plan) => sub_plan.id === subscriptionPlan); const targetSubPlan = subscriptionPlans.find(
(sub_plan) => sub_plan.id === subscriptionPlan
);
if (targetSubPlan) { if (targetSubPlan) {
dispatch(actions.payment.update({ activeSubPlan: targetSubPlan })); dispatch(actions.payment.update({ activeSubPlan: targetSubPlan }));
@ -70,7 +82,7 @@ export default function StepSubscriptionPlan() {
}, [subscriptionPlan]); }, [subscriptionPlan]);
React.useEffect(() => { React.useEffect(() => {
setEmail(storedEmail || ''); setEmail(storedEmail || "");
}, [storedEmail]); }, [storedEmail]);
const onNext = () => { const onNext = () => {
@ -80,15 +92,21 @@ export default function StepSubscriptionPlan() {
return ( return (
<> <>
<EmailHeader email={email}/> <EmailHeader email={email} />
<div className="palmistry-container__title"> <div className="palmistry-container__title">
We've helped millions of people to reveal the destiny of their love life and what the future holds for them and We've helped millions of people to reveal the destiny of their love life
their families. and what the future holds for them and their families.
</div> </div>
<div className="palmistry-container__image"> <div className="palmistry-container__image">
<svg width="153" height="133" viewBox="0 0 153 133" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg
width="153"
height="133"
viewBox="0 0 153 133"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_16903_24136)"> <g clipPath="url(#clip0_16903_24136)">
<rect width="153" height="133" fill=""></rect> <rect width="153" height="133" fill=""></rect>
<path <path
@ -131,7 +149,11 @@ export default function StepSubscriptionPlan() {
{subscriptionPlans.map((plan) => ( {subscriptionPlans.map((plan) => (
<div <div
key={plan.id} key={plan.id}
className={`palmistry-container__plan ${subscriptionPlan === plan.id ? 'palmistry-container__plan_active' : ''}`} className={`palmistry-container__plan ${
subscriptionPlan === plan.id
? "palmistry-container__plan_active"
: ""
}`}
onClick={() => setSubscriptionPlan(plan.id)} onClick={() => setSubscriptionPlan(plan.id)}
> >
<h3>${getFormattedPrice(plan)}</h3> <h3>${getFormattedPrice(plan)}</h3>
@ -139,11 +161,23 @@ export default function StepSubscriptionPlan() {
))} ))}
</div> </div>
<span className={`palmistry-container__subscription-text ${subscriptionPlan === bestPlanId ? 'palmistry-container__subscription-text_active' : ''}`}> <span
It costs us $13.21 to compensate our AURA employees for the trial, but please choose the amount you are comfortable with. className={`palmistry-container__subscription-text ${
subscriptionPlan === bestPlanId
? "palmistry-container__subscription-text_active"
: ""
}`}
>
It costs us $13.21 to compensate our AURA employees for the trial, but
please choose the amount you are comfortable with.
</span> </span>
<Button className="palmistry-container__button" type="button" onClick={onNext} active> <Button
className="palmistry-container__button"
type="button"
onClick={onNext}
active
>
Continue Continue
</Button> </Button>
</> </>

View File

@ -1,10 +1,9 @@
import styles from "./styles.module.css"; import styles from "./styles.module.css";
function PayPalOrApplePay() { function PaymentButtons() {
return <div className={styles.container}> return <div className={styles.container}>
<img src="/paypal.webp" alt="PayPal" />
<img src="/applepay.webp" alt="ApplePay" /> <img src="/applepay.webp" alt="ApplePay" />
</div>; </div>;
} }
export default PayPalOrApplePay; export default PaymentButtons;

View File

@ -1,20 +1,20 @@
import CreditCard from "@/components/ui/PaymentMethodsButtons/CreditCard"; import CreditCard from "@/components/ui/PaymentMethodsButtons/CreditCard";
import PayPalOrApplePay from "@/components/ui/PaymentMethodsButtons/PayPayOrApplePay"; import PaymentButtons from "@/components/ui/PaymentMethodsButtons/PaymentButtons";
export enum EPaymentMethod { export enum EPaymentMethod {
CREDIT_CARD = "card", CREDIT_CARD = "card",
PAYPAL_OR_APPLE_PAY = "payPalOrApplePay", PAYMENT_BUTTONS = "paymentButtons",
} }
interface IPaymentMethod { export interface IPaymentMethod {
id: EPaymentMethod; id: EPaymentMethod;
component: JSX.Element; component: JSX.Element;
} }
export const paymentMethods: IPaymentMethod[] = [ export const paymentMethods: IPaymentMethod[] = [
{ {
id: EPaymentMethod.PAYPAL_OR_APPLE_PAY, id: EPaymentMethod.PAYMENT_BUTTONS,
component: <PayPalOrApplePay />, component: <PaymentButtons />,
}, },
{ {
id: EPaymentMethod.CREDIT_CARD, id: EPaymentMethod.CREDIT_CARD,