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,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import './style.css'
2
3
 
3
4
  import { CircularProgress, ThemeOptions } from '@mui/material'
@@ -5,6 +6,7 @@ import Backdrop from '@mui/material/Backdrop'
5
6
  import Button from '@mui/material/Button'
6
7
  import { createTheme, ThemeProvider } from '@mui/material/styles'
7
8
  import { CSSProperties } from '@mui/styles'
9
+ import { Stripe, StripeElementsOptions } from '@stripe/stripe-js'
8
10
  import axios, { AxiosError } from 'axios'
9
11
  import { Field, Form, Formik, FormikHelpers, FormikProps, FormikValues } from 'formik'
10
12
  import _find from 'lodash/find'
@@ -16,22 +18,25 @@ import _isEmpty from 'lodash/isEmpty'
16
18
  import _isEqual from 'lodash/isEqual'
17
19
  import _map from 'lodash/map'
18
20
  import { nanoid } from 'nanoid'
19
- import React, { FC, useEffect, useRef, useState } from 'react'
21
+ import React, { FC, useCallback, useEffect, useRef, useState } from 'react'
20
22
 
21
23
  import {
22
24
  AttributesConfig,
23
25
  getCart,
24
26
  getCheckoutPageConfigs,
25
27
  getCountries,
28
+ getPaymentData,
26
29
  getProfileData,
27
30
  getStates,
28
31
  postOnCheckout,
29
32
  register,
30
33
  setCustomHeader,
31
34
  } from '../../api'
35
+ import { updateCheckout } from '../../api/checkout'
32
36
  import { withCustomFields } from '../../hoc'
33
37
  import { usePixel } from '../../hooks/usePixel'
34
38
  import { IBillingInfoData } from '../../types'
39
+ import { ICheckoutResponseData } from '../../types/api/checkout'
35
40
  import {
36
41
  createCheckoutDataBodyWithDefaultHolder,
37
42
  deleteCookieByName,
@@ -40,21 +45,27 @@ import {
40
45
  setLoggedUserData,
41
46
  } from '../../utils'
42
47
  import { ErrorFocus } from '../../utils/formikErrorFocus'
48
+ import { AddonsContainter, IAddonContainterProps } from '../addonsContainer'
49
+ import SimpleAddonsContainer from '../addonsContainer/SimpleAddonsContainer'
43
50
  import SnackbarAlert from '../common/SnackbarAlert'
44
51
  import { ForgotPasswordModal } from '../forgotPasswordModal'
45
52
  import { VerificationPendingModal } from '../idVerificationContainer/VerificationPendingModal'
46
53
  import { LoginModal } from '../loginModal'
54
+ import { IPaymentPage, PaymentContainer } from '../paymentContainer'
47
55
  import { SignupModal } from '../signupModal'
48
56
  import TimerWidget from '../timerWidget'
57
+ import { usePaymentRedirect, useStripePayment } from './hooks'
49
58
  import {
50
59
  assingUniqueIds,
51
60
  createCheckoutDataBody,
52
61
  createRegisterFormData,
62
+ filterBillingInfoFields,
53
63
  getFieldComponent,
54
64
  getFieldLabel,
55
65
  getInitialValues,
56
66
  getValidateFunctions,
57
67
  ICheckoutBody,
68
+ renderComponentWithProps,
58
69
  } from './utils'
59
70
 
60
71
  export interface IBillingInfoPage {
@@ -64,7 +75,9 @@ export interface IBillingInfoPage {
64
75
  values: FormikValues,
65
76
  formikHelpers: FormikHelpers<FormikValues>,
66
77
  eventId: any,
67
- res: any
78
+ res: any,
79
+ checkoutUpdateResponse?: any,
80
+ paymentResponse?: any
68
81
  ) => void;
69
82
  onRegisterSuccess?: (value: any) => void;
70
83
  onRegisterError?: (e: AxiosError, email: string) => void;
@@ -82,8 +95,11 @@ export interface IBillingInfoPage {
82
95
  onLogin?: () => void;
83
96
  onLoginSuccess?: () => void;
84
97
  onErrorClose?: () => void;
98
+ onCheckoutUpdateSuccess?: (res: any) => void;
99
+ onCheckoutUpdateError?: (e: AxiosError) => void;
85
100
  initialValues?: FormikValues;
86
101
  buttonName?: string;
102
+ freeOrderButtonName?: string;
87
103
  theme?: 'light' | 'dark';
88
104
  isLoggedIn?: boolean;
89
105
  accountInfoTitle?: string | JSX.Element;
@@ -110,11 +126,18 @@ export interface IBillingInfoPage {
110
126
  customFieldsOrderKeys?: string[];
111
127
  customFieldsTicketHolderKeys?: string[];
112
128
  onPendingVerification?: () => void;
129
+ includeAddons?: boolean;
130
+ addonsProps?: IAddonContainterProps;
131
+ addOnDataWithCustomFields?: any;
132
+ isSinglePageCheckout?: boolean;
133
+ paymentProps?: Partial<IPaymentPage>;
134
+ paymentSectionAddon?: React.ReactNode;
113
135
  }
114
136
 
115
137
  const LogicRunner: FC<{
116
138
  brandOptIn?: boolean;
117
139
  values: any;
140
+ errors: any;
118
141
  setStates: React.Dispatch<any>;
119
142
  setFieldValue: any;
120
143
  setValues: any;
@@ -133,72 +156,72 @@ const LogicRunner: FC<{
133
156
  shouldFetchCountries,
134
157
  brandOptIn,
135
158
  }) => {
136
- const prevCountry = useRef(values.country)
137
- useEffect(() => {
138
- const fetchStates = async () => {
139
- try {
140
- const res = await getStates(values.country)
141
- const mappedStates = _map(res.data, (item, key) => ({
142
- label: item,
143
- value: key,
144
- }))
145
- setStates(mappedStates)
146
- if (prevCountry.current !== values.country) {
147
- const stateExists = mappedStates.find(
148
- state => state.value === values.state
149
- )?.value
150
- setFieldValue('state', stateExists ?? mappedStates[0]?.value ?? '')
151
- prevCountry.current = values.country
152
- }
153
- onGetStatesSuccess(res.data)
154
- } catch (e) {
155
- if (axios.isAxiosError(e)) {
156
- onGetStatesError(e)
157
- }
159
+ const prevCountry = useRef(values.country)
160
+ useEffect(() => {
161
+ const fetchStates = async () => {
162
+ try {
163
+ const res = await getStates(values.country)
164
+ const mappedStates = _map(res.data, (item, key) => ({
165
+ label: item,
166
+ value: key,
167
+ }))
168
+ setStates(mappedStates)
169
+ if (prevCountry.current !== values.country) {
170
+ const stateExists = mappedStates.find(
171
+ state => state.value === values.state
172
+ )?.value
173
+ setFieldValue('state', stateExists ?? mappedStates[0]?.value ?? '')
174
+ prevCountry.current = values.country
175
+ }
176
+ onGetStatesSuccess(res.data)
177
+ } catch (e) {
178
+ if (axios.isAxiosError(e)) {
179
+ onGetStatesError(e)
158
180
  }
159
181
  }
160
- shouldFetchCountries && fetchStates()
161
- }, [values.country, setStates, setFieldValue])
162
- const userDataEncoded = isBrowser ? window.localStorage.getItem('user_data') : ''
163
- useEffect(() => {
164
- // set user data from local storage
165
- const getStoredUserData = () => {
166
- if (isBrowser) {
167
- if (userDataEncoded) {
168
- try {
169
- const parsedData = JSON.parse(userDataEncoded)
170
- const mappedValues = {
171
- firstName: parsedData?.first_name || parsedData?.firstName || '',
172
- lastName: parsedData?.last_name || parsedData?.lastName || '',
173
- email: parsedData?.email || '',
174
- phone: parsedData?.phone || '',
175
- confirmEmail: parsedData?.email || '',
176
- state: parsedData?.state || '',
177
- street_address: parsedData?.street_address || '',
178
- country: parsedData?.country || '1',
179
- zip: parsedData?.zip || '',
180
- brand_opt_in: brandOptIn ? brandOptIn : parsedData?.brand_opt_in || false,
181
- city: parsedData?.city || '',
182
- confirmPassword: '',
183
- password: '',
184
- 'holderFirstName-0': parsedData?.first_name || parsedData?.firstName || '',
185
- 'holderLastName-0': parsedData?.last_name || parsedData?.lastName || '',
186
- 'holderEmail-0': parsedData?.email || '',
187
- }
182
+ }
183
+ shouldFetchCountries && fetchStates()
184
+ }, [values.country, setStates, setFieldValue])
185
+ const userDataEncoded = isBrowser ? window.localStorage.getItem('user_data') : ''
186
+ useEffect(() => {
187
+ // set user data from local storage
188
+ const getStoredUserData = () => {
189
+ if (isBrowser) {
190
+ if (userDataEncoded) {
191
+ try {
192
+ const parsedData = JSON.parse(userDataEncoded)
193
+ const mappedValues = {
194
+ firstName: parsedData?.first_name || parsedData?.firstName || '',
195
+ lastName: parsedData?.last_name || parsedData?.lastName || '',
196
+ email: parsedData?.email || '',
197
+ phone: parsedData?.phone || '',
198
+ confirmEmail: parsedData?.email || '',
199
+ state: parsedData?.state || '',
200
+ street_address: parsedData?.street_address || '',
201
+ country: parsedData?.country || '1',
202
+ zip: parsedData?.zip || '',
203
+ brand_opt_in: brandOptIn ? brandOptIn : parsedData?.brand_opt_in || false,
204
+ city: parsedData?.city || '',
205
+ confirmPassword: '',
206
+ password: '',
207
+ 'holderFirstName-0': parsedData?.first_name || parsedData?.firstName || '',
208
+ 'holderLastName-0': parsedData?.last_name || parsedData?.lastName || '',
209
+ 'holderEmail-0': parsedData?.email || '',
210
+ }
188
211
 
189
- const extraDataJSON = window.localStorage.getItem('extraData')
190
- const extraData = extraDataJSON ? JSON.parse(extraDataJSON) : null
212
+ const extraDataJSON = window.localStorage.getItem('extraData')
213
+ const extraData = extraDataJSON ? JSON.parse(extraDataJSON) : null
191
214
 
192
- setValues({ ...values, ...mappedValues, ...(extraData ?? {}) })
193
- setUserValues(mappedValues)
194
- } catch (e) {}
195
- }
215
+ setValues({ ...values, ...mappedValues, ...(extraData ?? {}) })
216
+ setUserValues(mappedValues)
217
+ } catch (e) {}
196
218
  }
197
219
  }
198
- getStoredUserData()
199
- }, [userDataEncoded, setValues, setUserValues])
200
- return null
201
- }
220
+ }
221
+ getStoredUserData()
222
+ }, [userDataEncoded, setValues, setUserValues, brandOptIn])
223
+ return null
224
+ }
202
225
 
203
226
  const BillingInfoContainer = React.memo(
204
227
  ({
@@ -209,6 +232,7 @@ const BillingInfoContainer = React.memo(
209
232
  },
210
233
  initialValues = {},
211
234
  buttonName = 'Submit',
235
+ freeOrderButtonName = 'Complete Registration',
212
236
  handleSubmit = _identity,
213
237
  theme = 'light',
214
238
  onRegisterSuccess = _identity,
@@ -224,6 +248,8 @@ const BillingInfoContainer = React.memo(
224
248
  onGetProfileDataError = _identity,
225
249
  onLogin,
226
250
  onLoginSuccess = _identity,
251
+ onCheckoutUpdateSuccess = _identity,
252
+ onCheckoutUpdateError = _identity,
227
253
  isLoggedIn: pIsLoggedIn = false,
228
254
  accountInfoTitle = '',
229
255
  hideLogo,
@@ -249,12 +275,20 @@ const BillingInfoContainer = React.memo(
249
275
  onPendingVerification = _identity,
250
276
  onGetCheckoutConfigsSuccess = _identity,
251
277
  onGetCheckoutConfigsError = _identity,
278
+ includeAddons = false,
279
+ addonsProps,
280
+ addOnDataWithCustomFields,
281
+ isSinglePageCheckout = false,
282
+ paymentProps = {},
283
+ paymentSectionAddon,
252
284
  }: IBillingInfoPage) => {
253
285
  const [extraData, setExtraData] = useState(null)
254
286
  const [isConfigLoading, setIsConfigLoading] = useState(true)
255
287
  const [configs, setConfigs] = useState<null | AttributesConfig>(null)
256
288
  const [isNewUser, setIsNewUser] = useState(false)
257
289
  const themeMui = createTheme(themeOptions)
290
+ const elementsRef = useRef<any>(null)
291
+ const stripeRef = useRef<Stripe | null>(null)
258
292
 
259
293
  useEffect(() => {
260
294
  if (isBrowser) {
@@ -280,8 +314,15 @@ const BillingInfoContainer = React.memo(
280
314
  const defaultCountry = isBrowser ? window.localStorage.getItem('eventCountry') : ''
281
315
  const userData =
282
316
  isBrowser && window.localStorage.getItem('user_data')
283
- ? JSON.parse(window.localStorage.getItem('user_data') || '')
317
+ ? JSON.parse(window.localStorage.getItem('user_data') || '{}')
284
318
  : {}
319
+ const additionalConfigs =
320
+ isBrowser && window.localStorage.getItem('checkoutAdditionalConfigs')
321
+ ? JSON.parse(
322
+ window.localStorage.getItem('checkoutAdditionalConfigs') ||
323
+ '{"resale": false, "resaleWithAddons": false}'
324
+ )
325
+ : { resale: false, resaleWithAddons: false }
285
326
  const [dataWithUniqueIds, setDataWithUniqueIds] = useState<IBillingInfoData[]>(
286
327
  assingUniqueIds(data)
287
328
  )
@@ -314,7 +355,7 @@ const BillingInfoContainer = React.memo(
314
355
  const [loading, setLoading] = useState(true)
315
356
  const [cardLoading, setCardLoading] = useState(false)
316
357
  const [isCountriesLoading, setIsCountriesLoading] = useState(true)
317
- const [error, setError] = useState(null)
358
+ const [error, setError] = useState<string | null>(null)
318
359
  const [phoneValidationIsLoading, setPhoneValidationIsLoading] = useState(false)
319
360
  const emailLogged = _get(userData, 'email', '') || _get(userValues, 'email', '')
320
361
  const firstNameLogged =
@@ -349,18 +390,27 @@ const BillingInfoContainer = React.memo(
349
390
  const hideInstagramField = !collectMandatoryInstagram && !collectOptionalInstagram
350
391
  const collectMandatoryBusinessCategory = configs?.collect_mandatory_business_category
351
392
  const collectOptionalBusinessCategory = configs?.collect_optional_business_category
393
+ const eventHasAddons = configs?.has_add_on
352
394
  const hideBusinessCategoryField =
353
395
  !collectMandatoryBusinessCategory && !collectOptionalBusinessCategory
354
396
 
355
- const [pendingVerificationMessage, setPendingVerificationMessage] = useState()
356
-
357
- // Get prevProps
397
+ const [pendingVerificationMessage, setPendingVerificationMessage] = useState<string>()
398
+ const [reviewData, setReviewData] = useState<any>({})
399
+ const [checkoutData, setCheckoutData] = useState<any>({})
400
+ const [checkoutUpdateData, setCheckoutUpdateData] =
401
+ useState<ICheckoutResponseData | null>(null)
358
402
  const prevData = useRef(data)
359
403
 
360
- const addAddOnsInAttributes = (checkoutBody: any) => {
404
+ const addAddOnsInAttributes = useCallback((checkoutBody: any) => {
361
405
  const selectedAddOns = window.localStorage.getItem('add_ons') || '{}'
406
+ const addOnDataCapture = window.localStorage.getItem('add_on_data_capture') || '{}'
407
+
362
408
  checkoutBody.attributes.add_ons = JSON.parse(selectedAddOns)
363
- }
409
+ checkoutBody.attributes.add_on_data_capture = JSON.parse(addOnDataCapture)
410
+ }, [])
411
+ const [singleCheckoutAddons, setSingleCheckoutAddOns] = useState<any>({})
412
+
413
+ const orderIsFree = !Number(checkoutData?.total)
364
414
 
365
415
  useEffect(() => {
366
416
  const hasUniqueId = _get(dataWithUniqueIds, '[0].uniqueId')
@@ -373,13 +423,13 @@ const BillingInfoContainer = React.memo(
373
423
  }
374
424
  }, [dataWithUniqueIds, data])
375
425
 
376
- const getQuantity = (cart: any = []) => {
426
+ const getQuantity = useCallback((cart: any = []) => {
377
427
  let qty = 0
378
428
  cart.forEach((item: any) => {
379
429
  qty += +item.quantity
380
430
  })
381
431
  return qty
382
- }
432
+ }, [])
383
433
 
384
434
  useEffect(() => {
385
435
  if (pIsLoggedIn !== isLoggedIn || xtfCookie) {
@@ -407,10 +457,22 @@ const BillingInfoContainer = React.memo(
407
457
  shouldFetchCountries && fetchCountries()
408
458
  fetchCart()
409
459
 
460
+ // Initialize checkout with event_id on first load
461
+ if (isSinglePageCheckout && eventId) {
462
+ updateCheckoutWithAddOns()
463
+ }
464
+
410
465
  return () => {
411
466
  isBrowser && localStorage.removeItem('selectedTicketsQuantity')
412
467
  }
413
- }, [])
468
+ // eslint-disable-next-line react-hooks/exhaustive-deps
469
+ }, [
470
+ eventId,
471
+ isSinglePageCheckout,
472
+ shouldFetchCountries,
473
+ onGetCountriesSuccess,
474
+ onGetCountriesError,
475
+ ])
414
476
 
415
477
  // fetch cart data
416
478
  const fetchCart = async () => {
@@ -459,6 +521,45 @@ const BillingInfoContainer = React.memo(
459
521
  fetchCart()
460
522
  }, [isLoggedIn])
461
523
 
524
+ useEffect(() => {
525
+ const fetchCheckoutUpdate = async () => {
526
+ if (!eventId) return
527
+
528
+ try {
529
+ const checkoutUpdateResponse = await updateCheckout({
530
+ attributes: {
531
+ event_id: eventId,
532
+ is_from_resale: additionalConfigs?.resale,
533
+ },
534
+ })
535
+ console.log('Stripe in [useEffect] fetchCheckoutUpdate', checkoutUpdateResponse)
536
+
537
+ if (checkoutUpdateResponse.success) {
538
+ const checkoutAttributes = checkoutUpdateResponse.data.attributes
539
+ const cartPriceBreakdown = _get(
540
+ checkoutAttributes,
541
+ 'cart_price_breakdown',
542
+ {}
543
+ )
544
+ localStorage.setItem(
545
+ 'checkoutData',
546
+ JSON.stringify({ hash: '', total: _get(cartPriceBreakdown, 'total', 0) })
547
+ )
548
+ console.log(
549
+ 'Stripe in [useEffect] fetchCheckoutUpdate | checkoutAttributes',
550
+ checkoutAttributes
551
+ )
552
+ setCheckoutUpdateData(checkoutAttributes)
553
+ onCheckoutUpdateSuccess({ expires_at: expirationTime, ...cartPriceBreakdown })
554
+ }
555
+ } catch (error) {
556
+ console.error('Failed to fetch checkout update:', error)
557
+ }
558
+ }
559
+
560
+ fetchCheckoutUpdate()
561
+ }, [eventId, additionalConfigs?.resale])
562
+
462
563
  useEffect(() => {
463
564
  const collectPaymentData = async () => {
464
565
  if (
@@ -481,12 +582,13 @@ const BillingInfoContainer = React.memo(
481
582
 
482
583
  const checkoutResponse = await postOnCheckout(checkoutBody, flagFreeTicket)
483
584
  removeReferralKey()
585
+ removeAdditionalConfigs()
484
586
  onSkipBillingPage(checkoutResponse.data.attributes)
485
587
  setLoading(false)
486
588
  } catch (e) {
487
- onSubmitError(e)
488
- if (e.response?.data?.data?.hasUnverifiedOrder) {
489
- setPendingVerificationMessage(e.response?.data?.message)
589
+ onSubmitError(e as AxiosError)
590
+ if (_get(e, 'response.data.data.hasUnverifiedOrder')) {
591
+ setPendingVerificationMessage(_get(e, 'response.data.message'))
490
592
  }
491
593
  }
492
594
  } else {
@@ -496,91 +598,266 @@ const BillingInfoContainer = React.memo(
496
598
  collectPaymentData()
497
599
  }, [skipPage, ticketsQuantity])
498
600
 
499
- const collectCheckoutBody = (
500
- values: Record<string, any>,
501
- profileData?: any
502
- ): Record<string, any> => {
503
- let checkoutBody = {} as ICheckoutBody
504
-
505
- // Auto collect ticket holders name when it was skipped optionally
506
- if (showDOB && !showTicketHolders && canSkipHolderNames) {
507
- checkoutBody = createCheckoutDataBodyWithDefaultHolder(
508
- ticketsQuantity.length,
509
- values,
510
- true,
511
- { emailLogged, firstNameLogged, lastNameLogged }
512
- )
513
- } else {
514
- checkoutBody = createCheckoutDataBody(
515
- ticketsQuantity.length,
516
- values,
517
- {
518
- emailLogged: emailLogged || profileData.email,
519
- firstNameLogged:
520
- firstNameLogged || profileData.first_name || profileData.firstName,
521
- lastNameLogged:
522
- lastNameLogged || profileData.last_name || profileData.lastName,
523
- },
524
- showDOB
525
- )
526
- }
601
+ const collectCheckoutBody = useCallback(
602
+ (values: Record<string, any>, profileData?: any): Record<string, any> => {
603
+ let checkoutBody = {} as ICheckoutBody
527
604
 
528
- // Collect data_capture for Order Custom Fields
529
- const data_capture: Record<string, any> = {}
530
- _forEach(customFieldsOrderKeys, (key: string) => {
531
- data_capture[key] = values[key]
605
+ // Auto collect ticket holders name when it was skipped optionally
606
+ if (showDOB && !showTicketHolders && canSkipHolderNames) {
607
+ checkoutBody = createCheckoutDataBodyWithDefaultHolder(
608
+ ticketsQuantity.length,
609
+ values,
610
+ true,
611
+ { emailLogged, firstNameLogged, lastNameLogged }
612
+ )
613
+ } else {
614
+ checkoutBody = createCheckoutDataBody(
615
+ ticketsQuantity.length,
616
+ values,
617
+ {
618
+ emailLogged: emailLogged || profileData.email,
619
+ firstNameLogged:
620
+ firstNameLogged || profileData.first_name || profileData.firstName,
621
+ lastNameLogged:
622
+ lastNameLogged || profileData.last_name || profileData.lastName,
623
+ },
624
+ showDOB
625
+ )
626
+ }
532
627
 
533
- // Delete from values list
534
- delete checkoutBody.attributes[key]
535
- })
628
+ // Collect data_capture for Order Custom Fields
629
+ const data_capture: Record<string, any> = {}
630
+ _forEach(customFieldsOrderKeys, (key: string) => {
631
+ data_capture[key] = values[key]
536
632
 
537
- // Temp solution for hardcoded data capture values, to be deleted in the future
538
- const captureKeys = [
539
- 'businessCategory',
540
- 'company',
541
- 'instagram',
542
- 'jobTitle',
543
- 'wallet_address',
544
- ]
633
+ // Delete from values list
634
+ delete checkoutBody.attributes[key]
635
+ })
545
636
 
546
- captureKeys.forEach(key => {
547
- const path = `data_capture.${key}`
548
- const value = _get(values, path, '')
637
+ // Temp solution for hardcoded data capture values, to be deleted in the future
638
+ const captureKeys = [
639
+ 'businessCategory',
640
+ 'company',
641
+ 'instagram',
642
+ 'jobTitle',
643
+ 'wallet_address',
644
+ ]
549
645
 
550
- if (value !== '') {
551
- data_capture[key] = value
552
- }
646
+ captureKeys.forEach(key => {
647
+ const path = `data_capture.${key}`
648
+ const value = _get(values, path, '')
553
649
 
554
- const attributeKey = `data_capture[${key}]`
555
- delete checkoutBody.attributes?.[attributeKey]
556
- })
650
+ if (value !== '') {
651
+ data_capture[key] = value
652
+ }
653
+
654
+ const attributeKey = `data_capture[${key}]`
655
+ delete checkoutBody.attributes?.[attributeKey]
656
+ })
657
+
658
+ // Collect data_capture for Ticket Holders Fields
659
+ _forEach(checkoutBody.attributes.ticket_holders, (holder, holderIndex) => {
660
+ const holderDataCapture: Record<string, any> = {}
661
+
662
+ _forEach(customFieldsTicketHolderKeys, customFieldKey => {
663
+ const customFieldHolderName = `${customFieldKey}-${holderIndex}`
664
+ const customFieldHolderKey = values[customFieldHolderName] || ''
665
+ holderDataCapture[customFieldKey] = customFieldHolderKey
557
666
 
558
- // Collect data_capture for Ticket Holders Fields
559
- _forEach(checkoutBody.attributes.ticket_holders, (holder, holderIndex) => {
560
- const holderDataCapture: Record<string, any> = {}
667
+ // Delete holder specific value from values list
668
+ delete checkoutBody.attributes[customFieldHolderName]
669
+ })
670
+
671
+ // Assign Ticket Holder data_capture final value
672
+ if (!_isEmpty(holderDataCapture)) {
673
+ holder.ticket_data_capture = holderDataCapture
674
+ }
675
+ })
676
+
677
+ // Collect add_on_data_capture for Add-on Custom Fields
678
+ const add_on_data_capture: Record<string, any> = {}
679
+ _forEach(values, (val: any, key: string) => {
680
+ // Matches keys like: `${addonId}-${groupId}-${fieldKey}` created in AddonComponent
681
+ const match = key.match(/^(\d+)-\d+-(.+)$/)
682
+ if (match) {
683
+ const addonId = match[1]
684
+ const fieldKey = match[2]
685
+
686
+ // Initialize addon bucket
687
+ if (!add_on_data_capture[addonId]) {
688
+ add_on_data_capture[addonId] = {}
689
+ }
561
690
 
562
- _forEach(customFieldsTicketHolderKeys, customFieldKey => {
563
- const customFieldHolderName = `${customFieldKey}-${holderIndex}`
564
- const customFieldHolderKey = values[customFieldHolderName] || ''
565
- holderDataCapture[customFieldKey] = customFieldHolderKey
691
+ add_on_data_capture[addonId][fieldKey] = val
566
692
 
567
- // Delete holder specific value from values list
568
- delete checkoutBody.attributes[customFieldHolderName]
693
+ // Remove the raw form key from attributes to avoid leaking internals
694
+ delete checkoutBody.attributes[key]
695
+ }
569
696
  })
570
697
 
571
- // Assign Ticket Holder data_capture final value
572
- if (!_isEmpty(holderDataCapture)) {
573
- holder.ticket_data_capture = holderDataCapture
698
+ // Also include add_on_data_capture from localStorage for single-page checkout
699
+ if (isBrowser) {
700
+ const storedAddOnDataCapture = localStorage.getItem('add_on_data_capture')
701
+ if (storedAddOnDataCapture) {
702
+ const parsed = JSON.parse(storedAddOnDataCapture)
703
+ Object.keys(parsed).forEach(addonId => {
704
+ if (!add_on_data_capture[addonId]) {
705
+ add_on_data_capture[addonId] = {}
706
+ }
707
+ Object.keys(parsed[addonId]).forEach(fieldKey => {
708
+ add_on_data_capture[addonId][fieldKey] = parsed[addonId][fieldKey]
709
+ })
710
+ })
711
+ }
574
712
  }
575
- })
576
713
 
577
- return { attributes: { ...checkoutBody.attributes, data_capture } }
578
- }
714
+ const mergedAttributes: Record<string, any> = {
715
+ ...checkoutBody.attributes,
716
+ data_capture,
717
+ }
718
+
719
+ if (!_isEmpty(add_on_data_capture)) {
720
+ mergedAttributes.add_on_data_capture = add_on_data_capture
721
+ }
722
+
723
+ return { attributes: mergedAttributes }
724
+ },
725
+ [
726
+ showDOB,
727
+ showTicketHolders,
728
+ canSkipHolderNames,
729
+ customFieldsOrderKeys,
730
+ ticketsQuantity.length,
731
+ emailLogged,
732
+ firstNameLogged,
733
+ lastNameLogged,
734
+ customFieldsTicketHolderKeys,
735
+ ]
736
+ )
579
737
 
580
- const removeReferralKey = () => {
738
+ const removeReferralKey = useCallback(() => {
581
739
  localStorage.removeItem('referral_key')
582
- }
740
+ }, [])
741
+
742
+ const removeAdditionalConfigs = useCallback(() => {
743
+ localStorage.removeItem('checkoutAdditionalConfigs')
744
+ }, [])
745
+
746
+ useEffect(() => {
747
+ onCheckoutUpdateSuccess({ expires_at: expirationTime, ...checkoutData })
748
+ }, [checkoutData, cartInfoData])
749
+
750
+ // Initialize payment hooks
751
+ usePaymentRedirect({
752
+ stripeRef,
753
+ setError,
754
+ setLoading,
755
+ removeReferralKey,
756
+ removeAdditionalConfigs,
757
+ handleSubmit,
758
+ isBrowser,
759
+ })
760
+
761
+ const { processPayment } = useStripePayment({
762
+ stripeRef,
763
+ elementsRef,
764
+ setError,
765
+ isBrowser,
766
+ })
767
+
768
+ const updateCheckoutWithAddOns = useCallback(
769
+ async (addOns: { [key: string]: number } = {}) => {
770
+ if (!isSinglePageCheckout) return
771
+
772
+ const mergedAddOns = { ...singleCheckoutAddons }
773
+
774
+ // Update existing entries and add new ones
775
+ Object.entries(addOns).forEach(([key, value]) => {
776
+ const amount = Number(value)
777
+ if (amount) {
778
+ mergedAddOns[key] = amount
779
+ } else {
780
+ delete mergedAddOns[key]
781
+ }
782
+ })
783
+
784
+ try {
785
+ const checkoutUpdateData = {
786
+ attributes: {
787
+ event_id: eventId,
788
+ add_ons: mergedAddOns,
789
+ is_from_resale: additionalConfigs?.resale,
790
+ },
791
+ }
792
+
793
+ const checkoutResponse = await updateCheckout(checkoutUpdateData)
794
+
795
+ if (checkoutResponse.success) {
796
+ const checkoutDataObj = _get(
797
+ checkoutResponse,
798
+ 'data.attributes.cart_price_breakdown',
799
+ {}
800
+ )
801
+ setCheckoutData(checkoutDataObj)
802
+ setSingleCheckoutAddOns(mergedAddOns)
803
+ }
804
+ } catch (error) {
805
+ const errorMessage = _get(
806
+ error,
807
+ 'response.data.message',
808
+ 'Failed to update add-ons'
809
+ )
810
+ setError(errorMessage)
811
+ onCheckoutUpdateError(error as AxiosError)
812
+ }
813
+ },
814
+ [eventId, isSinglePageCheckout, onCheckoutUpdateError, onCheckoutUpdateSuccess]
815
+ )
816
+ console.log({ checkoutData })
817
+
818
+ const handleAddOnSelect = useCallback(
819
+ async (selectedAddOns: { [key: string]: number }) => {
820
+ await updateCheckoutWithAddOns(selectedAddOns)
821
+ },
822
+ [updateCheckoutWithAddOns]
823
+ )
824
+
825
+ const onAddOnSelect = useCallback(
826
+ (id: string, value: string, addon: any, fieldUpdates: Record<string, any> = {}) => {
827
+ const quantity = parseInt(value) || 0
828
+ const addonId = addon.id || id
829
+
830
+ // Get current add-ons and their custom fields
831
+ const currentAddOns = JSON.parse(localStorage.getItem('add_ons') || '{}')
832
+ const currentDataCapture = JSON.parse(
833
+ localStorage.getItem('add_on_data_capture') || '{}'
834
+ )
835
+
836
+ // Update quantities
837
+ const updatedAddOns = { ...currentAddOns }
838
+ updatedAddOns[addonId] = quantity
583
839
 
840
+ // Update custom fields if any
841
+ const updatedDataCapture = { ...currentDataCapture }
842
+ if (Object.keys(fieldUpdates).length > 0) {
843
+ updatedDataCapture[addonId] = {
844
+ ...(updatedDataCapture[addonId] || {}),
845
+ ...fieldUpdates,
846
+ }
847
+ }
848
+
849
+ if (quantity === 0 && updatedDataCapture[addonId]) {
850
+ delete updatedDataCapture[addonId]
851
+ }
852
+ // Save to localStorage
853
+ localStorage.setItem('add_ons', JSON.stringify(updatedAddOns))
854
+ localStorage.setItem('add_on_data_capture', JSON.stringify(updatedDataCapture))
855
+
856
+ // Call handleAddOnSelect with the updated addons object
857
+ handleAddOnSelect(updatedAddOns)
858
+ },
859
+ [handleAddOnSelect]
860
+ )
584
861
  if (loading || (enableTimer && !expirationTime && isBrowser)) {
585
862
  if (expirationTime === 0) {
586
863
  // Redirect to homepage (countdown finished and browser reloaded case)
@@ -597,20 +874,38 @@ const BillingInfoContainer = React.memo(
597
874
  if (isTable) {
598
875
  dataWithUniqueIds[0].label = 'Get Your Tables'
599
876
  }
877
+
878
+ const stripePublishableKey =
879
+ reviewData?.payment_method?.stripe_publishable_key ||
880
+ checkoutUpdateData?.additional_payment_information?.basic_config?.apiKey
881
+ const stripeAccountId =
882
+ reviewData?.payment_method?.stripe_connected_account ||
883
+ checkoutUpdateData?.additional_payment_information?.basic_config?.accountId
884
+ const addOnsIncludedOnInvitation =
885
+ additionalConfigs.resale && additionalConfigs.resaleWithAddons
886
+
887
+ const elementsOptions: StripeElementsOptions = {
888
+ ...checkoutUpdateData?.additional_payment_information?.elements_config,
889
+ }
890
+
891
+ if (loading || cardLoading || isCountriesLoading || isConfigLoading || !eventId) {
892
+ return (
893
+ <Backdrop
894
+ sx={{ color: '#fff', backgroundColor: '#000000bd', zIndex: 1205 }}
895
+ open={true}
896
+ >
897
+ <CircularProgress color="inherit" />
898
+ </Backdrop>
899
+ )
900
+ }
901
+
600
902
  return (
601
903
  <ThemeProvider theme={themeMui}>
602
- {(loading || cardLoading || isCountriesLoading || isConfigLoading) && (
603
- <Backdrop
604
- sx={{ color: '#fff', backgroundColor: '#000000bd', zIndex: 1205 }}
605
- open={true}
606
- >
607
- <CircularProgress color="inherit" />
608
- </Backdrop>
609
- )}
610
904
  {!!expirationTime && enableTimer && (
611
905
  <TimerWidget
612
906
  expires_at={expirationTime}
613
907
  onCountdownFinish={onCountdownFinish}
908
+ container={isSinglePageCheckout ? 'body' : ''}
614
909
  />
615
910
  )}
616
911
  {!isCountriesLoading && !isConfigLoading && (
@@ -619,8 +914,9 @@ const BillingInfoContainer = React.memo(
619
914
  dataWithUniqueIds,
620
915
  {
621
916
  country: initialCountry,
622
- state: _get(userData, 'stateId', '') || '1',
623
- brand_opt_in: optedInFieldValue,
917
+ state:
918
+ _get(userData, 'stateId', '') || _get(userData, 'state', '') || '1',
919
+ brand_opt_in: Boolean(optedInFieldValue),
624
920
  ttf_opt_in: ttfOptIn,
625
921
  data_capture: {
626
922
  instagram: _get(extraData, 'data_capture.instagram', ''),
@@ -637,6 +933,16 @@ const BillingInfoContainer = React.memo(
637
933
  enableReinitialize={false}
638
934
  onSubmit={async (values, formikHelpers) => {
639
935
  try {
936
+ if ((!elementsRef.current || !stripeRef.current) && !orderIsFree) {
937
+ setError('Fill in the payment details')
938
+ return
939
+ }
940
+
941
+ if (isSinglePageCheckout && !orderIsFree) {
942
+ // For PaymentElement, we'll use confirmPayment directly
943
+ // No need to create payment method separately
944
+ }
945
+
640
946
  if (isBrowser) {
641
947
  window.localStorage.setItem(
642
948
  'extraData',
@@ -654,124 +960,213 @@ const BillingInfoContainer = React.memo(
654
960
  })
655
961
  )
656
962
  }
657
- if (isLoggedIn) {
658
- const checkoutBody = collectCheckoutBody(values, userData)
659
-
660
- if (isBrowser) {
661
- addAddOnsInAttributes(checkoutBody)
662
- }
663
963
 
664
- const checkoutResponse = await postOnCheckout(
665
- checkoutBody,
964
+ if (!isLoggedIn) {
965
+ const checkoutBodyForRegistration = createCheckoutDataBody(
966
+ ticketsQuantity.length,
967
+ values,
968
+ { emailLogged, firstNameLogged, lastNameLogged },
969
+ showDOB
970
+ )
971
+ const bodyFormData = createRegisterFormData(
972
+ values,
973
+ checkoutBodyForRegistration,
666
974
  flagFreeTicket
667
975
  )
668
- removeReferralKey()
669
- // After checkout is successful recover updated profile and store it on local storage if needed
670
- if (isBrowser) {
671
- const updatedUserData = await getProfileData()
672
- const profileSpecifiedData = _get(updatedUserData, 'data')
673
- const profileDataObj = setLoggedUserData(profileSpecifiedData)
674
- window.localStorage.setItem(
675
- 'user_data',
676
- JSON.stringify(profileDataObj)
976
+ try {
977
+ setLoading(true)
978
+ const resRegister = await register(bodyFormData)
979
+ const xtfCookie = _get(resRegister, 'headers.x-tf-ecommerce')
980
+ const refreshToken = _get(
981
+ resRegister,
982
+ 'data.attributes.refresh_token'
677
983
  )
678
- }
984
+ const userProfile = _get(resRegister, 'data.attributes.user_profile')
679
985
 
680
- handleSubmit(
681
- values,
682
- formikHelpers as FormikHelpers<any>,
683
- eventId,
684
- checkoutResponse
685
- )
686
- return
687
- }
688
- const checkoutBodyForRegistration = createCheckoutDataBody(
689
- ticketsQuantity.length,
690
- values,
691
- { emailLogged, firstNameLogged, lastNameLogged },
692
- showDOB
693
- )
694
- const bodyFormData = createRegisterFormData(
695
- values,
696
- checkoutBodyForRegistration,
697
- flagFreeTicket
698
- )
699
- try {
700
- setLoading(true)
701
- const resRegister = await register(bodyFormData)
702
- const xtfCookie = _get(resRegister, 'headers.x-tf-ecommerce')
703
- const refreshToken = _get(resRegister, 'data.attributes.refresh_token')
704
- const userProfile = _get(resRegister, 'data.attributes.user_profile')
705
-
706
- setIsNewUser(true)
707
- onRegisterSuccess({
708
- xtfCookie,
709
- refreshToken,
710
- userProfile,
711
- })
712
- } catch (e) {
713
- setLoading(false)
714
-
715
- if (e.response?.data?.data?.hasUnverifiedOrder) {
716
- setPendingVerificationMessage(e.response?.data?.message)
717
- } else if (axios.isAxiosError(e)) {
718
- const error = e?.response?.data?.message
719
- if (_includes(error, 'You must be aged')) {
720
- formikHelpers.setFieldError('holderAge', error)
721
- }
722
- if (error?.password) {
723
- formikHelpers.setFieldError('password', error.password)
724
- formikHelpers.setFieldError('confirmPassword', error.password)
725
- }
726
- if (error?.email && !onLogin) {
727
- // False will stand for outside controll
728
- setAlreadyHasUser(true)
729
- setShowModalLogin(true)
730
- }
986
+ setIsNewUser(true)
987
+ onRegisterSuccess({
988
+ xtfCookie,
989
+ refreshToken,
990
+ userProfile,
991
+ })
992
+ } catch (e) {
993
+ setLoading(false)
994
+ const hasUnverifiedOrder = _get(
995
+ e,
996
+ 'response.data.data.hasUnverifiedOrder'
997
+ )
998
+ const message = _get(e, 'response.data.message', {}) as
999
+ | { password?: string | null, email?: string | null }
1000
+ | string
731
1001
 
732
- if (
733
- _includes(error, 'The cart is expired') &&
734
- !hideErrorsAlertSection
735
- ) {
736
- setError(error)
737
- }
1002
+ if (hasUnverifiedOrder && typeof message === 'string') {
1003
+ setPendingVerificationMessage(message)
1004
+ } else if (axios.isAxiosError(e)) {
1005
+ if (
1006
+ _includes(message, 'You must be aged') &&
1007
+ typeof message === 'string'
1008
+ ) {
1009
+ formikHelpers.setFieldError('holderAge', message)
1010
+ }
1011
+ if (typeof message !== 'string' && message?.password) {
1012
+ formikHelpers.setFieldError('password', message.password)
1013
+ formikHelpers.setFieldError('confirmPassword', message.password)
1014
+ }
1015
+ if (typeof message !== 'string' && message?.email && !onLogin) {
1016
+ // False will stand for outside controll
1017
+ setAlreadyHasUser(true)
1018
+ setShowModalLogin(true)
1019
+ }
1020
+
1021
+ if (
1022
+ _includes(error, 'The cart is expired') &&
1023
+ !hideErrorsAlertSection
1024
+ ) {
1025
+ setError(error)
1026
+ }
738
1027
 
739
- onRegisterError(e, values.email)
1028
+ onRegisterError(e, values.email)
1029
+ }
1030
+ return
740
1031
  }
741
- return
742
1032
  }
1033
+
743
1034
  const profileData = await getProfileData()
744
1035
  const profileSpecifiedData = _get(profileData, 'data')
745
1036
  const profileDataObj = setLoggedUserData(profileSpecifiedData)
1037
+ const userDataObj = isLoggedIn ? userData : profileDataObj
1038
+
746
1039
  if (isBrowser) {
747
- window.localStorage.setItem('user_data', JSON.stringify(profileDataObj))
1040
+ window.localStorage.setItem('user_data', JSON.stringify(userDataObj))
748
1041
  }
749
1042
 
750
- const checkoutBody = collectCheckoutBody(values, profileDataObj)
1043
+ const checkoutBody = collectCheckoutBody(values, userDataObj)
751
1044
 
752
- if (isBrowser) {
1045
+ if (isBrowser && !isSinglePageCheckout) {
753
1046
  addAddOnsInAttributes(checkoutBody)
754
1047
  }
755
1048
 
1049
+ if (isSinglePageCheckout) {
1050
+ checkoutBody.attributes.add_ons = singleCheckoutAddons
1051
+ // Include add_on_data_capture for single-page checkout
1052
+ const storedAddOnDataCapture =
1053
+ localStorage.getItem('add_on_data_capture')
1054
+ if (storedAddOnDataCapture) {
1055
+ checkoutBody.attributes.add_on_data_capture =
1056
+ JSON.parse(storedAddOnDataCapture)
1057
+ }
1058
+ }
1059
+
756
1060
  const checkoutResponse = await postOnCheckout(
757
1061
  checkoutBody,
758
1062
  flagFreeTicket
759
1063
  )
1064
+
1065
+ const checkoutUpdateResponse = await updateCheckout({
1066
+ attributes: {
1067
+ event_id: eventId,
1068
+ add_ons: checkoutBody?.attributes?.add_ons ?? [],
1069
+ is_from_resale: additionalConfigs?.resale,
1070
+ },
1071
+ })
1072
+ console.log(
1073
+ 'Stripe checkoutUpdateResponse in billing-info-container',
1074
+ checkoutUpdateResponse
1075
+ )
1076
+
1077
+ setCheckoutUpdateData(checkoutUpdateResponse.data.attributes)
1078
+
1079
+ let paymentResponse = null
1080
+
1081
+ if (isSinglePageCheckout) {
1082
+ const { hash, total } = checkoutResponse.data.attributes
1083
+ localStorage.setItem('checkoutData', JSON.stringify({ hash, total }))
1084
+
1085
+ const paymentDataResponse = await getPaymentData(String(hash))
1086
+
1087
+ if (paymentDataResponse.success) {
1088
+ const { attributes } = paymentDataResponse.data
1089
+ console.log('Stripe confirmPayment success in billing-info-container')
1090
+ setReviewData(attributes)
1091
+
1092
+ const { order_details, cart } = attributes
1093
+ const {
1094
+ tickets: [ticket],
1095
+ } = order_details
1096
+
1097
+ const updatedOrderData = {
1098
+ add_ons: order_details.add_ons || [],
1099
+ total: order_details.total,
1100
+ subtotal: order_details.subtotal,
1101
+ fees: order_details.fees,
1102
+ pay_now: order_details.pay_now || '',
1103
+
1104
+ id: order_details?.id,
1105
+ product_name: cart[0]?.product_name,
1106
+ ticketType: ticket?.name,
1107
+ quantity: ticket?.quantity,
1108
+ price: ticket?.price,
1109
+ currency: order_details?.currency,
1110
+ guest_count: order_details?.guest_count || '',
1111
+ debt: order_details?.debt || null,
1112
+ cost: ticket?.cost,
1113
+ }
1114
+
1115
+ const isFreeTickets =
1116
+ (!Number(total) && !Number(updatedOrderData.total)) ||
1117
+ !Number(updatedOrderData?.pay_now || 0)
1118
+ const paymentMethod = attributes.payment_method || {}
1119
+ const paymentPlanAvailable = paymentMethod.stripe_payment_plan_enabled
1120
+ console.log({ paymentPlanAvailable })
1121
+ // Process payment using the hook
1122
+ paymentResponse = await processPayment(
1123
+ paymentDataResponse,
1124
+ values,
1125
+ formikHelpers,
1126
+ checkoutResponse,
1127
+ checkoutUpdateResponse,
1128
+ {
1129
+ attributes,
1130
+ isFreeTickets,
1131
+ updatedOrderData,
1132
+ eventId,
1133
+ }
1134
+ )
1135
+
1136
+ if (!paymentResponse && !isFreeTickets) {
1137
+ // Payment failed or redirected, don't continue
1138
+ return
1139
+ }
1140
+ }
1141
+ }
1142
+
760
1143
  removeReferralKey()
1144
+ removeAdditionalConfigs()
761
1145
  handleSubmit(
762
1146
  values,
763
1147
  formikHelpers as FormikHelpers<any>,
764
1148
  eventId,
765
- checkoutResponse
1149
+ checkoutResponse,
1150
+ checkoutUpdateResponse,
1151
+ paymentResponse
766
1152
  )
767
1153
  } catch (e) {
768
1154
  setLoading(false)
769
- if (e.response?.data?.data?.hasUnverifiedOrder) {
770
- setPendingVerificationMessage(e.response?.data?.message)
1155
+ onSubmitError(e as AxiosError)
1156
+ const hasUnverifiedOrder = _get(
1157
+ e,
1158
+ 'response.data.data.hasUnverifiedOrder'
1159
+ )
1160
+ const message = _get(e, 'response.data.message', {}) as
1161
+ | { password?: string | null, email?: string | null }
1162
+ | string
1163
+
1164
+ if (hasUnverifiedOrder && typeof message === 'string') {
1165
+ setPendingVerificationMessage(message)
771
1166
  } else if (axios.isAxiosError(e)) {
772
1167
  if (
773
- e.response?.status === 401 ||
774
- e.response?.data?.error === 'invalid_token'
1168
+ (e as any).response?.status === 401 ||
1169
+ _get(e, 'response.data.error') === 'invalid_token'
775
1170
  ) {
776
1171
  if (isBrowser) {
777
1172
  window.localStorage.removeItem('user_data')
@@ -785,8 +1180,8 @@ const BillingInfoContainer = React.memo(
785
1180
  window.document.dispatchEvent(event)
786
1181
  }
787
1182
  }
788
- if (e.response?.data.message && !hideErrorsAlertSection) {
789
- setError(_get(e, 'response.data.message'))
1183
+ if (message && !hideErrorsAlertSection && typeof message === 'string') {
1184
+ setError(message)
790
1185
  }
791
1186
  onSubmitError(e)
792
1187
  }
@@ -801,6 +1196,7 @@ const BillingInfoContainer = React.memo(
801
1196
  <LogicRunner
802
1197
  brandOptIn={brandOptIn}
803
1198
  values={props.values}
1199
+ errors={props.errors}
804
1200
  setStates={setStates}
805
1201
  setFieldValue={props.setFieldValue}
806
1202
  setValues={props.setValues}
@@ -810,16 +1206,18 @@ const BillingInfoContainer = React.memo(
810
1206
  shouldFetchCountries={shouldFetchCountries}
811
1207
  />
812
1208
  <div className={`billing-info-container ${theme}`}>
813
- <SnackbarAlert
814
- type="error"
815
- isOpen={!!error}
816
- message={error || ''}
817
- onClose={() => {
818
- setError(null)
819
- onErrorClose()
820
- }}
821
- />
822
- {!isLoggedIn && (
1209
+ {!!error && (
1210
+ <SnackbarAlert
1211
+ type="error"
1212
+ isOpen={!!error}
1213
+ message={error || ''}
1214
+ onClose={() => {
1215
+ setError(null)
1216
+ onErrorClose()
1217
+ }}
1218
+ />
1219
+ )}
1220
+ {!isLoggedIn && !isSinglePageCheckout && (
823
1221
  <div className="account-actions-block">
824
1222
  <div className="action-item">
825
1223
  <div>{accountInfoTitle}</div>
@@ -855,178 +1253,151 @@ const BillingInfoContainer = React.memo(
855
1253
  </div>
856
1254
  </div>
857
1255
  )}
1256
+ {isSinglePageCheckout &&
1257
+ !addOnsIncludedOnInvitation &&
1258
+ eventHasAddons &&
1259
+ eventId ? (
1260
+ <SimpleAddonsContainer
1261
+ {...(addonsProps ?? {})}
1262
+ eventId={eventId}
1263
+ addOnDataWithCustomFields={addOnDataWithCustomFields}
1264
+ configs={configs}
1265
+ onAddOnSelect={onAddOnSelect}
1266
+ />
1267
+ ) : !addOnsIncludedOnInvitation &&
1268
+ includeAddons &&
1269
+ !isSinglePageCheckout ? (
1270
+ <AddonsContainter
1271
+ {...(addonsProps ?? {})}
1272
+ addOnDataWithCustomFields={addOnDataWithCustomFields}
1273
+ configs={configs}
1274
+ onAddOnSelect={onAddOnSelect}
1275
+ />
1276
+ ) : null}
858
1277
  {!cardLoading &&
859
1278
  _map(dataWithUniqueIds, item => {
860
1279
  const { label, labelClassName, fields } = item
861
1280
  return (
862
- <React.Fragment key={item.uniqueId}>
1281
+ <div key={item.uniqueId} className="billing-info-fields">
863
1282
  <p className={labelClassName}>{label}</p>
864
1283
  {_map(fields, group => {
865
1284
  const { groupClassname, groupItems } = group
1285
+ const filteredGroupItems = filterBillingInfoFields(
1286
+ groupItems,
1287
+ {
1288
+ showDOB: Boolean(showDOB),
1289
+ hideTtfOptIn: Boolean(hideTtfOptIn),
1290
+ hidePhoneField: Boolean(hidePhoneField),
1291
+ flagRequirePhone: Boolean(flagRequirePhone),
1292
+ collectMandatoryWalletAddress: Boolean(
1293
+ collectMandatoryWalletAddress
1294
+ ),
1295
+ collectMandatoryJobTitle: Boolean(
1296
+ collectMandatoryJobTitle
1297
+ ),
1298
+ collectMandatoryBusinessCategory: Boolean(
1299
+ collectMandatoryBusinessCategory
1300
+ ),
1301
+ collectMandatoryCompany: Boolean(collectMandatoryCompany),
1302
+ collectMandatoryInstagram: Boolean(
1303
+ collectMandatoryInstagram
1304
+ ),
1305
+ flagFreeTicket: Boolean(flagFreeTicket),
1306
+ hideWalletAddressField: Boolean(hideWalletAddressField),
1307
+ hideJobTitleField: Boolean(hideJobTitleField),
1308
+ hideBusinessCategoryField: Boolean(
1309
+ hideBusinessCategoryField
1310
+ ),
1311
+ hideCompanyField: Boolean(hideCompanyField),
1312
+ hideInstagramField,
1313
+ }
1314
+ )
866
1315
  return (
867
1316
  <React.Fragment key={group.uniqueId}>
868
1317
  <div className={groupClassname}>
869
- {_map(
870
- groupItems.filter(el => {
871
- if (el.name === 'holderAge' && !showDOB) {
872
- return false
873
- }
874
- if (el.name === 'ttf_opt_in' && hideTtfOptIn) {
875
- return false
876
- }
877
- if (el.name === 'phone') {
878
- if (!hidePhoneField) {
879
- el.required = flagRequirePhone
880
- } else {
881
- return false
882
- }
883
- }
884
- if (el.name === 'data_capture[wallet_address]') {
885
- if (collectMandatoryWalletAddress) {
886
- el.required = true
887
- }
888
- }
889
- if (el.name === 'data_capture[jobTitle]') {
890
- if (collectMandatoryJobTitle) {
891
- el.required = true
892
- }
893
- }
894
- if (el.name === 'data_capture[businessCategory]') {
895
- if (collectMandatoryBusinessCategory) {
896
- el.required = true
897
- }
898
- }
899
- if (el.name === 'data_capture[company]') {
900
- if (collectMandatoryCompany) {
901
- el.required = true
902
- }
903
- }
904
- if (el.name === 'data_capture[instagram]') {
905
- if (collectMandatoryInstagram) {
906
- el.required = true
907
- }
908
- }
909
- if (
910
- [
911
- 'street_address',
912
- 'country',
913
- 'state',
914
- 'city',
915
- ].includes(el.name)
916
- ) {
917
- if (flagFreeTicket) {
918
- el.required = false
919
- return false
920
- }
921
- }
922
- if (
923
- hideWalletAddressField &&
924
- (el.name === 'wallet-address-info' ||
925
- el.name === 'data_capture[wallet_address]')
926
- ) {
927
- return false
928
- }
929
- if (
930
- hideJobTitleField &&
931
- el.name === 'data_capture[jobTitle]'
932
- ) {
933
- return false
934
- }
935
- if (
936
- hideBusinessCategoryField &&
937
- el.name === 'data_capture[businessCategory]'
938
- ) {
939
- return false
940
- }
941
- if (
942
- hideCompanyField &&
943
- el.name === 'data_capture[company]'
944
- ) {
945
- return false
946
- }
947
- if (
948
- hideInstagramField &&
949
- (el.name === 'data_capture[instagram]' ||
950
- el.name === 'instagram-info')
951
- ) {
952
- return false
953
- }
954
- return true
955
- }),
956
- element =>
957
- [
958
- 'password',
959
- 'confirmPassword',
960
- 'password-info',
961
- ].includes(element.name) && isLoggedIn ? null : (
962
- <React.Fragment key={element.uniqueId}>
963
- <div
964
- className={`${element.className} ${
965
- props?.errors[element.name] || ''
966
- }`}
967
- >
968
- {element.component ? (
969
- element.component
1318
+ {_map(filteredGroupItems, element =>
1319
+ [
1320
+ 'password',
1321
+ 'confirmPassword',
1322
+ 'password-info',
1323
+ ].includes(element.name) && isLoggedIn ? null : (
1324
+ <React.Fragment key={element.uniqueId}>
1325
+ <div
1326
+ className={`${element.className} ${
1327
+ props?.errors[element.name] || ''
1328
+ }`}
1329
+ >
1330
+ {element.component ? (
1331
+ typeof element.component === 'function' ? (
1332
+ renderComponentWithProps(
1333
+ element.component as React.ComponentType<any>,
1334
+ element.name === 'payment_info'
1335
+ ? { reviewData }
1336
+ : {}
1337
+ )
970
1338
  ) : (
971
- <Field
972
- {...element}
973
- type={
974
- element.type === 'radio' ||
975
- element.type === 'checkbox'
976
- ? undefined
977
- : element.type
978
- }
979
- setPhoneValidationIsLoading={
980
- element.type === 'phone'
981
- ? setPhoneValidationIsLoading
982
- : undefined
983
- }
984
- label={getFieldLabel(element, configs)}
985
- validate={getValidateFunctions(
986
- element,
987
- states,
988
- props.values,
989
- props.errors
990
- )}
991
- setFieldValue={props.setFieldValue}
992
- onBlur={props.handleBlur}
993
- component={getFieldComponent(element)}
994
- selectOptions={
995
- element.name === 'country'
996
- ? _map(countries, item => ({
1339
+ element.component
1340
+ )
1341
+ ) : (
1342
+ <Field
1343
+ {...element}
1344
+ type={
1345
+ element.type === 'radio' ||
1346
+ element.type === 'checkbox'
1347
+ ? undefined
1348
+ : element.type
1349
+ }
1350
+ setPhoneValidationIsLoading={
1351
+ element.type === 'phone'
1352
+ ? setPhoneValidationIsLoading
1353
+ : undefined
1354
+ }
1355
+ label={getFieldLabel(element, configs)}
1356
+ validate={getValidateFunctions(
1357
+ element,
1358
+ states,
1359
+ props.values,
1360
+ props.errors
1361
+ )}
1362
+ setFieldValue={props.setFieldValue}
1363
+ onBlur={props.handleBlur}
1364
+ component={getFieldComponent(element)}
1365
+ selectOptions={
1366
+ element.name === 'country'
1367
+ ? _map(countries, item => ({
997
1368
  value: item.id,
998
1369
  label: item.name,
999
1370
  }))
1000
- : element.name === 'state'
1001
- ? [
1002
- {
1003
- label: element.label,
1004
- value: '',
1005
- disabled: true,
1006
- },
1007
- ...states,
1008
- ]
1009
- : element.selectOptions || []
1010
- }
1011
- theme={theme}
1012
- defaultCountry={
1013
- defaultCountry || element.defaultCountry
1014
- }
1015
- dateFormat={element.format}
1016
- isCountryCodeEditable={
1017
- isCountryCodeEditable
1018
- }
1019
- />
1020
- )}
1021
- </div>
1022
- </React.Fragment>
1023
- )
1371
+ : element.name === 'state'
1372
+ ? [
1373
+ {
1374
+ label: element.label,
1375
+ value: '',
1376
+ disabled: true,
1377
+ },
1378
+ ...states,
1379
+ ]
1380
+ : element.selectOptions || []
1381
+ }
1382
+ theme={theme}
1383
+ defaultCountry={
1384
+ defaultCountry || element.defaultCountry
1385
+ }
1386
+ dateFormat={element.format}
1387
+ isCountryCodeEditable={
1388
+ isCountryCodeEditable
1389
+ }
1390
+ />
1391
+ )}
1392
+ </div>
1393
+ </React.Fragment>
1394
+ )
1024
1395
  )}
1025
1396
  </div>
1026
1397
  </React.Fragment>
1027
1398
  )
1028
1399
  })}
1029
- </React.Fragment>
1400
+ </div>
1030
1401
  )
1031
1402
  })}
1032
1403
  {!_isEmpty(ticketHoldersFields.fields) && (
@@ -1046,7 +1417,7 @@ const BillingInfoContainer = React.memo(
1046
1417
  {...element}
1047
1418
  type={
1048
1419
  element.type === 'radio' ||
1049
- element.type === 'checkbox'
1420
+ element.type === 'checkbox'
1050
1421
  ? undefined
1051
1422
  : element.type
1052
1423
  }
@@ -1077,6 +1448,49 @@ const BillingInfoContainer = React.memo(
1077
1448
  ))}
1078
1449
  </div>
1079
1450
  )}
1451
+ <div className="payment-section">
1452
+ {isSinglePageCheckout && !orderIsFree && stripePublishableKey && (
1453
+ <PaymentContainer
1454
+ stripePublishableKey={stripePublishableKey}
1455
+ stripeAccountId={stripeAccountId}
1456
+ formTitle="Payment Information"
1457
+ orderInfoLabel=""
1458
+ enableTimer={false}
1459
+ checkoutData={checkoutData}
1460
+ elementsOptions={elementsOptions}
1461
+ paymentElementOptions={{
1462
+ wallets: {
1463
+ applePay:
1464
+ checkoutUpdateData?.additional_payment_information
1465
+ ?.stripe_wallets?.applePay || 'never',
1466
+ googlePay:
1467
+ checkoutUpdateData?.additional_payment_information
1468
+ ?.stripe_wallets?.googlePay || 'never',
1469
+ },
1470
+ }}
1471
+ onStripeReady={(stripe, elements) => {
1472
+ stripeRef.current = stripe
1473
+ elementsRef.current = elements
1474
+ }}
1475
+ paymentFields={paymentProps.paymentFields || []}
1476
+ onPaymentError={paymentProps.onPaymentError || _identity}
1477
+ handlePayment={paymentProps.handlePayment || _identity}
1478
+ onGetPaymentDataSuccess={
1479
+ paymentProps.onGetPaymentDataSuccess || _identity
1480
+ }
1481
+ onGetPaymentDataError={
1482
+ paymentProps.onGetPaymentDataError || _identity
1483
+ }
1484
+ themeOptions={themeOptions}
1485
+ paymentInfoLabel=""
1486
+ displayPaymentButton={false}
1487
+ hidePaymentForm={false}
1488
+ hideFieldsBlock={true}
1489
+ isSinglePageCheckout={true}
1490
+ />
1491
+ )}
1492
+ {paymentSectionAddon}
1493
+ </div>
1080
1494
  <div className="button-container">
1081
1495
  <Button
1082
1496
  type="submit"
@@ -1084,7 +1498,13 @@ const BillingInfoContainer = React.memo(
1084
1498
  className="login-register-button"
1085
1499
  disabled={props.isSubmitting || phoneValidationIsLoading}
1086
1500
  >
1087
- {props.isSubmitting ? <CircularProgress size={26} /> : buttonName}
1501
+ {props.isSubmitting ? (
1502
+ <CircularProgress size={26} />
1503
+ ) : orderIsFree ? (
1504
+ freeOrderButtonName
1505
+ ) : (
1506
+ buttonName
1507
+ )}
1088
1508
  </Button>
1089
1509
  </div>
1090
1510
  </div>
@@ -1149,6 +1569,7 @@ const BillingInfoContainer = React.memo(
1149
1569
  onForgotPasswordSuccess={onForgotPasswordSuccess}
1150
1570
  onForgotPasswordError={onForgotPasswordError}
1151
1571
  showPoweredByImage={showPoweredByImage}
1572
+ displaySuccessMessage
1152
1573
  />
1153
1574
  )}
1154
1575
  <VerificationPendingModal