mainstack-payments 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.env.sample +1 -0
  2. package/.eslintignore +5 -0
  3. package/.eslintrc.json +95 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  5. package/.husky/commit-msg +4 -0
  6. package/.husky/pre-commit +6 -0
  7. package/.prettierignore +7 -0
  8. package/.prettierrc +9 -0
  9. package/.vscode/extensions.json +10 -0
  10. package/README.md +113 -0
  11. package/build/_redirects +1 -0
  12. package/build/mainstack-payments.js +97183 -0
  13. package/build/manifest.json +15 -0
  14. package/build/robots.txt +3 -0
  15. package/build/style.css +6 -0
  16. package/build/vite.svg +1 -0
  17. package/commitlint.config.cjs +1 -0
  18. package/index.html +31 -0
  19. package/package.json +86 -0
  20. package/public/_redirects +1 -0
  21. package/public/manifest.json +15 -0
  22. package/public/robots.txt +3 -0
  23. package/public/vite.svg +1 -0
  24. package/src/api/config.ts +36 -0
  25. package/src/api/index.ts +84 -0
  26. package/src/app.tsx +39 -0
  27. package/src/assets/images/tired-emoji.png +0 -0
  28. package/src/assets/styles/index.css +26 -0
  29. package/src/assets/themes/baseThemes.ts +30 -0
  30. package/src/components/CheckoutForm.tsx +426 -0
  31. package/src/components/DrawerModal.tsx +63 -0
  32. package/src/components/Payment.tsx +772 -0
  33. package/src/components/PaystackPaymentErrorModal.tsx +120 -0
  34. package/src/components/PaystackPaymentModal.tsx +120 -0
  35. package/src/components/WalletPay.tsx +160 -0
  36. package/src/constants/index.ts +3 -0
  37. package/src/enums/currenciesEnums.ts +7 -0
  38. package/src/hooks/usePayment.ts +60 -0
  39. package/src/index.ts +3 -0
  40. package/src/pages/Home.tsx +97 -0
  41. package/src/pages/Index.tsx +23 -0
  42. package/src/pages/Login.tsx +13 -0
  43. package/src/pages/Page404.tsx +13 -0
  44. package/src/pages/PaymentRedirect.tsx +15 -0
  45. package/src/routes/index.tsx +8 -0
  46. package/src/types/index.ts +48 -0
  47. package/src/utils/countries-flag.json +1752 -0
  48. package/src/utils/countries_flag.json +1502 -0
  49. package/src/utils/countries_with_flags_and_currencies.json +4004 -0
  50. package/src/utils/formatUnderscoreText.ts +5 -0
  51. package/src/utils/index.ts +4 -0
  52. package/src/utils/stringifyPrice.ts +37 -0
  53. package/src/utils/validations.ts +44 -0
  54. package/tsconfig.json +35 -0
  55. package/vite.config.ts +36 -0
@@ -0,0 +1,120 @@
1
+ /** @format */
2
+ import {
3
+ Heading,
4
+ Img,
5
+ Modal,
6
+ ModalBody,
7
+ ModalContent,
8
+ ModalFooter,
9
+ ModalHeader,
10
+ ModalOverlay,
11
+ Text,
12
+ useMediaQuery,
13
+ } from "@chakra-ui/react";
14
+ import SorryEmoji from "assets/images/tired-emoji.png";
15
+ import BrandDrawer from "components/DrawerModal";
16
+ import { Button } from "mainstack-design-system";
17
+
18
+ interface PaystackPaymentErrorModalProps {
19
+ isOpen: boolean;
20
+ onClose: () => void;
21
+ }
22
+ const PaystackPaymentErrorModal = ({
23
+ isOpen,
24
+ onClose,
25
+ }: PaystackPaymentErrorModalProps) => {
26
+ const [isLargeScreen] = useMediaQuery("(min-width: 768px)");
27
+
28
+ return (
29
+ <>
30
+ {isLargeScreen ? (
31
+ <Modal
32
+ isCentered
33
+ isOpen={isOpen}
34
+ onClose={onClose}
35
+ motionPreset="slideInBottom"
36
+ size={"lg"}
37
+ >
38
+ <ModalOverlay bg="rgba(219, 222, 229, 0.6)" />
39
+ <ModalContent bg="#fff" borderRadius="20px" p="32px">
40
+ <ModalHeader p="0" mb={"12px"}>
41
+ <Img
42
+ src={SorryEmoji}
43
+ width={"32px"}
44
+ height={"32px"}
45
+ mx="auto"
46
+ mb="20px"
47
+ />
48
+ <Heading
49
+ fontSize={"24px"}
50
+ lineHeight={"32px"}
51
+ letterSpacing={"-0.48px"}
52
+ textAlign={"center"}
53
+ >
54
+ Unable to process purchase at this time
55
+ </Heading>
56
+ </ModalHeader>
57
+ <ModalBody p="0">
58
+ <Text
59
+ mt={"4px"}
60
+ mb={"16px"}
61
+ fontSize={"16px"}
62
+ fontWeight={400}
63
+ color={"#4D5760"}
64
+ lineHeight="160%"
65
+ letterSpacing={"-0.16px"}
66
+ textAlign={"center"}
67
+ >
68
+ Apologies, but our payment processor is currently unavailable
69
+ for processing payments in this currency. Please try again later
70
+ or consider using other currencies for your purchase.
71
+ </Text>
72
+ </ModalBody>
73
+ <ModalFooter p="0" mt="32px">
74
+ <Button
75
+ label="Okay"
76
+ size="medium"
77
+ variant="tertiary"
78
+ width="100%"
79
+ onClick={onClose}
80
+ />
81
+ </ModalFooter>
82
+ </ModalContent>
83
+ </Modal>
84
+ ) : (
85
+ <BrandDrawer isOpen={isOpen} onClose={onClose}>
86
+ <Img src={SorryEmoji} width={"24px"} height={"24px"} />
87
+ <Heading
88
+ fontSize={"18px"}
89
+ lineHeight={"20px"}
90
+ letterSpacing={"-0.36px"}
91
+ >
92
+ Unable to process purchase at this time
93
+ </Heading>
94
+ <Text
95
+ mt={"4px"}
96
+ mb={"28px"}
97
+ fontSize={"14px"}
98
+ fontWeight={400}
99
+ color={"#4D5760"}
100
+ lineHeight="160%"
101
+ letterSpacing={"-0.14px"}
102
+ >
103
+ Apologies, but our payment processor is currently unavailable for
104
+ processing payments in this currency. Please try again later or
105
+ consider using other currencies for your purchase.
106
+ </Text>
107
+ <Button
108
+ label="Okay"
109
+ size="medium"
110
+ variant="tertiary"
111
+ width="100%"
112
+ onClick={onClose}
113
+ />
114
+ </BrandDrawer>
115
+ )}
116
+ </>
117
+ );
118
+ };
119
+
120
+ export default PaystackPaymentErrorModal;
@@ -0,0 +1,120 @@
1
+ /** @format */
2
+
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { Box, useMediaQuery } from "@chakra-ui/react";
5
+ import { CancelFilledIcon } from "mainstack-design-system";
6
+ import { PAYSTACK_REDIRECT_EVENT } from "constants/index";
7
+
8
+ interface PaystackPaymentModalProps {
9
+ isOpen: boolean;
10
+ src: string;
11
+ handleCloseModal: () => void;
12
+ }
13
+
14
+ export const PaystackPaymentModal = ({
15
+ src,
16
+ isOpen,
17
+ handleCloseModal,
18
+ }: PaystackPaymentModalProps) => {
19
+ const [iframeLoaded, setIframeLoaded] = useState(false);
20
+ const iframeRef = useRef(null);
21
+ const [isSmallerThan600px] = useMediaQuery("(max-width: 600px)");
22
+
23
+ useEffect(() => {
24
+ const handleWindowMessage = (event: any) => {
25
+ if (event.data.type === PAYSTACK_REDIRECT_EVENT) {
26
+ handleCloseModal();
27
+ }
28
+ };
29
+
30
+ window.addEventListener("message", handleWindowMessage);
31
+
32
+ return () => {
33
+ window.removeEventListener("message", handleWindowMessage);
34
+ };
35
+ }, [handleCloseModal]);
36
+
37
+ const handleIframeLoad = () => {
38
+ setIframeLoaded(true);
39
+ if (iframeRef?.current) {
40
+ (iframeRef.current as HTMLIFrameElement).contentWindow?.postMessage(
41
+ { type: "iframeLoaded" },
42
+ window.location.origin
43
+ );
44
+ }
45
+ };
46
+
47
+ return (
48
+ <Box
49
+ display={isOpen ? "block" : "none"}
50
+ position="fixed"
51
+ zIndex={999999999}
52
+ left={0}
53
+ top={0}
54
+ width="100%"
55
+ height="100%"
56
+ maxH={"100vh"}
57
+ overflow="hidden"
58
+ backgroundColor="rgba(0, 0, 0, 0.4)"
59
+ >
60
+ <Box
61
+ className={`modal-content ${isOpen ? "show" : ""}`}
62
+ margin="15% auto"
63
+ backgroundColor="#fefefe"
64
+ border="1px solid #888"
65
+ width={isSmallerThan600px ? "90%" : "80%"}
66
+ maxWidth="600px"
67
+ borderRadius="5px"
68
+ {...(isSmallerThan600px
69
+ ? {
70
+ position: "absolute",
71
+ left: "50%",
72
+ transform: "translateX(-50%)",
73
+ bottom: "0px",
74
+ }
75
+ : {})}
76
+ >
77
+ {isSmallerThan600px && (
78
+ <Box
79
+ onClick={handleCloseModal}
80
+ fontSize={"20px"}
81
+ position={"absolute"}
82
+ bottom={"-30px"}
83
+ left={"50%"}
84
+ transform={"translateX(-50%)"}
85
+ >
86
+ <CancelFilledIcon color={"white"} boxSize={"24px"} />
87
+ </Box>
88
+ )}
89
+ <Box
90
+ onClick={handleCloseModal}
91
+ fontSize={"20px"}
92
+ position={"fixed"}
93
+ right={"calc(50% - 320px)"}
94
+ transform={"translate(-50%, -20px)"}
95
+ cursor={"pointer"}
96
+ >
97
+ <CancelFilledIcon color={"white"} boxSize={"24px"} />
98
+ </Box>
99
+ <iframe
100
+ title={"main-ghs-pay"}
101
+ ref={iframeRef}
102
+ src={src}
103
+ width="100%"
104
+ height="400"
105
+ allowFullScreen
106
+ name="main-ghs-pay"
107
+ id="main-ghs-pay"
108
+ onLoad={handleIframeLoad}
109
+ style={{
110
+ opacity: iframeLoaded ? 1 : 0,
111
+ pointerEvents: iframeLoaded ? "auto" : "none",
112
+ transition: "opacity 0.3s ease",
113
+ margin: "0 auto",
114
+ height: "600px",
115
+ }}
116
+ ></iframe>
117
+ </Box>
118
+ </Box>
119
+ );
120
+ };
@@ -0,0 +1,160 @@
1
+ /** @format */
2
+
3
+ import { Flex, Text } from "@chakra-ui/react";
4
+ import { useElements, useStripe } from "@stripe/react-stripe-js";
5
+ import { useEffect, useState } from "react";
6
+ import {
7
+ Button,
8
+ customSnackbar,
9
+ GooglePayIcon,
10
+ SnackbarType,
11
+ } from "mainstack-design-system";
12
+
13
+ const WalletPay = ({
14
+ currency,
15
+ metadata,
16
+ amount,
17
+ formik,
18
+ checkForErrors,
19
+ onPaymentComplete,
20
+ Request,
21
+ }: // discounted_price,
22
+ // redeemDiscount,
23
+
24
+ {
25
+ currency: string;
26
+ amount: number;
27
+ metadata?: any;
28
+ formik: any;
29
+ // discounted_price: number;
30
+ // redeemDiscount: (price: number) => void;
31
+ checkForErrors: () => Promise<boolean>;
32
+ onPaymentComplete: (reference: string) => void;
33
+ Request: any;
34
+ }) => {
35
+ const stripe = useStripe();
36
+ const elements = useElements();
37
+ const [paymentRequest, setPaymentRequest] = useState<any>(null);
38
+ const [wallets, setWallets] = useState<any>(null);
39
+ const [isPending, setIsPending] = useState<boolean>(false);
40
+
41
+ useEffect(() => {
42
+ if (stripe && amount > 0) {
43
+ metadata.email = formik.values.email;
44
+ metadata.name = formik.values.fullname;
45
+
46
+ // eslint-disable-next-line @typescript-eslint/naming-convention
47
+ const payment_request = stripe?.paymentRequest({
48
+ country: "US",
49
+ currency: currency?.toLowerCase(),
50
+ total: {
51
+ label: metadata?.productName,
52
+ amount: amount,
53
+ },
54
+ requestPayerName: true,
55
+ requestPayerEmail: true,
56
+ });
57
+
58
+ payment_request.canMakePayment().then((result) => {
59
+ if (result) {
60
+ setPaymentRequest(payment_request);
61
+ setWallets(result);
62
+ }
63
+ });
64
+
65
+ payment_request.on("paymentmethod", async (e: any) => {
66
+ // eslint-disable-next-line @typescript-eslint/naming-convention
67
+ const { client_secret } = await Request.post("/stripe/intent", {
68
+ amount: amount,
69
+ currency: currency,
70
+ metadata: metadata,
71
+ }).then((res: any) => res.data);
72
+
73
+ const { paymentIntent, error: confirmError } =
74
+ await stripe.confirmCardPayment(
75
+ client_secret,
76
+ {
77
+ payment_method: e.paymentMethod.id,
78
+ },
79
+ { handleActions: false }
80
+ );
81
+
82
+ if (confirmError) {
83
+ e.complete("fail");
84
+ customSnackbar(
85
+ confirmError?.message ?? "We couldn't process your payment",
86
+ SnackbarType.error
87
+ );
88
+ } else {
89
+ e.complete("success");
90
+ if (paymentIntent.status === "requires_action") {
91
+ const { error } = await stripe.confirmCardPayment(client_secret);
92
+ if (error) {
93
+ customSnackbar(
94
+ "Payment failed, please add a new payment method",
95
+ SnackbarType.error
96
+ );
97
+ } else {
98
+ setIsPending(true);
99
+ setTimeout(() => {
100
+ onPaymentComplete(metadata?.reference);
101
+ }, 3000);
102
+ }
103
+ }
104
+ customSnackbar("Payment was successful", SnackbarType.success);
105
+ setIsPending(true);
106
+ setTimeout(() => {
107
+ onPaymentComplete(metadata?.reference);
108
+ }, 3000);
109
+ }
110
+ });
111
+ }
112
+ }, [stripe, elements, formik.values.email, formik.values.fullname, amount]);
113
+
114
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
115
+ return (
116
+ <>
117
+ {paymentRequest && wallets !== null && (
118
+ <Flex width={"100%"} gap={"16px"}>
119
+ {wallets?.applePay && (
120
+ <Button
121
+ size="medium"
122
+ variant="primary"
123
+ label=" Pay"
124
+ width="100%"
125
+ fontFamily={"sans-serif"}
126
+ onClick={async () => {
127
+ const hasErrors = await checkForErrors();
128
+ if (!hasErrors) {
129
+ paymentRequest?.show();
130
+ }
131
+ }}
132
+ />
133
+ )}
134
+ {wallets?.googlePay && (
135
+ <Button
136
+ size="medium"
137
+ variant="primary"
138
+ label="Pay"
139
+ width="100%"
140
+ icon={<GooglePayIcon boxSize={"20px"} mt={"6px"} />}
141
+ icontype="leading"
142
+ fontSize={"1.125rem"}
143
+ onClick={async () => {
144
+ const hasErrors = await checkForErrors();
145
+ if (!hasErrors) {
146
+ paymentRequest?.show();
147
+ }
148
+ }}
149
+ fontFamily="inherit"
150
+ isLoading={isPending}
151
+ disabled={isPending}
152
+ />
153
+ )}
154
+ </Flex>
155
+ )}
156
+ </>
157
+ );
158
+ };
159
+
160
+ export default WalletPay;
@@ -0,0 +1,3 @@
1
+ /** @format */
2
+
3
+ export const PAYSTACK_REDIRECT_EVENT = "Payment Completed";
@@ -0,0 +1,7 @@
1
+ /** @format */
2
+
3
+ export enum CURRENCIES {
4
+ GHS = "GHS",
5
+ NGN = "NGN",
6
+ UGX = "UGX",
7
+ }
@@ -0,0 +1,60 @@
1
+ /** @format */
2
+
3
+ import AllowedCountriesWithFlags from "utils/countries_with_flags_and_currencies.json";
4
+
5
+ const getAmount = (
6
+ curr: string,
7
+ price: string | number,
8
+ currencies: any,
9
+ markup?: number
10
+ ) => {
11
+ if (curr === "USD") {
12
+ return Number(price);
13
+ }
14
+ const newPrice = Number(
15
+ (Array.isArray(currencies) ? currencies : currencies?.currency_rate)?.find(
16
+ (i: any) => i?.currency === curr
17
+ )?.rate || 0
18
+ );
19
+
20
+ const result = Number(price) * Number(newPrice);
21
+ const markupResult = (val: number) =>
22
+ markup ? val + (markup / 100) * val : val;
23
+ return curr === "NGN"
24
+ ? result
25
+ : curr === "UGX"
26
+ ? markupResult(Math.ceil(result / 100) * 100)
27
+ : markupResult(result);
28
+ };
29
+
30
+ const getAmountInUSD = (curr: string, price: string, currencies: any) => {
31
+ const newPrice = Number(
32
+ (Array.isArray(currencies) ? currencies : currencies?.currency_rate)?.find(
33
+ (i: any) => i?.currency === curr
34
+ )?.rate || 0
35
+ );
36
+ return Number(price) / Number(newPrice);
37
+ };
38
+
39
+ // get price of product and format it for display
40
+
41
+ export const usePayment = (currency: string) => {
42
+ const getCountryNameByCurrency = () => {
43
+ return AllowedCountriesWithFlags.filter(
44
+ (country) => country.currency.code === currency
45
+ )[0]?.name;
46
+ };
47
+
48
+ const getCountryCurrency = (country: any) => {
49
+ return AllowedCountriesWithFlags.filter(
50
+ (entry) => entry.name === country
51
+ )[0]?.currency.code;
52
+ };
53
+
54
+ return {
55
+ getAmount,
56
+ getAmountInUSD,
57
+ getCountryNameByCurrency,
58
+ getCountryCurrency,
59
+ };
60
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** @format */
2
+
3
+ export { default as MainstackPayments } from "./components/Payment";
@@ -0,0 +1,97 @@
1
+ /** @format */
2
+
3
+ import { Box, Text } from "@chakra-ui/react";
4
+ import MainstackPayment from "components/Payment";
5
+ import { Input } from "mainstack-design-system";
6
+ import { useRef, useState } from "react";
7
+ import { v4 as uuidv4 } from "uuid";
8
+ import * as yup from "yup";
9
+
10
+ export default function Home() {
11
+ const reference = uuidv4();
12
+ const config = {
13
+ accountId: "65c38a52dc20c4b7d8fd13b7",
14
+ currency: "NGN",
15
+ reference,
16
+ amount: 1000,
17
+ transactionFeesSlug: "bookings-pro-plan",
18
+ userAllowsWalletPayment: true,
19
+ userAllowsCardPayment: true,
20
+ metadata: {
21
+ reference,
22
+ actual_price: 1.5,
23
+ account_id: "65c38a52dc20c4b7d8fd13b7",
24
+ user_id: "65c38a52dc20c4b7d8fd13b5",
25
+ product_id: "6643bf37530e76a622129042",
26
+ type: "appointment",
27
+ mainstack_product_type: "bookings" as const,
28
+ productName: "For Free",
29
+ service_id: "6643bf37530e76a622129042",
30
+ service_name: "For Free",
31
+ service_price: 1000,
32
+ booking_id: "662b7488a1cf46a4c2d24e73",
33
+ },
34
+ };
35
+
36
+ const storeConfig = {
37
+ accountId: "65081b1bd7e9e85ba863ec07",
38
+ currency: "NGN",
39
+ reference,
40
+ amount: 2000,
41
+ transactionFeesSlug: "store-pro-plan",
42
+ userAllowsWalletPayment: true,
43
+ userAllowsCardPayment: true,
44
+ metadata: {
45
+ reference,
46
+ actual_price: 1.5,
47
+ account_id: "65081b1bd7e9e85ba863ec07",
48
+ user_id: "65081b1bd7e9e85ba863ec05",
49
+ product_id: "q8zLqq9vOEUV",
50
+ type: "digital_product",
51
+ mainstack_product_type: "store" as const,
52
+ productName: "The Other Room",
53
+ },
54
+ paystackRedirectUrl: "http://localhost:3008/payment-redirect",
55
+ baseUrl: "https://dev.api.mainstack.io",
56
+ authMSUrl: "https://dev.auth.mainstack.io",
57
+ };
58
+
59
+ const [testName, setTestName] = useState("");
60
+ const paymentRef = useRef<any>();
61
+ return (
62
+ <Box h="100vh" display="flex" justifyContent="center">
63
+ <MainstackPayment
64
+ paymentConfig={storeConfig}
65
+ customForm={
66
+ <Input
67
+ id="Test name"
68
+ label="Your name *"
69
+ name="fullname"
70
+ onChange={(e: any) => {
71
+ setTestName(e.target.value);
72
+ if (paymentRef?.current) {
73
+ paymentRef.current.updateCustomFormValues(
74
+ "testName",
75
+ e.target.value
76
+ );
77
+ }
78
+ }}
79
+ value={testName}
80
+ placeholder="name"
81
+ type="text"
82
+ // error={Boolean(formik?.errors?.fullname)}
83
+ // errorMessage={formik?.errors?.fullname as string}
84
+ fontFamily={"inherit"}
85
+ />
86
+ }
87
+ customFormValues={{ testName }}
88
+ customFormValidation={{
89
+ testName: yup.string().required("Please select a currency."),
90
+ }}
91
+ onGoBack={() => console.log("Go back")}
92
+ onPaymentComplete={() => console.log("Payment Complete!")}
93
+ ref={paymentRef}
94
+ />
95
+ </Box>
96
+ );
97
+ }
@@ -0,0 +1,23 @@
1
+ /** @format */
2
+ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
3
+ import { ROUTES } from "routes";
4
+ import Home from "./Home";
5
+ import Login from "./Login";
6
+ import Page404 from "./Page404";
7
+ import PaymentRedirect from "./PaymentRedirect";
8
+
9
+ function Index() {
10
+ return (
11
+ <BrowserRouter>
12
+ <Routes>
13
+ <Route path={ROUTES.HOME} element={<Home />} />
14
+ <Route path={ROUTES.LOGIN} element={<Login />} />
15
+ <Route path={ROUTES.PAYMENT_REDIRECT} element={<PaymentRedirect />} />
16
+ <Route path={ROUTES.NOT_FOUND} element={<Page404 />} />
17
+ <Route path="*" element={<Navigate to={ROUTES.NOT_FOUND} />} />
18
+ </Routes>
19
+ </BrowserRouter>
20
+ );
21
+ }
22
+
23
+ export default Index;
@@ -0,0 +1,13 @@
1
+ /** @format */
2
+
3
+ import { Box, Text } from "@chakra-ui/react";
4
+
5
+ export default function Login() {
6
+ return (
7
+ <Box h="100vh" display="flex" justifyContent="center" alignItems="center">
8
+ <Text fontSize="h1" fontWeight="bold">
9
+ Login Page
10
+ </Text>
11
+ </Box>
12
+ );
13
+ }
@@ -0,0 +1,13 @@
1
+ /** @format */
2
+
3
+ import { Box, Text } from "@chakra-ui/react";
4
+
5
+ export default function Page404() {
6
+ return (
7
+ <Box h="100vh" display="flex" justifyContent="center" alignItems="center">
8
+ <Text fontSize="h1" fontWeight="bold">
9
+ 404 page
10
+ </Text>
11
+ </Box>
12
+ );
13
+ }
@@ -0,0 +1,15 @@
1
+ /** @format */
2
+ import { PAYSTACK_REDIRECT_EVENT } from "constants/index";
3
+ import { useEffect } from "react";
4
+
5
+ const PostMessagePage = () => {
6
+ useEffect(() => {
7
+ (() => {
8
+ window.parent.postMessage({ type: PAYSTACK_REDIRECT_EVENT }, "*");
9
+ })();
10
+ }, []);
11
+
12
+ return <div></div>;
13
+ };
14
+
15
+ export default PostMessagePage;
@@ -0,0 +1,8 @@
1
+ /** @format */
2
+
3
+ export const ROUTES = {
4
+ HOME: "/",
5
+ LOGIN: "/login",
6
+ NOT_FOUND: "/404",
7
+ PAYMENT_REDIRECT: "/payment-redirect",
8
+ };