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.
- package/.env.sample +1 -0
- package/.eslintignore +5 -0
- package/.eslintrc.json +95 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +7 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +10 -0
- package/README.md +113 -0
- package/build/_redirects +1 -0
- package/build/mainstack-payments.js +97183 -0
- package/build/manifest.json +15 -0
- package/build/robots.txt +3 -0
- package/build/style.css +6 -0
- package/build/vite.svg +1 -0
- package/commitlint.config.cjs +1 -0
- package/index.html +31 -0
- package/package.json +86 -0
- package/public/_redirects +1 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/public/vite.svg +1 -0
- package/src/api/config.ts +36 -0
- package/src/api/index.ts +84 -0
- package/src/app.tsx +39 -0
- package/src/assets/images/tired-emoji.png +0 -0
- package/src/assets/styles/index.css +26 -0
- package/src/assets/themes/baseThemes.ts +30 -0
- package/src/components/CheckoutForm.tsx +426 -0
- package/src/components/DrawerModal.tsx +63 -0
- package/src/components/Payment.tsx +772 -0
- package/src/components/PaystackPaymentErrorModal.tsx +120 -0
- package/src/components/PaystackPaymentModal.tsx +120 -0
- package/src/components/WalletPay.tsx +160 -0
- package/src/constants/index.ts +3 -0
- package/src/enums/currenciesEnums.ts +7 -0
- package/src/hooks/usePayment.ts +60 -0
- package/src/index.ts +3 -0
- package/src/pages/Home.tsx +97 -0
- package/src/pages/Index.tsx +23 -0
- package/src/pages/Login.tsx +13 -0
- package/src/pages/Page404.tsx +13 -0
- package/src/pages/PaymentRedirect.tsx +15 -0
- package/src/routes/index.tsx +8 -0
- package/src/types/index.ts +48 -0
- package/src/utils/countries-flag.json +1752 -0
- package/src/utils/countries_flag.json +1502 -0
- package/src/utils/countries_with_flags_and_currencies.json +4004 -0
- package/src/utils/formatUnderscoreText.ts +5 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/stringifyPrice.ts +37 -0
- package/src/utils/validations.ts +44 -0
- package/tsconfig.json +35 -0
- 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,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,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;
|