tf-checkout-react 1.6.6 → 1.7.1

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 (131) hide show
  1. package/README.md +401 -59
  2. package/dist/adapters/customFields.d.ts +1 -0
  3. package/dist/api/checkout.d.ts +2 -0
  4. package/dist/api/common.d.ts +1 -0
  5. package/dist/api/index.d.ts +2 -0
  6. package/dist/api/preRegistrationComplete.d.ts +1 -1
  7. package/dist/components/addonsContainer/AddonComponent.d.ts +6 -1
  8. package/dist/components/addonsContainer/SimpleAddonsContainer.d.ts +17 -0
  9. package/dist/components/addonsContainer/index.d.ts +6 -1
  10. package/dist/components/billing-info-container/hooks/index.d.ts +3 -0
  11. package/dist/components/billing-info-container/hooks/usePaymentContext.d.ts +5 -0
  12. package/dist/components/billing-info-container/hooks/usePaymentRedirect.d.ts +14 -0
  13. package/dist/components/billing-info-container/hooks/useStripePayment.d.ts +18 -0
  14. package/dist/components/billing-info-container/index.d.ts +13 -2
  15. package/dist/components/billing-info-container/utils.d.ts +25 -1
  16. package/dist/components/common/DatePickerField.d.ts +7 -1
  17. package/dist/components/confirmationContainer/index.d.ts +4 -1
  18. package/dist/components/countdown/index.d.ts +1 -1
  19. package/dist/components/forgotPasswordModal/index.d.ts +2 -1
  20. package/dist/components/myTicketsContainer/index.d.ts +3 -2
  21. package/dist/components/orderDetailsContainer/index.d.ts +8 -1
  22. package/dist/components/paymentContainer/OrderDetails.d.ts +9 -0
  23. package/dist/components/paymentContainer/handlePayment.d.ts +15 -0
  24. package/dist/components/paymentContainer/index.d.ts +12 -6
  25. package/dist/components/preRegistration/FieldsSection.d.ts +7 -1
  26. package/dist/components/preRegistration/PreRegistrationComplete.d.ts +6 -0
  27. package/dist/components/preRegistration/constants.d.ts +2 -2
  28. package/dist/components/preRegistration/index.d.ts +6 -0
  29. package/dist/components/resetPasswordContainer/index.d.ts +2 -2
  30. package/dist/components/ticketsContainer/InfoIcon.d.ts +5 -0
  31. package/dist/components/ticketsContainer/TicketsSection.d.ts +3 -2
  32. package/dist/components/ticketsContainer/TimeSlotsSection.d.ts +25 -0
  33. package/dist/components/ticketsContainer/index.d.ts +29 -5
  34. package/dist/components/timerWidget/index.d.ts +2 -1
  35. package/dist/constants/index.d.ts +5 -0
  36. package/dist/index.d.ts +4 -1
  37. package/dist/tf-checkout-react.cjs.development.js +11231 -9563
  38. package/dist/tf-checkout-react.cjs.development.js.map +1 -1
  39. package/dist/tf-checkout-react.cjs.production.min.js +1 -1
  40. package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
  41. package/dist/tf-checkout-react.esm.js +11194 -9529
  42. package/dist/tf-checkout-react.esm.js.map +1 -1
  43. package/dist/tf-checkout-styles.css +1 -1
  44. package/dist/types/add_on.d.ts +1 -0
  45. package/dist/types/checkoutPageConfigs.d.ts +1 -1
  46. package/dist/types/order-data.d.ts +3 -1
  47. package/dist/utils/auth.d.ts +8 -0
  48. package/dist/utils/customFields.d.ts +11 -0
  49. package/dist/utils/getDomain.d.ts +1 -1
  50. package/dist/utils/index.d.ts +1 -1
  51. package/dist/utils/setConfigs.d.ts +1 -0
  52. package/package.json +14 -8
  53. package/src/adapters/customFields.ts +7 -1
  54. package/src/api/auth.ts +2 -1
  55. package/src/api/checkout.ts +9 -4
  56. package/src/api/common.ts +49 -2
  57. package/src/api/index.ts +1 -0
  58. package/src/api/interceptors.ts +7 -23
  59. package/src/api/preRegistrationComplete.ts +1 -1
  60. package/src/api/publicRequest.ts +10 -0
  61. package/src/components/addonsContainer/AddonComponent.tsx +96 -11
  62. package/src/components/addonsContainer/SimpleAddonsContainer.tsx +420 -0
  63. package/src/components/addonsContainer/index.tsx +198 -47
  64. package/src/components/billing-info-container/hooks/index.ts +3 -0
  65. package/src/components/billing-info-container/hooks/usePaymentContext.ts +22 -0
  66. package/src/components/billing-info-container/hooks/usePaymentRedirect.ts +147 -0
  67. package/src/components/billing-info-container/hooks/useStripePayment.ts +121 -0
  68. package/src/components/billing-info-container/index.tsx +827 -406
  69. package/src/components/billing-info-container/{utils.ts → utils.tsx} +119 -0
  70. package/src/components/common/CheckboxField/index.tsx +1 -1
  71. package/src/components/common/CustomField.tsx +38 -2
  72. package/src/components/common/DatePickerField.tsx +25 -10
  73. package/src/components/common/SnackbarAlert.tsx +32 -34
  74. package/src/components/confirmationContainer/config.ts +3 -3
  75. package/src/components/confirmationContainer/index.tsx +20 -1
  76. package/src/components/confirmationContainer/social-buttons.tsx +5 -3
  77. package/src/components/confirmationContainer/style.css +9 -5
  78. package/src/components/countdown/index.tsx +22 -22
  79. package/src/components/delegationsContainer/IssueComponent.tsx +2 -1
  80. package/src/components/forgotPasswordModal/index.tsx +44 -13
  81. package/src/components/loginForm/index.tsx +1 -1
  82. package/src/components/loginModal/index.tsx +19 -27
  83. package/src/components/loginModal/style.css +3 -1
  84. package/src/components/myTicketsContainer/index.tsx +13 -9
  85. package/src/components/orderDetailsContainer/index.tsx +206 -174
  86. package/src/components/paymentContainer/OrderDetails.tsx +257 -0
  87. package/src/components/paymentContainer/handlePayment.ts +86 -0
  88. package/src/components/paymentContainer/index.tsx +299 -259
  89. package/src/components/paymentContainer/style.css +141 -0
  90. package/src/components/preRegistration/FieldsSection.tsx +8 -0
  91. package/src/components/preRegistration/PreRegistrationComplete.tsx +128 -118
  92. package/src/components/preRegistration/PreRegistrationInformations.tsx +21 -15
  93. package/src/components/preRegistration/constants.tsx +10 -4
  94. package/src/components/preRegistration/index.tsx +194 -174
  95. package/src/components/registerForm/constants.tsx +3 -1
  96. package/src/components/registerForm/index.tsx +3 -3
  97. package/src/components/registerModal/index.tsx +47 -72
  98. package/src/components/resetPasswordContainer/index.tsx +20 -14
  99. package/src/components/seatMapContainer/TicketsSection.tsx +2 -2
  100. package/src/components/signupModal/index.tsx +13 -6
  101. package/src/components/ticketResale/index.tsx +7 -0
  102. package/src/components/ticketsContainer/InfoIcon.tsx +35 -0
  103. package/src/components/ticketsContainer/PromoCodeSection.tsx +34 -28
  104. package/src/components/ticketsContainer/TicketRow.tsx +1 -1
  105. package/src/components/ticketsContainer/TicketsSection.tsx +189 -57
  106. package/src/components/ticketsContainer/TimeSlotsSection.tsx +120 -0
  107. package/src/components/ticketsContainer/index.tsx +268 -106
  108. package/src/components/timerWidget/index.tsx +15 -3
  109. package/src/constants/index.ts +2 -0
  110. package/src/env.ts +14 -6
  111. package/src/hoc/CustomFields/index.tsx +9 -1
  112. package/src/index.ts +7 -2
  113. package/src/types/add_on.ts +1 -0
  114. package/src/types/api/cart.d.ts +8 -0
  115. package/src/types/api/checkout.d.ts +58 -7
  116. package/src/types/api/common.d.ts +30 -0
  117. package/src/types/api/orders.d.ts +19 -3
  118. package/src/types/api/payment.d.ts +6 -2
  119. package/src/types/api/preRegistrationComplete.d.ts +2 -2
  120. package/src/types/checkoutPageConfigs.ts +1 -1
  121. package/src/types/order-data.ts +3 -1
  122. package/src/types/pre-registration-complete.d.ts +6 -1
  123. package/src/utils/auth.ts +32 -0
  124. package/src/utils/cookies.ts +42 -11
  125. package/src/utils/customFields.ts +22 -0
  126. package/src/utils/getDomain.ts +10 -4
  127. package/src/utils/index.ts +1 -1
  128. package/src/utils/setConfigs.ts +3 -1
  129. package/dist/components/stripePayment/index.d.ts +0 -24
  130. package/src/components/stripePayment/index.tsx +0 -281
  131. package/src/components/stripePayment/style.css +0 -60
@@ -1,21 +1,27 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  /* eslint-disable react/no-unescaped-entities */
2
3
 
3
4
  import { CircularProgress } from '@mui/material'
4
5
  import { Form, Formik } from 'formik'
5
6
  import _get from 'lodash/get'
6
7
  import _identity from 'lodash/identity'
7
- import React, { useEffect, useState } from 'react'
8
+ import _map from 'lodash/map'
9
+ import React, { useEffect, useMemo, useState } from 'react'
10
+ import { Tooltip } from 'react-tooltip'
8
11
 
9
12
  import { getAddons, getCart, getCheckoutPageConfigs, postOnCheckout } from '../../api'
13
+ import { FEES_STYLES } from '../../constants'
10
14
  import { currencyNormalizerCreator } from '../../normalizers'
11
15
  import { ICheckoutPageConfigs } from '../../types'
12
16
  import {
17
+ CONFIGS,
13
18
  createCheckoutDataBodyWithDefaultHolder,
14
19
  createMarkup,
15
20
  getQueryVariable,
16
21
  isBrowser,
17
22
  } from '../../utils'
18
23
  import { VerificationPendingModal } from '../idVerificationContainer/VerificationPendingModal'
24
+ import InfoIcon from '../ticketsContainer/InfoIcon'
19
25
  import TimerWidget from '../timerWidget'
20
26
  import { addonsWithGroupsAdapter, cartAdapter } from './adapters'
21
27
  import AddonComponent from './AddonComponent'
@@ -40,6 +46,12 @@ export interface IAddonContainterProps {
40
46
  onConfirmSelectionError?: (error: any) => void;
41
47
  onCountdownFinish?: () => void;
42
48
  onPendingVerification?: () => void;
49
+ samePage?: boolean;
50
+ descriptionTrigger?: 'click' | 'hover' | 'always';
51
+ addOnDataWithCustomFields: any;
52
+ configs: any;
53
+
54
+ onAddOnSelect?: (id: string, value: string, addon: any) => void;
43
55
  }
44
56
 
45
57
  export interface ObjectLiteral {
@@ -58,6 +70,11 @@ export const AddonsContainter = ({
58
70
  onConfirmSelectionError = _identity,
59
71
  onCountdownFinish = _identity,
60
72
  onPendingVerification = _identity,
73
+ samePage,
74
+ descriptionTrigger = 'click',
75
+ addOnDataWithCustomFields,
76
+ configs,
77
+ onAddOnSelect = _identity,
61
78
  }: IAddonContainterProps) => {
62
79
  const eventId = getQueryVariable('event_id')
63
80
  const [addons, setAddons] = useState<any>([])
@@ -69,7 +86,16 @@ export const AddonsContainter = ({
69
86
  const [cartExpirationTime, setCartExpirationTime] = useState(0)
70
87
  const [pendingVerificationMessage, setPendingVerificationMessage] = useState()
71
88
 
89
+ const [visibleDescription, setVisibleDescription] = useState<string | null>(null)
90
+
91
+ const handleDescriptionToggle = (ticketId: string) => {
92
+ setVisibleDescription(current => (current === ticketId ? null : ticketId))
93
+ }
94
+
72
95
  useEffect(() => {
96
+ if (samePage) {
97
+ window.localStorage.removeItem('add_ons')
98
+ }
73
99
  const getAddonsPageInfo = async () => {
74
100
  try {
75
101
  if (eventId) {
@@ -222,8 +248,8 @@ export const AddonsContainter = ({
222
248
  total,
223
249
  })
224
250
  } catch (error) {
225
- if (error.response?.data?.data?.hasUnverifiedOrder) {
226
- setPendingVerificationMessage(error.response?.data?.message)
251
+ if ((error as any).response?.data?.data?.hasUnverifiedOrder) {
252
+ setPendingVerificationMessage((error as any).response?.data?.message)
227
253
  } else {
228
254
  onPostCheckoutError(error)
229
255
  onConfirmSelectionError(error)
@@ -255,6 +281,22 @@ export const AddonsContainter = ({
255
281
  window.localStorage.removeItem('add_ons')
256
282
  }
257
283
 
284
+ const initialValues = useMemo(() => {
285
+ const addOnsData: any = {}
286
+ if (addons?.length > 0 && addOnDataWithCustomFields?.fields?.length > 0) {
287
+ _map(addons, addon => {
288
+ _map(addOnDataWithCustomFields.fields, field => {
289
+ const { id, groupItems } = field
290
+ _map(groupItems, item => {
291
+ addOnsData[`${addon.id}-${id}-${item.name}`] = item.value
292
+ })
293
+ })
294
+ })
295
+ }
296
+
297
+ return addOnsData
298
+ }, [addons, addOnDataWithCustomFields])
299
+
258
300
  if (loading) {
259
301
  return (
260
302
  <div className={`${classNamePrefix}_loader`}>
@@ -263,9 +305,17 @@ export const AddonsContainter = ({
263
305
  )
264
306
  }
265
307
 
308
+ if (addons?.length === 0) {
309
+ return null
310
+ }
311
+
312
+ const params = new URL(`${window.location}`).searchParams
313
+ const addOnIsIncluded = params.get('include_add_on') === 'true'
314
+ const isResale = params.get('resale') === 'true'
315
+
266
316
  return (
267
317
  <>
268
- {!!cartExpirationTime && enableTimer && (
318
+ {!!cartExpirationTime && enableTimer && !samePage && (
269
319
  <TimerWidget
270
320
  expires_at={cartExpirationTime}
271
321
  onCountdownFinish={() => {
@@ -277,44 +327,72 @@ export const AddonsContainter = ({
277
327
  <div className={`${classNamePrefix}_container`}>
278
328
  <div className={`${classNamePrefix}_block`}>
279
329
  <div className={`${classNamePrefix}_line_block`}>
280
- <p className={`${classNamePrefix}_info_title`}>Get Your Tickets</p>
281
- <div className={`${classNamePrefix}_title`}>UPGRADES & ADD-ONS</div>
282
- <button
283
- type="button"
284
- className={`${classNamePrefix}_skip`}
285
- onClick={() => {
286
- handleClearAddons()
287
- handleConfirm({}, true)
288
- }}
289
- >
290
- Skip
291
- </button>
292
- </div>
293
- <div className={`${classNamePrefix}_subtitle`}>
294
- PLEASE SELECT FROM THE OPTIONAL ADD-ONS BELOW
330
+ {samePage ? null : (
331
+ <p className={`${classNamePrefix}_info_title`}>Get Your Tickets</p>
332
+ )}
333
+ {(!addons?.length || (isResale && addOnIsIncluded)) && samePage ? null : (
334
+ <div className={`${classNamePrefix}_title`}>UPGRADES & ADD-ONS</div>
335
+ )}
336
+ {samePage ? null : (
337
+ <button
338
+ type="button"
339
+ className={`${classNamePrefix}_skip`}
340
+ onClick={() => {
341
+ handleClearAddons()
342
+ handleConfirm({}, true)
343
+ }}
344
+ >
345
+ Skip
346
+ </button>
347
+ )}
295
348
  </div>
349
+ {(!addons?.length || (isResale && addOnIsIncluded)) && samePage ? null : (
350
+ <div className={`${classNamePrefix}_subtitle`}>
351
+ PLEASE SELECT FROM THE OPTIONAL ADD-ONS BELOW
352
+ </div>
353
+ )}
296
354
  <Formik
297
- initialValues={{}}
355
+ initialValues={initialValues}
298
356
  onSubmit={values => {
299
357
  handleConfirm(values)
300
358
  }}
359
+ validate={
360
+ samePage
361
+ ? values => {
362
+ if (isBrowser) {
363
+ window.localStorage.setItem('add_ons', JSON.stringify(values))
364
+ }
365
+ }
366
+ : undefined
367
+ }
301
368
  >
302
- {({ values }) => {
369
+ {({ values, errors, setFieldTouched }) => {
303
370
  const isConfirmDisabled = !isAtLeastOneAddonSelected(values)
304
371
 
305
372
  return (
306
373
  <Form autoComplete="off" className="form_holder">
307
374
  <>
308
- {addons.map((addon: any) => {
375
+ {(isResale && addOnIsIncluded ? [] : addons).map((addon: any) => {
309
376
  const price = addon.feeIncluded ? addon.price : addon.cost
310
- const isAddonFree = Number(price) === 0
377
+ const isAddonFree = Number(addon?.price) === 0
378
+
379
+ const addOnNormalizedCost = isAddonFree
380
+ ? 'FREE'
381
+ : currencyNormalizerCreator(
382
+ getNormalizedPrice(addon?.cost ?? 0),
383
+ addon.currency
384
+ )
311
385
 
312
386
  const addonNormalizedPrice = isAddonFree
313
387
  ? 'FREE'
314
388
  : currencyNormalizerCreator(
315
- getNormalizedPrice(price),
316
- addon.currency
317
- )
389
+ getNormalizedPrice(
390
+ CONFIGS.FEES_STYLE === FEES_STYLES.DISPLAY_BOTH
391
+ ? addon?.price ?? price
392
+ : price
393
+ ),
394
+ addon.currency
395
+ )
318
396
 
319
397
  return (
320
398
  <div
@@ -331,20 +409,69 @@ export const AddonsContainter = ({
331
409
  <div className={`${classNamePrefix}_product_info_block`}>
332
410
  <div className={`${classNamePrefix}_product_title`}>
333
411
  {addon.name}
412
+ {addon.description && descriptionTrigger !== 'always' && (
413
+ <>
414
+ <span
415
+ aria-hidden
416
+ className="info-icon"
417
+ onClick={
418
+ descriptionTrigger === 'click'
419
+ ? () => handleDescriptionToggle(addon.id)
420
+ : undefined
421
+ }
422
+ onMouseEnter={
423
+ descriptionTrigger === 'hover'
424
+ ? () => setVisibleDescription(addon.id)
425
+ : undefined
426
+ }
427
+ onMouseLeave={
428
+ descriptionTrigger === 'hover'
429
+ ? () => setVisibleDescription(null)
430
+ : undefined
431
+ }
432
+ style={{
433
+ marginLeft: 8,
434
+ cursor: 'pointer',
435
+ display: 'flex',
436
+ }}
437
+ data-tooltip-id={`tooltip-${addon.id}`}
438
+ data-tooltip-content="View Add-On info"
439
+ >
440
+ <InfoIcon size={14} />
441
+ </span>
442
+ <Tooltip id={`tooltip-${addon.id}`} place="top">
443
+ {addon.description || 'No description available'}
444
+ </Tooltip>
445
+ </>
446
+ )}
334
447
  </div>
335
448
  <div className={`${classNamePrefix}_product_price`}>
336
- {addonNormalizedPrice}
337
- {!isAddonFree && (
338
- <span className={`${classNamePrefix}_product_fee`}>
339
- {addon.feeIncluded ? '(incl. Fees)' : '(excl. Fees)'}
340
- </span>
341
- )}
449
+ {CONFIGS.FEES_STYLE === FEES_STYLES.TRADITIONAL
450
+ ? addonNormalizedPrice
451
+ : addOnNormalizedCost}
452
+ {!isAddonFree &&
453
+ CONFIGS.FEES_STYLE === FEES_STYLES.TRADITIONAL && (
454
+ <span className={`${classNamePrefix}_product_fee`}>
455
+ {addon.feeIncluded ? '(incl. Fees)' : '(excl. Fees)'}
456
+ </span>
457
+ )}
458
+ {!isAddonFree &&
459
+ CONFIGS.FEES_STYLE === FEES_STYLES.DISPLAY_BOTH && (
460
+ <>
461
+ <span className={`${classNamePrefix}_product_fee`}>
462
+ {`(${addonNormalizedPrice} with fees)`}
463
+ </span>
464
+ </>
465
+ )}
342
466
  </div>
343
467
  </div>
344
- <div
345
- className={`${classNamePrefix}_product_desc`}
346
- dangerouslySetInnerHTML={createMarkup(addon.description)}
347
- />
468
+ {(visibleDescription === addon.id ||
469
+ descriptionTrigger === 'always') && (
470
+ <div
471
+ className={`${classNamePrefix}_product_desc`}
472
+ dangerouslySetInnerHTML={createMarkup(addon.description)}
473
+ />
474
+ )}
348
475
  <div className={`${classNamePrefix}_product_select_container`}>
349
476
  {addon.variants ? (
350
477
  addon.variants.map((variant: any) => (
@@ -354,9 +481,14 @@ export const AddonsContainter = ({
354
481
  data={variant}
355
482
  selectOptions={addonsOptions[variant.id]}
356
483
  classNamePrefix={classNamePrefix}
357
- handleAddonChange={(id, value) =>
484
+ handleAddonChange={(id, value) => {
485
+ onAddOnSelect(id, value, addon)
358
486
  onFieldChange(id, value, addon)
359
- }
487
+ }}
488
+ addOnDataWithCustomFields={addOnDataWithCustomFields}
489
+ configs={configs}
490
+ values={values}
491
+ errors={errors}
360
492
  />
361
493
  ))
362
494
  ) : (
@@ -366,23 +498,42 @@ export const AddonsContainter = ({
366
498
  data={addon}
367
499
  selectOptions={addonsOptions[addon.id]}
368
500
  classNamePrefix={classNamePrefix}
369
- handleAddonChange={(id, value) =>
501
+ handleAddonChange={(id, value) => {
502
+ onAddOnSelect(id, value, addon)
370
503
  onFieldChange(id, value, addon)
371
- }
504
+
505
+ _map(addOnDataWithCustomFields.fields, fieldGroup => {
506
+ const { id, groupItems } = fieldGroup
507
+ _map(groupItems, field => {
508
+ setFieldTouched(
509
+ `${addon.id}-${id}-${field.name}`,
510
+ true,
511
+ true
512
+ )
513
+ })
514
+ })
515
+ }}
516
+ addOnDataWithCustomFields={addOnDataWithCustomFields}
517
+ configs={configs}
518
+ values={values}
519
+ errors={errors}
372
520
  />
373
521
  )}
374
522
  </div>
375
523
  </div>
376
524
  )
377
525
  })}
378
- <button
379
- type="submit"
380
- className={`${isConfirmDisabled ? `${classNamePrefix}_is_disabled` : ''
526
+ {samePage ? null : (
527
+ <button
528
+ type="submit"
529
+ className={`${
530
+ isConfirmDisabled ? `${classNamePrefix}_is_disabled` : ''
381
531
  } ${classNamePrefix}_submit_button`}
382
- disabled={isConfirmDisabled}
383
- >
384
- CONFIRM SELECTION
385
- </button>
532
+ disabled={isConfirmDisabled}
533
+ >
534
+ CONFIRM SELECTION
535
+ </button>
536
+ )}
386
537
  </>
387
538
  </Form>
388
539
  )
@@ -0,0 +1,3 @@
1
+ export { usePaymentRedirect } from './usePaymentRedirect'
2
+ export { useStripePayment } from './useStripePayment'
3
+ export { usePaymentContext } from './usePaymentContext'
@@ -0,0 +1,22 @@
1
+ import { useCallback } from 'react'
2
+
3
+ export const usePaymentContext = () => {
4
+ const storePaymentContext = useCallback((context: any) => {
5
+ localStorage.setItem('stripe_payment_context', JSON.stringify(context))
6
+ }, [])
7
+
8
+ const getPaymentContext = useCallback(() => {
9
+ const storedContext = localStorage.getItem('stripe_payment_context')
10
+ return storedContext ? JSON.parse(storedContext) : null
11
+ }, [])
12
+
13
+ const clearPaymentContext = useCallback(() => {
14
+ localStorage.removeItem('stripe_payment_context')
15
+ }, [])
16
+
17
+ return {
18
+ storePaymentContext,
19
+ getPaymentContext,
20
+ clearPaymentContext
21
+ }
22
+ }
@@ -0,0 +1,147 @@
1
+ import { Stripe } from '@stripe/stripe-js'
2
+ import { FormikHelpers } from 'formik'
3
+ import _identity from 'lodash/identity'
4
+ import { useEffect } from 'react'
5
+
6
+ import { handlePaymentMiddleWare } from '../../paymentContainer/handlePayment'
7
+
8
+ interface PaymentContext {
9
+ attributes: any
10
+ isFreeTickets: boolean
11
+ updatedOrderData: any
12
+ eventId: string
13
+ values: any
14
+ formikHelpers: FormikHelpers<any>
15
+ checkoutResponse: any
16
+ checkoutUpdateResponse: any
17
+ timestamp: number
18
+ }
19
+
20
+ interface UsePaymentRedirectProps {
21
+ stripeRef: React.MutableRefObject<Stripe | null>
22
+ setError: (error: string | null) => void
23
+ setLoading: (loading: boolean) => void
24
+ removeReferralKey: () => void
25
+ removeAdditionalConfigs: () => void
26
+ handleSubmit: (
27
+ values: any,
28
+ formikHelpers: FormikHelpers<any>,
29
+ eventId: any,
30
+ checkoutResponse: any,
31
+ checkoutUpdateResponse: any,
32
+ paymentResponse: any
33
+ ) => void
34
+ isBrowser: boolean
35
+ }
36
+
37
+ export const usePaymentRedirect = ({
38
+ stripeRef,
39
+ setError,
40
+ setLoading,
41
+ removeReferralKey,
42
+ removeAdditionalConfigs,
43
+ handleSubmit,
44
+ isBrowser,
45
+ }: UsePaymentRedirectProps) => {
46
+ useEffect(() => {
47
+ const handlePaymentReturn = async () => {
48
+ const urlParams = new URLSearchParams(window.location.search)
49
+ const isPaymentReturn = urlParams.get('payment_return')
50
+ const clientSecret = urlParams.get('payment_intent_client_secret')
51
+
52
+ if (isPaymentReturn && clientSecret && stripeRef.current) {
53
+ console.log('Detected payment redirect return, checking status')
54
+ setLoading(true)
55
+
56
+ try {
57
+ // Get stored payment context
58
+ const storedContext = localStorage.getItem('stripe_payment_context')
59
+ if (!storedContext) {
60
+ setError('Payment context not found')
61
+ setLoading(false)
62
+ return
63
+ }
64
+
65
+ const paymentContext: PaymentContext = JSON.parse(storedContext)
66
+ const { paymentIntent } = await stripeRef.current.retrievePaymentIntent(
67
+ clientSecret
68
+ )
69
+
70
+ if (paymentIntent?.status === 'succeeded') {
71
+ console.log('Payment succeeded after redirect, continuing flow')
72
+
73
+ // Clean up stored context
74
+ localStorage.removeItem('stripe_payment_context')
75
+
76
+ // Continue with the success flow using stored context
77
+ let paymentResponse = null
78
+ await handlePaymentMiddleWare(
79
+ null,
80
+ {},
81
+ {
82
+ reviewData: paymentContext.attributes,
83
+ isFreeTickets: paymentContext.isFreeTickets,
84
+ paymentPlanIsAvailable: false,
85
+ showPaymentPlanSection: false,
86
+ handlePayment: (data: any) => {
87
+ paymentResponse = data
88
+ },
89
+ setPaymentIsLoading: _identity,
90
+ setError: _identity,
91
+ orderData: paymentContext.updatedOrderData,
92
+ eventId: paymentContext.eventId,
93
+ isBrowser,
94
+ onPaymentError: (error: any) => {
95
+ throw error
96
+ },
97
+ }
98
+ )
99
+
100
+ // Complete the flow with cleanup and handleSubmit
101
+ removeReferralKey()
102
+ removeAdditionalConfigs()
103
+ handleSubmit(
104
+ paymentContext.values,
105
+ paymentContext.formikHelpers,
106
+ paymentContext.eventId,
107
+ paymentContext.checkoutResponse,
108
+ paymentContext.checkoutUpdateResponse,
109
+ paymentResponse
110
+ )
111
+
112
+ // Clean URL parameters
113
+ window.history.replaceState({}, document.title, window.location.pathname)
114
+ } else if (paymentIntent?.status === 'requires_action') {
115
+ setError('Payment requires additional action')
116
+ // Clean up stored context and URL to prevent infinite loop
117
+ localStorage.removeItem('stripe_payment_context')
118
+ window.history.replaceState({}, document.title, window.location.pathname)
119
+ } else {
120
+ setError(`Payment failed or was cancelled.`)
121
+ // Clean up stored context and URL to prevent infinite loop
122
+ localStorage.removeItem('stripe_payment_context')
123
+ window.history.replaceState({}, document.title, window.location.pathname)
124
+ }
125
+ } catch (error) {
126
+ console.error('Error handling payment return:', error)
127
+ setError('Error processing payment return')
128
+ // Clean up stored context and URL to prevent infinite loop
129
+ localStorage.removeItem('stripe_payment_context')
130
+ window.history.replaceState({}, document.title, window.location.pathname)
131
+ } finally {
132
+ setLoading(false)
133
+ }
134
+ }
135
+ }
136
+
137
+ handlePaymentReturn()
138
+ }, [
139
+ stripeRef.current,
140
+ setError,
141
+ setLoading,
142
+ removeReferralKey,
143
+ removeAdditionalConfigs,
144
+ handleSubmit,
145
+ isBrowser,
146
+ ])
147
+ }
@@ -0,0 +1,121 @@
1
+ import { FormikHelpers } from 'formik'
2
+ import _identity from 'lodash/identity'
3
+
4
+ import { handlePaymentMiddleWare } from '../../paymentContainer/handlePayment'
5
+
6
+ interface PaymentData {
7
+ attributes: any
8
+ isFreeTickets: boolean
9
+ updatedOrderData: any
10
+ eventId: string
11
+ }
12
+
13
+ interface UseStripePaymentProps {
14
+ stripeRef: any
15
+ elementsRef: React.MutableRefObject<any>
16
+ setError: (error: string | null) => void
17
+ isBrowser: boolean
18
+ }
19
+
20
+ export const useStripePayment = ({
21
+ stripeRef,
22
+ elementsRef,
23
+ setError,
24
+ isBrowser,
25
+ }: UseStripePaymentProps) => {
26
+ const processPayment = async (
27
+ paymentDataResponse: any,
28
+ values: any,
29
+ formikHelpers: FormikHelpers<any>,
30
+ checkoutResponse: any,
31
+ checkoutUpdateResponse: any,
32
+ paymentData: PaymentData
33
+ ) => {
34
+ const { attributes, isFreeTickets, updatedOrderData, eventId } = paymentData
35
+
36
+ if (!isFreeTickets) {
37
+ if (!stripeRef.current) {
38
+ setError('Stripe is not ready')
39
+ console.log('Stripe is not ready in billing-info-container')
40
+ return null
41
+ }
42
+
43
+ console.log('Stripe confirmPayment in billing-info-container')
44
+
45
+ // Step 1: Submit elements first
46
+ const { error: submitError } = await elementsRef.current.submit()
47
+ if (submitError) {
48
+ setError('' + submitError?.message)
49
+ console.log(
50
+ 'Stripe elements.submit() error in billing-info-container',
51
+ submitError
52
+ )
53
+ return null
54
+ }
55
+
56
+ // Step 2: Store payment context before potential redirect
57
+ const paymentContext = {
58
+ attributes,
59
+ isFreeTickets,
60
+ updatedOrderData,
61
+ eventId,
62
+ values,
63
+ formikHelpers,
64
+ checkoutResponse,
65
+ checkoutUpdateResponse,
66
+ timestamp: Date.now(),
67
+ }
68
+ localStorage.setItem('stripe_payment_context', JSON.stringify(paymentContext))
69
+
70
+ // Step 3: Confirm payment with current page return URL
71
+ const { error: confirmError } = await stripeRef.current.confirmPayment({
72
+ clientSecret:
73
+ paymentDataResponse.data.attributes.payment_method.stripe_client_secret,
74
+ elements: elementsRef.current,
75
+ confirmParams: {
76
+ return_url:
77
+ window.location.href +
78
+ (window.location.href.includes('?') ? '&' : '?') +
79
+ 'payment_return=true',
80
+ },
81
+ redirect: 'if_required',
82
+ })
83
+
84
+ if (confirmError) {
85
+ setError('' + confirmError?.message)
86
+ console.log('Stripe confirmPayment error in billing-info-container')
87
+ return null
88
+ }
89
+ }
90
+
91
+ console.log('Stripe confirmPayment success in billing-info-container')
92
+
93
+ // Handle payment middleware for non-redirect payments
94
+ let paymentResponse = null
95
+ await handlePaymentMiddleWare(
96
+ null,
97
+ {},
98
+ {
99
+ reviewData: attributes,
100
+ isFreeTickets,
101
+ paymentPlanIsAvailable: false,
102
+ showPaymentPlanSection: false,
103
+ handlePayment: (data: any) => {
104
+ paymentResponse = data
105
+ },
106
+ setPaymentIsLoading: _identity,
107
+ setError: _identity,
108
+ orderData: updatedOrderData,
109
+ eventId,
110
+ isBrowser,
111
+ onPaymentError: (error: any) => {
112
+ throw error
113
+ },
114
+ }
115
+ )
116
+
117
+ return paymentResponse
118
+ }
119
+
120
+ return { processPayment }
121
+ }