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,426 @@
1
+ /** @format */
2
+
3
+ import { useChargePayment } from "api";
4
+ import {
5
+ useMediaQuery,
6
+ Collapse,
7
+ Flex,
8
+ Grid,
9
+ GridItem,
10
+ FormControl,
11
+ Box,
12
+ Text,
13
+ Heading,
14
+ } from "@chakra-ui/react";
15
+ import {
16
+ useStripe,
17
+ useElements,
18
+ CardNumberElement,
19
+ CardExpiryElement,
20
+ CardCvcElement,
21
+ } from "@stripe/react-stripe-js";
22
+ import { StripeCardNumberElement, StripeCardElement } from "@stripe/stripe-js";
23
+ import {
24
+ Colors,
25
+ CancelFilledIcon,
26
+ CountrySelect,
27
+ LockIcon,
28
+ Input,
29
+ Button,
30
+ customSnackbar,
31
+ SnackbarType,
32
+ } from "mainstack-design-system";
33
+ import { useState } from "react";
34
+ import countries_flag from "utils/countries_flag.json";
35
+ import { postcodeValidator } from "postcode-validator";
36
+
37
+ const CheckoutDetailsForm = ({
38
+ currency,
39
+ amount,
40
+ metadata,
41
+ // discounted_price,
42
+ // product_id,
43
+ formik,
44
+ isFree,
45
+ // paystackConfiguration,
46
+ // product,
47
+ showPayment,
48
+ onCancel,
49
+ btn_color,
50
+ onPaymentComplete,
51
+ Request,
52
+ }: {
53
+ currency: string;
54
+ amount: number;
55
+ metadata?: any;
56
+ // discounted_price?: number;
57
+ // product_id?: string;
58
+ discount_code?: string;
59
+ formik: any;
60
+ isFree: boolean;
61
+ // paystackConfiguration: any;
62
+ // product: any;
63
+ showPayment: boolean;
64
+ onCancel: () => void;
65
+ btn_color: string;
66
+ onPaymentComplete: (reference: string) => void;
67
+ Request: any;
68
+ }) => {
69
+ const stripe = useStripe();
70
+ const elements = useElements();
71
+
72
+ const ipAddress =
73
+ typeof window !== "undefined"
74
+ ? JSON.parse(window.localStorage.getItem("userIp") as string)
75
+ : "";
76
+
77
+ const { mutate: handleChargeCard, data: stripeData } =
78
+ useChargePayment(Request);
79
+
80
+ const [payingToStripe, setPayingToStripe] = useState(false);
81
+ const [userName, setName] = useState("");
82
+ const [country, setCountry] = useState(ipAddress?.country_name || "");
83
+ const [address, setAddress] = useState("");
84
+ const [zipCode, setZipCode] = useState("");
85
+ const [showBilling, setShowBilling] = useState<boolean>(false);
86
+
87
+ const handleSubmit = async (event: any) => {
88
+ setPayingToStripe(true);
89
+ event.preventDefault();
90
+
91
+ if (!stripe || !elements) {
92
+ return;
93
+ }
94
+
95
+ if (isFree) {
96
+ onPaymentComplete(metadata?.reference);
97
+ } else {
98
+ const cardNumberElement:
99
+ | StripeCardNumberElement
100
+ | StripeCardElement
101
+ | null = elements?.getElement(CardNumberElement);
102
+
103
+ if (!cardNumberElement) {
104
+ // Handle the case where cardNumberElement is null.
105
+ return;
106
+ }
107
+
108
+ const result = await stripe.createToken(cardNumberElement, {
109
+ name: userName,
110
+ address_country: country,
111
+ address_line1: address,
112
+ address_zip: zipCode,
113
+ });
114
+
115
+ if (result.error) {
116
+ setPayingToStripe(false);
117
+ customSnackbar(result?.error?.message as string, SnackbarType.error);
118
+ } else {
119
+ // Send the token to your server.
120
+ const data = {
121
+ source: result?.token?.id,
122
+ amount,
123
+ currency,
124
+ metadata,
125
+ };
126
+
127
+ handleChargeCard(data, {
128
+ onSuccess: () => {
129
+ setTimeout(() => {
130
+ onPaymentComplete(metadata?.reference);
131
+ setPayingToStripe(false);
132
+ }, 3000);
133
+ // redeemDiscount(discounted_price);
134
+ },
135
+ onError: (err: any) => {
136
+ setPayingToStripe(false);
137
+ customSnackbar(err?.raw?.message, SnackbarType.error);
138
+ },
139
+ });
140
+ }
141
+ }
142
+ };
143
+
144
+ const [isLessThan768] = useMediaQuery("(max-width: 767px)");
145
+
146
+ const inputStyle = {
147
+ backgroundColor: "#fff",
148
+ iconColor: "#c4f0ff",
149
+ color: "#131316",
150
+ fontWeight: "500",
151
+ fontFamily: "inherit",
152
+ fontSize: isLessThan768 ? "14px" : "16px",
153
+ fontSmoothing: "antialiased",
154
+ ":-webkit-autofill": {
155
+ color: "#131316",
156
+ },
157
+ "::placeholder": {
158
+ color: "#A6ABB0",
159
+ },
160
+ lineHeight: "20px",
161
+ letterSpacing: "-0.064px",
162
+ };
163
+ const [isValidCode, setIsValidCode] = useState<boolean | null>(null);
164
+
165
+ return (
166
+ <Box>
167
+ {!isFree ? (
168
+ <Collapse in={showPayment} animateOpacity>
169
+ <Box
170
+ border={"1px solid #EFF1F6"}
171
+ borderRadius="16px"
172
+ p={{ md: "24px", base: "16px" }}
173
+ mt={"24px"}
174
+ fontFamily="Bricolage Grotesque"
175
+ >
176
+ <Box>
177
+ <Flex
178
+ justifyContent={"space-between"}
179
+ alignItems="center"
180
+ mb={"24px"}
181
+ >
182
+ <Heading
183
+ fontSize={{ md: "20px", base: "16px" }}
184
+ lineHeight={"120%"}
185
+ fontWeight={600}
186
+ letterSpacing={{ md: "-0.4px", base: "-0.2px" }}
187
+ color={
188
+ !formik.values.cardHolderName && !showPayment
189
+ ? "#888F95"
190
+ : `${Colors.black300}`
191
+ }
192
+ >
193
+ Pay with Card
194
+ </Heading>
195
+
196
+ <Box
197
+ cursor={"pointer"}
198
+ onClick={() => {
199
+ formik.setFieldValue("country", "");
200
+ onCancel();
201
+ }}
202
+ >
203
+ <CancelFilledIcon />
204
+ </Box>
205
+ </Flex>
206
+
207
+ <Text
208
+ fontSize={{ md: "16px", base: "14px" }}
209
+ fontWeight="500"
210
+ mb={"12px"}
211
+ lineHeight={"20px"}
212
+ letterSpacing={"-0.064px"}
213
+ >
214
+ Card Information
215
+ </Text>
216
+ <Grid
217
+ border={"1px solid"}
218
+ borderColor={Colors.gray50}
219
+ borderRadius="12px"
220
+ p="1px"
221
+ gridTemplateColumns={"repeat(2, 1fr)"}
222
+ >
223
+ <GridItem
224
+ borderBottom={"1px solid"}
225
+ borderColor={Colors.gray50}
226
+ colSpan={2}
227
+ >
228
+ <Box
229
+ bg={"#fff"}
230
+ height={"48px"}
231
+ fontSize={"14px"}
232
+ borderRadius={"8px"}
233
+ alignItems="center"
234
+ px={"15px"}
235
+ py="15px"
236
+ >
237
+ <CardNumberElement
238
+ options={{
239
+ style: {
240
+ base: inputStyle,
241
+ invalid: {
242
+ iconColor: "red",
243
+ color: "red",
244
+ },
245
+ },
246
+ }}
247
+ />
248
+ </Box>
249
+ </GridItem>
250
+
251
+ <GridItem borderRight={"1px solid"} borderColor={Colors.gray50}>
252
+ <Box
253
+ bg={"#fff"}
254
+ height={"48px"}
255
+ fontSize={"14px"}
256
+ borderRadius={"8px"}
257
+ alignItems="center"
258
+ px={"15px"}
259
+ py="13px"
260
+ >
261
+ <CardExpiryElement
262
+ options={{
263
+ style: {
264
+ base: inputStyle,
265
+ invalid: {
266
+ iconColor: "red",
267
+ color: "red",
268
+ },
269
+ },
270
+ }}
271
+ />
272
+ </Box>
273
+ </GridItem>
274
+ <GridItem>
275
+ <Box
276
+ bg={"#fff"}
277
+ height={"48px"}
278
+ fontSize={"14px"}
279
+ borderRadius={"8px"}
280
+ alignItems="center"
281
+ px={"15px"}
282
+ py="13px"
283
+ >
284
+ <CardCvcElement
285
+ options={{
286
+ style: {
287
+ base: inputStyle,
288
+ invalid: {
289
+ iconColor: "red",
290
+ color: "red",
291
+ },
292
+ },
293
+ }}
294
+ />
295
+ </Box>
296
+ </GridItem>
297
+ </Grid>
298
+ <FormControl mt={"24px"}>
299
+ <Input
300
+ id={"cardHolderName"}
301
+ label="Cardholder name"
302
+ placeholder="Cardholder name"
303
+ name="cardHolderName"
304
+ type="text"
305
+ onChange={formik.handleChange}
306
+ value={formik.values.cardHolderName}
307
+ fontFamily="inherit"
308
+ // required
309
+ />
310
+ </FormControl>
311
+ </Box>
312
+
313
+ <FormControl mt="24px">
314
+ <CountrySelect
315
+ value={formik.values.country}
316
+ onChange={(e) => formik.setFieldValue("country", e)}
317
+ fontFamily="inherit"
318
+ />
319
+ </FormControl>
320
+ <FormControl mt="24px">
321
+ <Input
322
+ id="street"
323
+ label="Street Address"
324
+ placeholder="Street Address"
325
+ name="street"
326
+ fontSize={"14px"}
327
+ type="text"
328
+ bg="#EFF1F6"
329
+ onChange={formik.handleChange}
330
+ value={formik.values.street}
331
+ fontFamily="inherit"
332
+ // required
333
+ />
334
+ </FormControl>
335
+ <Flex mt="24px" gap="16px">
336
+ <FormControl>
337
+ <Input
338
+ label="Zip Code"
339
+ placeholder="Zip Code"
340
+ id="zipCode"
341
+ name="zipCode"
342
+ fontSize={"14px"}
343
+ type="text"
344
+ bg="#EFF1F6"
345
+ onChange={(e) => {
346
+ setZipCode(e.target.value);
347
+ formik.setFieldValue("zipCode", e.target.value);
348
+
349
+ setIsValidCode(
350
+ countries_flag?.find(
351
+ (i) => i.name === formik.values.country
352
+ )?.alpha2Code ?? ""
353
+ ? postcodeValidator(
354
+ e.target.value,
355
+ countries_flag?.find(
356
+ (i) => i.name === formik.values.country
357
+ )?.alpha2Code ?? ""
358
+ )
359
+ : false
360
+ );
361
+ }}
362
+ value={formik.values.zipCode}
363
+ fontFamily="inherit"
364
+ />
365
+
366
+ {isValidCode !== null && (
367
+ <Text
368
+ color={isValidCode ? "#0BA162" : "red"}
369
+ fontSize="12px"
370
+ mt="5px"
371
+ >
372
+ {isValidCode ? "Valid zip code" : "Invalid zip code"}
373
+ </Text>
374
+ )}
375
+ </FormControl>
376
+ </Flex>
377
+ <Button
378
+ size="large"
379
+ variant="primary"
380
+ bgColor={btn_color}
381
+ label="Confirm Payment"
382
+ width={"100%"}
383
+ mt={"12px"}
384
+ onClick={(e: any) => {
385
+ handleSubmit(e);
386
+ }}
387
+ isLoading={payingToStripe}
388
+ disabled={payingToStripe}
389
+ _hover={{
390
+ opacity: 0.8,
391
+ _after: {
392
+ background: btn_color,
393
+ },
394
+ }}
395
+ fontFamily="inherit"
396
+ />
397
+
398
+ <Flex
399
+ mt={"16px"}
400
+ mb={"10px"}
401
+ w="100%"
402
+ justify="center"
403
+ gap={"6px"}
404
+ justifyContent={"center"}
405
+ // alignItems={"end"}
406
+ >
407
+ <Text
408
+ fontSize={"14px"}
409
+ lineHeight={"22px"}
410
+ color={"#4D5760"}
411
+ // fontFamily={headerFontFamily}
412
+ >
413
+ Payments are secured and encrypted
414
+ </Text>
415
+ <LockIcon mt={0.5} boxSize={6} />
416
+ </Flex>
417
+ </Box>
418
+ </Collapse>
419
+ ) : (
420
+ <></>
421
+ )}
422
+ </Box>
423
+ );
424
+ };
425
+
426
+ export default CheckoutDetailsForm;
@@ -0,0 +1,63 @@
1
+ /** @format */
2
+
3
+ import {
4
+ Drawer,
5
+ DrawerBody,
6
+ DrawerCloseButton,
7
+ DrawerContent,
8
+ DrawerHeader,
9
+ DrawerOverlay,
10
+ } from "@chakra-ui/react";
11
+
12
+ interface IModal {
13
+ headerText?: string;
14
+ isOpen: boolean;
15
+ onClose: () => void;
16
+ children: any;
17
+ modalBodyStyle?: any;
18
+ }
19
+
20
+ const DrawerModal = ({
21
+ isOpen,
22
+ onClose,
23
+ children,
24
+ headerText,
25
+ modalBodyStyle,
26
+ }: IModal) => {
27
+ return (
28
+ <Drawer isOpen={isOpen} onClose={onClose} placement="bottom">
29
+ <DrawerOverlay bg="rgba(219, 222, 229, 0.6)" />
30
+ <DrawerContent
31
+ borderRadius="20px"
32
+ mb="10px"
33
+ ml={{ base: "2%", md: "initial" }}
34
+ w={{ base: "96%", md: "456px" }}
35
+ maxW={{ base: "96%", md: "456px" }}
36
+ mr={{ md: "10px" }}
37
+ p="0"
38
+ maxHeight={{ lg: "95vh", base: "80vh" }}
39
+ fontFamily="Bricolage Grotesque"
40
+ >
41
+ {headerText && (
42
+ <>
43
+ <DrawerCloseButton />
44
+ <DrawerHeader
45
+ fontSize="18px"
46
+ lineHeight="24px"
47
+ fontWeight={700}
48
+ px={{ base: "16px", lg: "24px" }}
49
+ fontFamily="inherit"
50
+ >
51
+ {headerText}
52
+ </DrawerHeader>
53
+ </>
54
+ )}
55
+ <DrawerBody p="16px" {...modalBodyStyle}>
56
+ {children}
57
+ </DrawerBody>
58
+ </DrawerContent>
59
+ </Drawer>
60
+ );
61
+ };
62
+
63
+ export default DrawerModal;