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,772 @@
1
+ /** @format */
2
+
3
+ /* eslint-disable @typescript-eslint/no-use-before-define */
4
+
5
+ import {
6
+ Box,
7
+ Grid,
8
+ GridItem,
9
+ Heading,
10
+ Flex,
11
+ Text,
12
+ useDisclosure,
13
+ } from "@chakra-ui/react";
14
+ import {
15
+ Button,
16
+ ClickTooltip,
17
+ Colors,
18
+ customSnackbar,
19
+ InfoIcon,
20
+ Input,
21
+ NotificationsFilledIcon,
22
+ SnackbarType,
23
+ } from "mainstack-design-system";
24
+ import {
25
+ useEffect,
26
+ useState,
27
+ ReactNode,
28
+ forwardRef,
29
+ useImperativeHandle,
30
+ } from "react";
31
+ import { stringifyPrice } from "utils";
32
+ import { Elements } from "@stripe/react-stripe-js";
33
+ import { loadStripe, Stripe } from "@stripe/stripe-js";
34
+ import {
35
+ useGetTransactionFees,
36
+ useChargePayment,
37
+ useVerifyPayment,
38
+ useInitPayment,
39
+ } from "api";
40
+ import { FormikProvider, useFormik } from "formik";
41
+ // import { v4 as uuidv4 } from "uuid";
42
+ import * as yup from "yup";
43
+ import CheckoutDetailsForm from "components/CheckoutForm";
44
+ import { usePayment } from "hooks/usePayment";
45
+ import { PaystackProps } from "react-paystack/dist/types";
46
+ import { CURRENCIES } from "enums/currenciesEnums";
47
+ import { PaystackPaymentModal } from "components/PaystackPaymentModal";
48
+ import PaystackPaymentErrorModal from "components/PaystackPaymentErrorModal";
49
+ import WalletPay from "components/WalletPay";
50
+ import { TPaymentConfig, TPaymentOption } from "types";
51
+ import InitializeAPIConfig from "api/config";
52
+
53
+ interface IMainstackPaymentProps {
54
+ paymentConfig: TPaymentConfig;
55
+
56
+ summaryTitle?: string | ReactNode;
57
+ customForm?: ReactNode;
58
+ customFormValidation?: { [key: string]: any };
59
+ customFormValues?: { [key: string]: string };
60
+
61
+ // functions
62
+ onGoBack: () => void;
63
+ // useful if you want to do something lke trigger your formik errors when init payment is called
64
+ onInitializePayment?: () => void;
65
+ onPaymentComplete: (reference: string) => void;
66
+ }
67
+
68
+ const MainstackPayment = forwardRef(
69
+ (
70
+ {
71
+ paymentConfig,
72
+ summaryTitle,
73
+ customForm,
74
+ customFormValidation,
75
+ customFormValues,
76
+ onGoBack,
77
+ onInitializePayment,
78
+ onPaymentComplete,
79
+ }: IMainstackPaymentProps,
80
+ ref
81
+ ) => {
82
+ const {
83
+ reference,
84
+ currency,
85
+ amount,
86
+ discountAmount,
87
+ customizations,
88
+ accountId,
89
+ transactionFeesSlug,
90
+ paymentOptions,
91
+ paystackRedirectUrl,
92
+ userAllowsWalletPayment,
93
+ userAllowsCardPayment,
94
+ baseUrl,
95
+ authMSUrl,
96
+ } = paymentConfig;
97
+ const { Request, AuthMSRequest } = InitializeAPIConfig(baseUrl, authMSUrl);
98
+
99
+ const [showCheckoutForm, setShowCheckoutForm] = useState(false);
100
+ const [showContact, setShowContact] = useState<boolean>(true);
101
+ const [showPayment, setShowPayment] = useState<boolean>(false);
102
+ const [chargeTransactionData, setChargeTransactionData] = useState<
103
+ { reference: string; url?: string } | undefined
104
+ >();
105
+ const [stripeValidation, setStripevalidation] = useState<boolean>(false);
106
+
107
+ const isAvailablePaymentMethod = (paymentMethod: TPaymentOption) => {
108
+ if (!paymentOptions || paymentOptions.length <= 0) return true;
109
+ return paymentOptions?.includes(paymentMethod);
110
+ };
111
+
112
+ const StripeValidationSchema = yup.object().shape({
113
+ email: yup
114
+ .string()
115
+ .matches(
116
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
117
+ "Please enter a valid email"
118
+ )
119
+ .required("Please enter your email address"),
120
+ fullname: yup.string().required("Please enter your Fullname"),
121
+ cardNumber: yup
122
+ .string()
123
+ .required("Please enter your card number")
124
+ .max(16),
125
+ cardHolderName: yup
126
+ .string()
127
+ .required("Please enter your card holder name"),
128
+ cardExpDate: yup.string().required("Please enter your card expiry date"),
129
+ securityCode: yup.string().required("Please enter your security code"),
130
+ country: yup.string().required("Please select a country"),
131
+ street: yup.string().required("Please enter your street address"),
132
+ ...customFormValidation,
133
+ });
134
+
135
+ const PaystackValidationSchema = yup.object().shape({
136
+ email: yup
137
+ .string()
138
+ .matches(
139
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
140
+ "Please enter a valid email"
141
+ )
142
+ .required("Please enter your email address"),
143
+ fullname: yup.string().required("Please enter your Fullname"),
144
+ currency: yup.string().required("Please select a currency."),
145
+ ...customFormValidation,
146
+ });
147
+
148
+ // const discountPrice = service?.price?.discount_price;
149
+
150
+ const { getAmount, getCountryCurrency } = usePayment(currency);
151
+
152
+ const ipAddress =
153
+ typeof window !== "undefined"
154
+ ? JSON.parse(window.localStorage.getItem("userIp") as string)
155
+ : "";
156
+
157
+ // payment
158
+ const { data: newTransactionFeesResponse, isLoading: feesLoading } =
159
+ useGetTransactionFees(
160
+ {
161
+ account_id: accountId,
162
+ amount,
163
+ currency,
164
+ slug: transactionFeesSlug,
165
+ },
166
+ AuthMSRequest
167
+ );
168
+
169
+ const { mutate: handleInitPayment, isPending: isInitializingPayment } =
170
+ useInitPayment(Request);
171
+ const { mutate: handleChargeCard } = useChargePayment(Request);
172
+ const { data: verifyPaymentData, refetch: verifyPayment } =
173
+ useVerifyPayment(chargeTransactionData?.reference as string, Request);
174
+
175
+ const formik = useFormik({
176
+ initialValues: {
177
+ email: "",
178
+ fullname: "",
179
+ message: "",
180
+ phone: "",
181
+ currency,
182
+ discount_code: "",
183
+ paymentType: "",
184
+ cardHolderName: "",
185
+ country: "",
186
+ street: "",
187
+ zipCode: "",
188
+ additional_details: "",
189
+ isCard: false,
190
+ ...customFormValues,
191
+ },
192
+ validationSchema: stripeValidation
193
+ ? StripeValidationSchema
194
+ : PaystackValidationSchema,
195
+
196
+ onSubmit: (values, { setSubmitting }) => {
197
+ if (newAmount === 0) {
198
+ // success callback
199
+ onPaymentComplete(reference);
200
+ return;
201
+ }
202
+ setSubmitting(true);
203
+ handleInitPayment(
204
+ {
205
+ amount: newAmount,
206
+ reference,
207
+ currency,
208
+ metadata,
209
+ },
210
+ {
211
+ onSuccess: () => {
212
+ if (
213
+ (values.currency === CURRENCIES.NGN &&
214
+ isAvailablePaymentMethod("paystack")) ||
215
+ (values.currency === CURRENCIES.GHS &&
216
+ isAvailablePaymentMethod("startbutton"))
217
+ ) {
218
+ handleChargeCard(
219
+ {
220
+ amount: newAmount,
221
+ currency,
222
+ metadata,
223
+ callback_url: paystackRedirectUrl,
224
+ },
225
+ {
226
+ onSuccess(data) {
227
+ const { data: url, reference: paymentRef } = data;
228
+ if (url && paymentRef) {
229
+ setChargeTransactionData({
230
+ url,
231
+ reference: paymentRef,
232
+ });
233
+ }
234
+ },
235
+ onError(error) {
236
+ onOpenPaystackErrorModal();
237
+ },
238
+ }
239
+ );
240
+ } else {
241
+ setShowCheckoutForm(true);
242
+ setSubmitting(false);
243
+ }
244
+ },
245
+ onError: (error: any) => {
246
+ setSubmitting(false);
247
+ customSnackbar(
248
+ error?.response?.data ?? "Error loading payment gateway",
249
+ SnackbarType.error
250
+ );
251
+ },
252
+ }
253
+ );
254
+ },
255
+ });
256
+
257
+ useImperativeHandle(ref, () => ({
258
+ updateCustomFormValues(label: string, value: string) {
259
+ formik.setFieldValue(label, value);
260
+ },
261
+ }));
262
+
263
+ const amountFromMarkup =
264
+ newTransactionFeesResponse?.payload?.local_amount ?? 0;
265
+ const TransactionFeeValueFromEndpoint =
266
+ newTransactionFeesResponse?.payload?.local_transaction_fee ?? 0;
267
+
268
+ const InternationalFees =
269
+ newTransactionFeesResponse?.payload?.local_international_card_fee ?? 0;
270
+
271
+ const DollarInternationalFees =
272
+ newTransactionFeesResponse?.payload?.dollar_international_card_fee ?? 0;
273
+ const TotalAmountAfterTransactionFeesHasBeenAdded =
274
+ parseFloat(amountFromMarkup) +
275
+ parseFloat(TransactionFeeValueFromEndpoint) +
276
+ (currency !== CURRENCIES.NGN &&
277
+ (formik.values.country !== "" || ipAddress?.currency !== currency)
278
+ ? getCountryCurrency(formik?.values?.country) !== currency
279
+ ? Number(InternationalFees)
280
+ : 0
281
+ : 0);
282
+
283
+ const isIntlCard =
284
+ formik.values.country !== ""
285
+ ? getCountryCurrency(formik?.values?.country) !== currency
286
+ : ipAddress?.currency !== currency;
287
+
288
+ const newAmount =
289
+ // Uganda Shillings must be round to nearest 100th
290
+ currency === CURRENCIES.UGX
291
+ ? Math.ceil(TotalAmountAfterTransactionFeesHasBeenAdded) * 100
292
+ : Math.round(
293
+ TotalAmountAfterTransactionFeesHasBeenAdded *
294
+ (newTransactionFeesResponse?.payload?.is_zero_decimal_currency
295
+ ? 1
296
+ : 100)
297
+ );
298
+
299
+ // eslint-disable-next-line @typescript-eslint/naming-convention
300
+ const transaction_fee = parseFloat(
301
+ (
302
+ Number(newTransactionFeesResponse?.payload?.dollar_transaction_fee) +
303
+ Number(isIntlCard ? DollarInternationalFees : 0)
304
+ ).toFixed(2)
305
+ );
306
+ const metadata = {
307
+ ...paymentConfig.metadata,
308
+ phone_number: formik.values?.phone,
309
+ reference,
310
+ email: formik.values.email,
311
+ name: formik.values?.fullname,
312
+ country: formik?.values?.country,
313
+ actual_price: Number(newTransactionFeesResponse?.payload?.dollar_amount),
314
+ transaction_fee,
315
+ international_fee: Number(
316
+ isIntlCard ? Number(DollarInternationalFees) : 0
317
+ ),
318
+ isIntlCard,
319
+ };
320
+
321
+ const stripePromise = async () => {
322
+ try {
323
+ return await loadStripe(
324
+ import.meta.env.REACT_APP_STRIPE_SECRET_KEY as string
325
+ );
326
+ } catch (error) {
327
+ customSnackbar("Error loading payment gateway", SnackbarType.error);
328
+ }
329
+ };
330
+
331
+ useEffect(() => {
332
+ // const currency = getCurrency();
333
+ if (
334
+ // isFree
335
+ currency !== "NGN" &&
336
+ currency !== "GHS"
337
+ ) {
338
+ setShowCheckoutForm(true);
339
+ } else {
340
+ setShowCheckoutForm(false);
341
+ }
342
+ }, [currency]);
343
+
344
+ const proceedToCheckoutForm = async () => {
345
+ if (onInitializePayment) {
346
+ onInitializePayment();
347
+ }
348
+
349
+ const errors = await formik.validateForm();
350
+ if (Object.keys(errors).length <= 0) {
351
+ setStripevalidation(true);
352
+ if (newAmount === 0) {
353
+ // success callback
354
+ onPaymentComplete(reference);
355
+ return;
356
+ }
357
+ handleInitPayment(
358
+ {
359
+ amount: newAmount,
360
+ reference,
361
+ currency,
362
+ metadata,
363
+ },
364
+ {
365
+ onSuccess() {
366
+ setShowPayment(true);
367
+ setShowContact(!showContact);
368
+ },
369
+ onError(error: any) {
370
+ customSnackbar(error?.response?.data, SnackbarType.error);
371
+ },
372
+ }
373
+ );
374
+ }
375
+ return;
376
+ };
377
+
378
+ const editContact = () => {
379
+ setShowPayment(false);
380
+ setShowContact(!showContact);
381
+ };
382
+
383
+ const {
384
+ isOpen: isPaystackErrorModalOpen,
385
+ onClose: onClosePaystackErrorModal,
386
+ onOpen: onOpenPaystackErrorModal,
387
+ } = useDisclosure();
388
+
389
+ const handleClosePaystackModal = async () => {
390
+ setChargeTransactionData((prevData: any) => ({
391
+ ...prevData,
392
+ url: undefined,
393
+ }));
394
+ const res = await verifyPayment();
395
+ if (res.isSuccess) {
396
+ onPaymentComplete(chargeTransactionData?.reference as string);
397
+ } else {
398
+ customSnackbar(`${res?.error}`, SnackbarType.error);
399
+ }
400
+ };
401
+
402
+ return (
403
+ <>
404
+ {
405
+ <Grid
406
+ p={{ base: "16px", lg: "48px" }}
407
+ templateColumns={{ base: "100%", lg: "repeat(2, 1fr)" }}
408
+ gap={"40px"}
409
+ maxWidth={"1800px"}
410
+ width={"100%"}
411
+ mx={"auto"}
412
+ fontFamily={customizations?.font_family ?? "inherit"}
413
+ >
414
+ <GridItem order={{ base: "2", lg: "1" }}>
415
+ <FormikProvider value={formik}>
416
+ <form onSubmit={formik.handleSubmit}>
417
+ <Flex flexDirection={"column"} gap={"20px"} pb={"40px"}>
418
+ <Heading
419
+ fontSize={{ base: "1.25rem", lg: "1.5rem" }}
420
+ fontWeight={700}
421
+ lineHeight={"28.8px"}
422
+ letterSpacing={{ base: "-0.4px", lg: "-0.48px" }}
423
+ fontFamily="inherit"
424
+ >
425
+ Checkout Details
426
+ </Heading>
427
+ <Input
428
+ id="fullname"
429
+ label="Your name *"
430
+ name="fullname"
431
+ onChange={(e) =>
432
+ formik.setFieldValue("fullname", e.target.value)
433
+ }
434
+ value={formik.values?.fullname}
435
+ placeholder="name"
436
+ type="text"
437
+ error={Boolean(formik?.errors?.fullname)}
438
+ errorMessage={formik?.errors?.fullname as string}
439
+ fontFamily={"inherit"}
440
+ />
441
+ <Input
442
+ id="email"
443
+ label="Email address *"
444
+ name="email"
445
+ onChange={(e) =>
446
+ formik.setFieldValue("email", e.target.value)
447
+ }
448
+ value={formik.values?.email}
449
+ placeholder="email"
450
+ type="email"
451
+ error={Boolean(formik?.errors?.email)}
452
+ errorMessage={formik?.errors?.email as string}
453
+ fontFamily={"inherit"}
454
+ />
455
+ {customForm}
456
+ </Flex>
457
+ {isAvailablePaymentMethod("wallet") && (
458
+ <>
459
+ {" "}
460
+ {userAllowsWalletPayment &&
461
+ showContact &&
462
+ currency !== CURRENCIES.NGN &&
463
+ currency !== CURRENCIES.GHS && (
464
+ <Elements
465
+ stripe={stripePromise() as Promise<Stripe | null>}
466
+ options={{
467
+ appearance: {
468
+ variables: { borderRadius: "100px" },
469
+ },
470
+ }}
471
+ >
472
+ <WalletPay
473
+ metadata={metadata}
474
+ currency={currency}
475
+ // discounted_price={0}
476
+ // redeemDiscount={() => {}}
477
+ amount={newAmount}
478
+ formik={formik}
479
+ checkForErrors={async () => {
480
+ if (onInitializePayment) {
481
+ onInitializePayment();
482
+ }
483
+ const errors = await formik.validateForm();
484
+ return Object.keys(errors).length > 0;
485
+ }}
486
+ onPaymentComplete={onPaymentComplete}
487
+ Request={Request}
488
+ />
489
+ </Elements>
490
+ )}
491
+ </>
492
+ )}
493
+ {isAvailablePaymentMethod("stripe") && (
494
+ <>
495
+ {" "}
496
+ {userAllowsCardPayment &&
497
+ showCheckoutForm &&
498
+ showContact && (
499
+ <Flex mt={"12px"} gap="10px">
500
+ <Button
501
+ size="large"
502
+ variant="tertiary"
503
+ icon={
504
+ <NotificationsFilledIcon
505
+ transform={"rotate(-90deg)"}
506
+ boxSize={"20px"}
507
+ />
508
+ }
509
+ icontype="leading"
510
+ label={"Back"}
511
+ width="144px"
512
+ fontFamily="inherit"
513
+ onClick={onGoBack}
514
+ />
515
+ <Button
516
+ size="large"
517
+ variant="primary"
518
+ bgColor={customizations?.theme_color}
519
+ label={
520
+ customizations?.button_label ??
521
+ "Pay for Product"
522
+ }
523
+ width={"100%"}
524
+ isLoading={isInitializingPayment}
525
+ _hover={{
526
+ opacity: 0.8,
527
+ _after: {
528
+ background: customizations?.theme_color,
529
+ },
530
+ }}
531
+ fontFamily="inherit"
532
+ onClick={proceedToCheckoutForm}
533
+ // type="submit"
534
+ />
535
+ </Flex>
536
+ )}
537
+ {showCheckoutForm && (
538
+ <Elements
539
+ stripe={stripePromise() as Promise<Stripe | null>}
540
+ >
541
+ <CheckoutDetailsForm
542
+ metadata={metadata}
543
+ currency={currency}
544
+ amount={newAmount}
545
+ discount_code={formik.values.discount_code}
546
+ formik={formik}
547
+ isFree={newAmount === 0}
548
+ // paystackConfiguration={paystackConfiguration}
549
+ // product={product}
550
+ showPayment={showPayment}
551
+ onCancel={editContact}
552
+ btn_color={customizations?.theme_color as string}
553
+ onPaymentComplete={onPaymentComplete}
554
+ Request={Request}
555
+ />
556
+ </Elements>
557
+ )}
558
+ </>
559
+ )}
560
+ {(isAvailablePaymentMethod("paystack") ||
561
+ isAvailablePaymentMethod("startbutton")) && (
562
+ <>
563
+ {userAllowsCardPayment && !showCheckoutForm && (
564
+ <Flex mt={"12px"} gap="10px">
565
+ <Button
566
+ size="large"
567
+ variant="tertiary"
568
+ icon={
569
+ <NotificationsFilledIcon
570
+ transform={"rotate(-90deg)"}
571
+ boxSize={"20px"}
572
+ />
573
+ }
574
+ icontype="leading"
575
+ label={"Back"}
576
+ width="144px"
577
+ fontFamily="inherit"
578
+ onClick={onGoBack}
579
+ />
580
+ <Button
581
+ size="large"
582
+ variant="primary"
583
+ bgColor={customizations?.theme_color}
584
+ label={
585
+ customizations?.button_label ?? "Pay for Product"
586
+ }
587
+ width={"100%"}
588
+ type="submit"
589
+ isLoading={formik?.isSubmitting}
590
+ onClick={() => {
591
+ if (onInitializePayment) {
592
+ onInitializePayment();
593
+ }
594
+ }}
595
+ _hover={{
596
+ opacity: 0.8,
597
+ _after: {
598
+ background: customizations?.theme_color,
599
+ },
600
+ }}
601
+ fontFamily="inherit"
602
+ />
603
+ </Flex>
604
+ )}
605
+ </>
606
+ )}
607
+ </form>
608
+ </FormikProvider>
609
+ </GridItem>
610
+ <GridItem order={{ base: "1", lg: "2" }}>
611
+ <Box
612
+ p={{ base: "16px", lg: "24px" }}
613
+ borderRadius={"16px"}
614
+ boxShadow={
615
+ "0px 2px 6px 0px rgba(45, 59, 67, 0.06), 0px 2px 4px 0px rgba(45, 59, 67, 0.05)"
616
+ }
617
+ border={`1px solid ${Colors.gray50}`}
618
+ >
619
+ <Box
620
+ p={"16px"}
621
+ bg={Colors.gray50}
622
+ borderRadius={"12px"}
623
+ textAlign={"center"}
624
+ >
625
+ <Text fontSize={".875rem"} lineHeight={"22px"}>
626
+ {summaryTitle ?? metadata?.productName}
627
+ </Text>
628
+ <Heading
629
+ fontSize={"1.25rem"}
630
+ fontWeight={700}
631
+ lineHeight={"22px"}
632
+ fontFamily="inherit"
633
+ >
634
+ {currency} {stringifyPrice(amount)}
635
+ </Heading>
636
+ </Box>
637
+ <Flex flexDirection={"column"} gap={"20px"} mt={"32px"}>
638
+ <Flex width="100%" justifyContent={"space-between"}>
639
+ <Text fontSize={".875rem"} lineHeight={"22px"}>
640
+ Subtotal
641
+ </Text>
642
+ <Text
643
+ fontSize={".875rem"}
644
+ lineHeight={"22px"}
645
+ fontWeight={600}
646
+ >
647
+ {currency}{" "}
648
+ {Number(amountFromMarkup?.toString())
649
+ ?.toFixed(2)
650
+ ?.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
651
+ </Text>
652
+ </Flex>
653
+ {currency !== CURRENCIES.NGN &&
654
+ (formik.values.country !== "" ||
655
+ ipAddress?.currency !== currency) &&
656
+ getCountryCurrency(formik?.values?.country) !==
657
+ currency && (
658
+ <Flex width="100%" justifyContent={"space-between"}>
659
+ <Flex alignItems={"center"}>
660
+ <Text
661
+ fontSize={".875rem"}
662
+ lineHeight={"22px"}
663
+ mr={"8px"}
664
+ >
665
+ International Card Fee
666
+ </Text>
667
+ <ClickTooltip
668
+ body={`This fee (1.5%) covers the processing of
669
+ your international currency conversion.`}
670
+ label="International cards conversion fee"
671
+ triggerComponent={<InfoIcon boxSize={"20px"} />}
672
+ fontFamily="Bricolage Grotesque"
673
+ />
674
+ </Flex>
675
+ <Text
676
+ fontSize={".875rem"}
677
+ lineHeight={"22px"}
678
+ fontWeight={600}
679
+ >
680
+ {currency}{" "}
681
+ {InternationalFees?.toFixed(2)?.replace(
682
+ /\B(?=(\d{3})+(?!\d))/g,
683
+ ","
684
+ )}
685
+ </Text>
686
+ </Flex>
687
+ )}
688
+ {TotalAmountAfterTransactionFeesHasBeenAdded > 0 && (
689
+ <Flex width="100%" justifyContent={"space-between"}>
690
+ <Flex alignItems={"center"}>
691
+ <Text
692
+ fontSize={".875rem"}
693
+ lineHeight={"22px"}
694
+ mr={"8px"}
695
+ >
696
+ Fees
697
+ </Text>
698
+ <ClickTooltip
699
+ body={
700
+ <Text mb={"20px"}>
701
+ Platform fee :{" "}
702
+ {
703
+ newTransactionFeesResponse?.payload
704
+ ?.platform_fee?.display
705
+ }
706
+ <br />
707
+ Processor fee :{" "}
708
+ {
709
+ newTransactionFeesResponse?.payload
710
+ ?.processor_fee?.display
711
+ }
712
+ </Text>
713
+ }
714
+ label="Fees"
715
+ triggerComponent={<InfoIcon boxSize={"20px"} />}
716
+ fontFamily="Bricolage Grotesque"
717
+ />
718
+ </Flex>
719
+ <Text
720
+ fontSize={".875rem"}
721
+ lineHeight={"22px"}
722
+ fontWeight={600}
723
+ >
724
+ {currency}{" "}
725
+ {TransactionFeeValueFromEndpoint?.toFixed(2)?.replace(
726
+ /\B(?=(\d{3})+(?!\d))/g,
727
+ ","
728
+ )}
729
+ </Text>
730
+ </Flex>
731
+ )}
732
+ <Flex width="100%" justifyContent={"space-between"}>
733
+ <Text
734
+ fontSize={"1.125rem"}
735
+ lineHeight={"24px"}
736
+ fontWeight={700}
737
+ letterSpacing={"-0.36px"}
738
+ >
739
+ Total
740
+ </Text>
741
+ <Text
742
+ fontSize={"1.125rem"}
743
+ lineHeight={"24px"}
744
+ fontWeight={700}
745
+ letterSpacing={"-0.36px"}
746
+ >
747
+ {currency}{" "}
748
+ {TotalAmountAfterTransactionFeesHasBeenAdded?.toFixed(
749
+ 2
750
+ )?.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
751
+ </Text>
752
+ </Flex>
753
+ </Flex>
754
+ </Box>
755
+ </GridItem>
756
+ <PaystackPaymentModal
757
+ isOpen={!!chargeTransactionData?.url}
758
+ handleCloseModal={handleClosePaystackModal}
759
+ src={chargeTransactionData?.url as string}
760
+ />
761
+ <PaystackPaymentErrorModal
762
+ isOpen={isPaystackErrorModalOpen}
763
+ onClose={onClosePaystackErrorModal}
764
+ />
765
+ </Grid>
766
+ }
767
+ </>
768
+ );
769
+ }
770
+ );
771
+
772
+ export default MainstackPayment;