diff --git a/public/amazon-pay-mark.png b/public/amazon-pay-mark.png new file mode 100644 index 0000000..602efb7 Binary files /dev/null and b/public/amazon-pay-mark.png differ diff --git a/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx new file mode 100644 index 0000000..ef717bf --- /dev/null +++ b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx @@ -0,0 +1,116 @@ +import { useMemo, useState } from "react"; +import styles from "./styles.module.css"; + +import { + useStripe, + useElements, + ExpressCheckoutElement, +} from "@stripe/react-stripe-js"; +import { + AvailablePaymentMethods, + StripeExpressCheckoutElementReadyEvent, +} from "@stripe/stripe-js"; + +interface IExpressCheckoutStripeProps { + clientSecret: string; + returnUrl?: string; + isHide?: boolean; + onAvailable?: ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => void; + onChangeLoading?: (isLoading: boolean) => void; +} + +const checkFormAvailable = ( + availablePaymentMethods: undefined | AvailablePaymentMethods +) => { + if (!availablePaymentMethods) return false; + let result = false; + for (const key in availablePaymentMethods) { + if (availablePaymentMethods[key as keyof AvailablePaymentMethods]) { + result = true; + break; + } + } + return result; +}; + +function ExpressCheckoutStripe({ + clientSecret, + returnUrl = "https://${window.location.host}/payment/result/", + isHide = false, + onAvailable, + onChangeLoading, +}: IExpressCheckoutStripeProps) { + const stripe = useStripe(); + const elements = useElements(); + const [errorMessage, setErrorMessage] = useState(); + const [isAvailable, setIsAvailable] = useState(false); + const isHideForm = useMemo( + () => isHide || !isAvailable, + [isAvailable, isHide] + ); + + const onConfirm = async () => + // event: StripeExpressCheckoutElementConfirmEvent + { + if (!stripe || !elements) { + // Stripe.js hasn't loaded yet. + // Make sure to disable form submission until Stripe.js has loaded. + return; + } + + const { error: submitError } = await elements.submit(); + if (submitError) { + setErrorMessage(submitError.message); + return; + } + + // // Create the PaymentIntent and obtain clientSecret + // const res = await fetch("/create-intent", { + // method: "POST", + // }); + // const { client_secret: clientSecret } = await res.json(); + + // Confirm the PaymentIntent using the details collected by the Express Checkout Element + const { error } = await stripe.confirmPayment({ + // `elements` instance used to create the Express Checkout Element + elements, + // `clientSecret` from the created PaymentIntent + clientSecret, + confirmParams: { + return_url: returnUrl, + }, + }); + + if (error) { + // This point is only reached if there's an immediate error when + // confirming the payment. Show the error to your customer (for example, payment details incomplete) + setErrorMessage(error.message); + } else { + // The payment UI automatically closes with a success animation. + // Your customer is redirected to your `return_url`. + } + }; + + const onReady = (event: StripeExpressCheckoutElementReadyEvent) => { + const _isAvailable = checkFormAvailable(event.availablePaymentMethods); + setIsAvailable(_isAvailable); + onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods); + onChangeLoading && onChangeLoading(false); + }; + + return ( +
+ onChangeLoading && onChangeLoading(false)} + onConfirm={onConfirm} + /> + {errorMessage &&

{errorMessage}

} +
+ ); +} + +export default ExpressCheckoutStripe; diff --git a/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css b/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css new file mode 100644 index 0000000..68a9185 --- /dev/null +++ b/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css @@ -0,0 +1,16 @@ +.container { + width: 100%; +} + +.hide { + height: 0; + visibility: hidden; +} + +.error { + width: 100%; + color: #FF5758; + text-align: center; + font-size: 14px; + margin-top: 8px; +} \ No newline at end of file diff --git a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx index 4b4412d..6d65711 100644 --- a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx +++ b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, useState } from "react"; import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods"; import { Elements } from "@stripe/react-stripe-js"; import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm"; -import { Stripe, loadStripe } from "@stripe/stripe-js"; +import { AvailablePaymentMethods, Stripe, loadStripe } from "@stripe/stripe-js"; import { useSelector } from "react-redux"; import { selectors } from "@/store"; import { useNavigate } from "react-router-dom"; @@ -15,8 +15,7 @@ import SecurityPayments from "../SecurityPayments"; import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall"; import { usePaywall } from "@/hooks/paywall/usePaywall"; import { useMakePayment } from "@/hooks/payment/useMakePayment"; -import StripeButton from "@/components/PaymentPage/methods/StripeButton"; -import CheckAvailableStripeButton from "@/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton"; +import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe"; interface IPaymentModalProps { activeProduct?: IPaywallProduct; @@ -49,7 +48,7 @@ function PaymentModal({ returnUrl: checkoutUrl, paymentType, publicKey, - isLoading, + isLoading: isLoadingPayment, error, } = useMakePayment({ productId: _activeProduct?._id || "", @@ -58,17 +57,23 @@ function PaymentModal({ returnPaidUrl: returnUrl, }); - const stripeButton = useSelector(selectors.selectStripeButton); + const [availableMethods, setAvailableMethods] = useState< + AvailablePaymentMethods | undefined + >(); - const paymentRequest = stripeButton?.paymentRequest; - const availableMethods = stripeButton?.availableMethods; + const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] = + useState(true); + + const isLoading = useMemo(() => { + return isLoadingPayment || isLoadingExpressCheckout; + }, [isLoadingPayment, isLoadingExpressCheckout]); if (checkoutUrl?.length) { window.location.href = checkoutUrl; } const paymentMethodsButtons = useMemo(() => { - return paymentMethods(availableMethods); + return paymentMethods(availableMethods || null); }, [availableMethods]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( @@ -92,15 +97,16 @@ function PaymentModal({ })(); }, [_activeProduct, navigate, products, publicKey]); - if (isLoading) { - return ( -
-
- -
-
- ); - } + const onAvailableExpressCheckout = ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => { + if (isAvailable && availableMethods) { + setAvailableMethods(availableMethods); + return setSelectedPaymentMethod(EPaymentMethod.PAYMENT_BUTTONS); + } + return setAvailableMethods(undefined); + }; if (error?.length) { return ( @@ -113,62 +119,75 @@ function PaymentModal({ } return ( -
- - Choose payment method - - - {_activeProduct && ( -
- {!noTrial && ( - <> -

- You will be charged only{" "} - ${getPrice(_activeProduct)} for your 3-day trial. -

-

- We`ll email you a reminder before your trial period ends. -

- - )} - -

- Cancel anytime. The charge will appear on your bill as witapps. -

+ <> + {isLoading && ( +
+
+ +
)} -
- {stripePromise && clientSecret && ( - - - {selectedPaymentMethod === EPaymentMethod.PAYMENT_BUTTONS && ( -
- {paymentRequest && ( - - )} -
+
+ + Choose payment method + + + {_activeProduct && ( +
+ {!noTrial && ( + <> +

+ You will be charged only{" "} + ${getPrice(_activeProduct)} for your 3-day trial. +

+

+ We`ll email you a reminder before your trial period + ends. +

+ )} - {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( - - )} - +

+ Cancel anytime. The charge will appear on your bill as witapps. +

+
)} +
+ {stripePromise && clientSecret && ( + + + onAvailableExpressCheckout(_isAvailable, _availableMethods) + } + onChangeLoading={(isLoading) => + setIsLoadingExpressCheckout(isLoading) + } + /> + {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( + + )} + + )} +
+ +

500 N RAINBOW BLVD LAS VEGAS, NV 89107

- -

500 N RAINBOW BLVD LAS VEGAS, NV 89107

-
+ ); } diff --git a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css index db76c7b..113d87d 100644 --- a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css +++ b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css @@ -8,6 +8,12 @@ color: #2f2e37; } +.payment-modal.hide { + min-height: 0; + height: 0; + opacity: 0; +} + .title { font-weight: 700; font-size: 20px; diff --git a/src/components/pages/TrialPayment/components/PaymentModal/index.tsx b/src/components/pages/TrialPayment/components/PaymentModal/index.tsx index 8f11bdd..c178efc 100644 --- a/src/components/pages/TrialPayment/components/PaymentModal/index.tsx +++ b/src/components/pages/TrialPayment/components/PaymentModal/index.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, useState } from "react"; import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods"; import { Elements } from "@stripe/react-stripe-js"; import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm"; -import { Stripe, loadStripe } from "@stripe/stripe-js"; +import { AvailablePaymentMethods, Stripe, loadStripe } from "@stripe/stripe-js"; import { useSelector } from "react-redux"; import { selectors } from "@/store"; import Loader from "@/components/Loader"; @@ -13,8 +13,9 @@ import SecurityPayments from "../SecurityPayments"; import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall"; import { usePaywall } from "@/hooks/paywall/usePaywall"; import { useMakePayment } from "@/hooks/payment/useMakePayment"; -import StripeButton from "@/components/PaymentPage/methods/StripeButton"; -import CheckAvailableStripeButton from "@/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton"; +import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe"; +import routes from "@/routes"; +import { useNavigate } from "react-router-dom"; interface IPaymentModalProps { activeProduct?: IPaywallProduct; @@ -36,6 +37,7 @@ function PaymentModal({ returnUrl, placementKey = EPlacementKeys["aura.placement.main"], }: IPaymentModalProps) { + const navigate = useNavigate(); const [stripePromise, setStripePromise] = useState | null>(null); @@ -51,7 +53,7 @@ function PaymentModal({ returnUrl: checkoutUrl, paymentType, publicKey, - isLoading, + isLoading: isLoadingPayment, error, } = useMakePayment({ productId: _activeProduct?._id || "", @@ -60,20 +62,27 @@ function PaymentModal({ returnPaidUrl: returnUrl, }); - const { paymentRequest, availableMethods } = useSelector( - selectors.selectStripeButton - ); + const [availableMethods, setAvailableMethods] = useState< + AvailablePaymentMethods | undefined + >(); + + const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] = + useState(true); + + const isLoading = useMemo(() => { + return isLoadingPayment || isLoadingExpressCheckout; + }, [isLoadingPayment, isLoadingExpressCheckout]); if (checkoutUrl?.length) { window.location.href = checkoutUrl; } const paymentMethodsButtons = useMemo(() => { - return paymentMethods(availableMethods); + return paymentMethods(availableMethods || null); }, [availableMethods]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( - EPaymentMethod.PAYMENT_BUTTONS + paymentMethodsButtons[0].id ); const onSelectPaymentMethod = (method: EPaymentMethod) => { @@ -84,18 +93,25 @@ 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()); + } })(); - }, [products, publicKey]); + }, [_activeProduct, navigate, products, publicKey]); - if (isLoading) { - return ( -
-
- -
-
- ); - } + const onAvailableExpressCheckout = ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => { + if (isAvailable && availableMethods) { + setAvailableMethods(availableMethods); + return setSelectedPaymentMethod(EPaymentMethod.PAYMENT_BUTTONS); + } + return setAvailableMethods(undefined); + }; if (error?.length) { return ( @@ -108,62 +124,75 @@ function PaymentModal({ } return ( -
- - Choose payment method - - - {_activeProduct && ( -
- {!noTrial && ( - <> -

- You will be charged only{" "} - ${getPrice(_activeProduct)} for your 3-day trial. -

-

- We`ll email you a reminder before your trial period ends. -

- - )} - -

- Cancel anytime. The charge will appear on your bill as witapps. -

+ <> + {isLoading && ( +
+
+ +
)} -
- {stripePromise && clientSecret && ( - - - {selectedPaymentMethod === EPaymentMethod.PAYMENT_BUTTONS && ( -
- {paymentRequest && ( - - )} -
+
+ + Choose payment method + + + {_activeProduct && ( +
+ {!noTrial && ( + <> +

+ You will be charged only{" "} + ${getPrice(_activeProduct)} for your 3-day trial. +

+

+ We`ll email you a reminder before your trial period + ends. +

+ )} - {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( - - )} - +

+ Cancel anytime. The charge will appear on your bill as witapps. +

+
)} +
+ {stripePromise && clientSecret && ( + + + onAvailableExpressCheckout(_isAvailable, _availableMethods) + } + onChangeLoading={(isLoading) => + setIsLoadingExpressCheckout(isLoading) + } + /> + {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( + + )} + + )} +
+ +

500 N RAINBOW BLVD LAS VEGAS, NV 89107

- -

500 N RAINBOW BLVD LAS VEGAS, NV 89107

-
+ ); } diff --git a/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css b/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css index db76c7b..113d87d 100644 --- a/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css +++ b/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css @@ -8,6 +8,12 @@ color: #2f2e37; } +.payment-modal.hide { + min-height: 0; + height: 0; + opacity: 0; +} + .title { font-weight: 700; font-size: 20px; diff --git a/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx b/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx index 1faf69c..b78586b 100644 --- a/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx +++ b/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx @@ -4,26 +4,28 @@ import { TCanMakePaymentResult } from "@/components/PaymentPage/methods/StripeBu interface IPaymentButtonsProps { availableMethods: TCanMakePaymentResult; } +const Image = ({ availableMethods }: IPaymentButtonsProps) => { + if (!availableMethods) return <>; + if (availableMethods["applePay"]) { + return ApplePay; + } + if (availableMethods["googlePay"]) { + return google; + } + if (availableMethods["amazon"]) { + return AmazonPay; + } + if (availableMethods["link"]) { + return LinkPay; + } + return <>; +}; function PaymentButtons({ availableMethods }: IPaymentButtonsProps) { if (!availableMethods) return <>; return (
- {availableMethods["applePay"] && ( - ApplePay - )} - {availableMethods["googlePay"] && ( - google - )} - {availableMethods["link"] && ( - LinkPay - )} +
); }