tf-checkout-react 1.6.6 → 1.7.2

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