diff --git a/public/paypal-logo.svg b/public/paypal-logo.svg new file mode 100644 index 0000000..52913d8 --- /dev/null +++ b/public/paypal-logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/api/resources/UserSubscriptionReceipts.ts b/src/api/resources/UserSubscriptionReceipts.ts index 0692ff2..b5f5960 100644 --- a/src/api/resources/UserSubscriptionReceipts.ts +++ b/src/api/resources/UserSubscriptionReceipts.ts @@ -26,7 +26,9 @@ export interface StripeReceiptPayload extends AuthPayload { } export interface PayPalReceiptPayload extends AuthPayload { - itemInterval: "week" | "month" | "year"; + subscription_receipt: { + sub_plan_id: string; + }; way: "paypal"; } @@ -82,8 +84,8 @@ function createRequest({ function createRequest({ token, receiptData, - autorenewable = true, - sandbox = true, + autorenewable, + sandbox, }: AppleReceiptPayload): Request; function createRequest({ token }: StripeReceiptPayload): Request; function createRequest(payload: Payload): Request; @@ -123,7 +125,7 @@ function getDataPayload(payload: Payload) { return { way: "paypal", subscription_receipt: { - item_interval: payload.itemInterval, + sub_plan_id: payload.subscription_receipt.sub_plan_id, }, }; } diff --git a/src/components/PriceListPage/index.tsx b/src/components/PriceListPage/index.tsx index 69e6668..018eca6 100644 --- a/src/components/PriceListPage/index.tsx +++ b/src/components/PriceListPage/index.tsx @@ -28,17 +28,15 @@ function PriceListPage(): JSX.Element { (async () => { const { sub_plans } = await api.getSubscriptionPlans({ locale }); const plans = sub_plans - .filter( - (plan: ISubscriptionPlan) => plan.provider === "stripe" && plan.trial - ) + .filter((plan: ISubscriptionPlan) => plan.provider === "stripe") .sort((a, b) => { if (!a.trial || !b.trial) { return 0; } - if (a.trial.price_cents < b.trial.price_cents) { + if (a.trial?.price_cents < b.trial?.price_cents) { return -1; } - if (a.trial.price_cents > b.trial.price_cents) { + if (a.trial?.price_cents > b.trial?.price_cents) { return 1; } return 0; diff --git a/src/components/StripePage/ApplePayButton/index.tsx b/src/components/StripePage/ApplePayButton/index.tsx index 0fecbf5..2771df8 100644 --- a/src/components/StripePage/ApplePayButton/index.tsx +++ b/src/components/StripePage/ApplePayButton/index.tsx @@ -22,7 +22,6 @@ function ApplePayButton({ activeSubPlan }: ApplePayButtonProps) { const [paymentRequest, setPaymentRequest] = useState( null ); -// const [test, setTest] = useState(""); const getAmountFromSubPlan = (subPlan: ISubscriptionPlan) => { if (subPlan.trial) { @@ -46,33 +45,14 @@ function ApplePayButton({ activeSubPlan }: ApplePayButtonProps) { requestPayerName: true, requestPayerEmail: true, }); - console.log("paymentRequest: ", pr); - // Check the availability of the Payment Request API. pr.canMakePayment().then((result) => { - console.log("canMakePayment: ", result); - // setTest("canMakePayment: " + JSON.stringify(result)); - if (result) { setPaymentRequest(pr); } }); pr.on("paymentmethod", async (e) => { - // const { error: backendError, clientSecret } = await fetch( - // "/create-payment-intent", - // { - // method: "POST", - // headers: { - // "Content-Type": "application/json", - // }, - // body: JSON.stringify({ - // paymentMethodType: "card", - // currency: "usd", - // }), - // } - // ).then((r) => r.json()); - const { subscription_receipt } = await api.createSubscriptionReceipt({ token, way: "stripe", @@ -109,11 +89,13 @@ function ApplePayButton({ activeSubPlan }: ApplePayButtonProps) { return ( <> - {/* {test} */} {paymentRequest && ( )} diff --git a/src/components/StripePage/index.tsx b/src/components/StripePage/index.tsx index 50f0cb4..a9c639f 100644 --- a/src/components/StripePage/index.tsx +++ b/src/components/StripePage/index.tsx @@ -11,15 +11,21 @@ import { selectors } from "@/store"; import { useNavigate } from "react-router-dom"; import routes from "@/routes"; import SubPlanInformation from "../SubPlanInformation"; -import ApplePayButton from "./ApplePayButton"; +import Title from "../Title"; +import { useTranslation } from "react-i18next"; +import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans"; export function StripePage(): JSX.Element { + const { i18n } = useTranslation(); const api = useApi(); const { token } = useAuth(); + const locale = i18n.language; const navigate = useNavigate(); const activeSubPlan = useSelector(selectors.selectActiveSubPlan); + const email = useSelector(selectors.selectEmail); const [stripePromise, setStripePromise] = useState | null>(null); + const [subPlans, setSubPlans] = useState(null); const [clientSecret, setClientSecret] = useState(""); const [isLoading, setIsLoading] = useState(true); if (!activeSubPlan) { @@ -40,8 +46,16 @@ export function StripePage(): JSX.Element { (async () => { const siteConfig = await api.getAppConfig({ bundleId: "auraweb" }); setStripePromise(loadStripe(siteConfig.data.stripe_public_key)); + const { sub_plans } = await api.getSubscriptionPlans({ locale }); + setSubPlans(sub_plans); + const isActiveSubPlan = sub_plans.find( + (subPlan) => subPlan.id === activeSubPlan?.id + ); + if (!activeSubPlan || !isActiveSubPlan) { + navigate(routes.client.priceList()); + } })(); - }, [api]); + }, [activeSubPlan, api, locale, navigate]); useEffect(() => { (async () => { @@ -60,23 +74,29 @@ export function StripePage(): JSX.Element { return (
- {/* Cross navigate(routes.client.home())} - /> */} {isLoading ? (
) : null} + {!isLoading && ( + <> + + Pay + +

{email}

+ + )} {stripePromise && clientSecret && ( <> - - {activeSubPlan && } + {activeSubPlan && ( + + )} diff --git a/src/components/StripePage/styles.module.css b/src/components/StripePage/styles.module.css index 0862b8d..d305530 100644 --- a/src/components/StripePage/styles.module.css +++ b/src/components/StripePage/styles.module.css @@ -3,11 +3,10 @@ position: static; /* height: calc(100vh - 50px); max-height: -webkit-fill-available; */ - flex: auto; - justify-content: center; - display: grid; - grid-template-rows: 1fr 96px; + display: flex; justify-items: center; + justify-content: center; + gap: 16px; } .payment-loader { @@ -25,3 +24,15 @@ cursor: pointer; z-index: 9; } + +.title { + font-size: 27px; + font-weight: 700; + margin: 0; +} + +.email { + font-size: 17px; + font-weight: 500; + margin: 0; +} \ No newline at end of file diff --git a/src/components/SubPlanInformation/index.tsx b/src/components/SubPlanInformation/index.tsx index 6a4db85..da73452 100644 --- a/src/components/SubPlanInformation/index.tsx +++ b/src/components/SubPlanInformation/index.tsx @@ -2,9 +2,16 @@ import { useTranslation } from "react-i18next"; import styles from "./styles.module.css"; import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans"; import TotalToday from "./TotalToday"; +import ApplePayButton from "../StripePage/ApplePayButton"; +import MainButton from "../MainButton"; +import { useApi } from "@/api"; +import { useAuth } from "@/auth"; +import { useEffect, useState } from "react"; +import Loader from "../Loader"; interface ISubPlanInformationProps { subPlan: ISubscriptionPlan; + subPlans: ISubscriptionPlan[] | null; } const getPrice = (plan: ISubscriptionPlan): string => { @@ -15,14 +22,76 @@ const getPrice = (plan: ISubscriptionPlan): string => { function SubPlanInformation({ subPlan, + subPlans, }: ISubPlanInformationProps): JSX.Element { const { t } = useTranslation(); + const api = useApi(); + const { token } = useAuth(); + const [payPalSubPlan, setPayPalSubPlan] = useState(); + const [errors, setErrors] = useState(""); + const [isLoading, setIsLoading] = useState(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.open(link.href, "_blank"); + }; + return (
+ {payPalSubPlan && ( + + {!isLoading && PayPal Button} + {isLoading && } + + )} +

{t("auweb.pay.information").replaceAll("%@", getPrice(subPlan))}

+ {!!errors.length &&

{errors}

}
); } diff --git a/src/components/SubPlanInformation/styles.module.css b/src/components/SubPlanInformation/styles.module.css index 4404a0e..7c6cd7d 100644 --- a/src/components/SubPlanInformation/styles.module.css +++ b/src/components/SubPlanInformation/styles.module.css @@ -14,3 +14,18 @@ line-height: 16px; padding-bottom: 16px; } + +.pay-pal-button { + width: 100%; + height: 60px; + display: flex; + align-items: center; + justify-content: center; + background-color: #ffc43a; + border-radius: 7px; +} + +.errors { + color: red; + text-align: center; +} diff --git a/src/components/SubscriptionPage/index.tsx b/src/components/SubscriptionPage/index.tsx index c99fcea..6b0e602 100644 --- a/src/components/SubscriptionPage/index.tsx +++ b/src/components/SubscriptionPage/index.tsx @@ -62,8 +62,8 @@ function SubscriptionPage(): JSX.Element { const targetSubPlan = subPlans.find( (sub_plan) => String( - sub_plan.trial?.price_cents - ? Math.floor(sub_plan.trial?.price_cents / 100) + sub_plan?.trial?.price_cents + ? Math.floor(sub_plan?.trial?.price_cents / 100) : sub_plan.id.replace(".", "") ) === subPlan ); @@ -181,16 +181,16 @@ function SubscriptionPage(): JSX.Element { const { sub_plans } = await api.getSubscriptionPlans({ locale }); const plans = sub_plans .filter( - (plan: ISubscriptionPlan) => plan.provider === "stripe" && plan.trial + (plan: ISubscriptionPlan) => plan.provider === "stripe" ) .sort((a, b) => { if (!a.trial || !b.trial) { return 0; } - if (a.trial.price_cents < b.trial.price_cents) { + if (a?.trial?.price_cents < b?.trial?.price_cents) { return -1; } - if (a.trial.price_cents > b.trial.price_cents) { + if (a?.trial?.price_cents > b?.trial?.price_cents) { return 1; } return 0;