tf-checkout-react-native 2.0.0-beta.0 → 2.0.0-beta.6

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 (60) hide show
  1. package/README.md +92 -8
  2. package/dist/index.d.mts +7410 -290
  3. package/dist/index.d.ts +7410 -290
  4. package/dist/index.js +23539 -5909
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +23561 -5857
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +22 -10
  9. package/scripts/build-seat-map.mjs +31 -0
  10. package/scripts/seat-map-entry.jsx +22 -0
  11. package/src/CardInput.tsx +64 -3
  12. package/src/EmbeddedCheckout.tsx +129 -36
  13. package/src/auth/AuthModal.tsx +90 -6
  14. package/src/auth/EmailCodeForm.tsx +91 -0
  15. package/src/auth/RegisterForm.tsx +91 -5
  16. package/src/auth/TwoFactorForm.tsx +74 -0
  17. package/src/auth/index.ts +4 -0
  18. package/src/configure.ts +8 -1
  19. package/src/confirmer.ts +112 -20
  20. package/src/generated/seatMapWidget.ts +3 -0
  21. package/src/index.tsx +1 -1
  22. package/src/sections/Accommodation.tsx +57 -0
  23. package/src/sections/AccountRewards.tsx +108 -0
  24. package/src/sections/AddOns.tsx +99 -148
  25. package/src/sections/BillingInfo.tsx +63 -64
  26. package/src/sections/Bundles.tsx +467 -0
  27. package/src/sections/EventDiscussionView.tsx +637 -0
  28. package/src/sections/Passes.tsx +177 -0
  29. package/src/sections/Payment.tsx +169 -52
  30. package/src/sections/PostReaction.tsx +122 -0
  31. package/src/sections/Rewards.tsx +320 -0
  32. package/src/sections/SeatMap.tsx +225 -0
  33. package/src/sections/SeatsIoMap.tsx +110 -0
  34. package/src/sections/Streaming.tsx +278 -0
  35. package/src/sections/TicketChange.tsx +379 -0
  36. package/src/sections/TicketGift.tsx +206 -0
  37. package/src/sections/TicketRefund.tsx +104 -0
  38. package/src/sections/Tickets.tsx +289 -208
  39. package/src/sections/Trips.tsx +87 -0
  40. package/src/sections/WaiverSigning.tsx +429 -0
  41. package/src/sections/billing/PhoneIdentity.tsx +169 -0
  42. package/src/sections/discussion/CommentImages.tsx +366 -0
  43. package/src/sections/discussion/SwipeReply.tsx +88 -0
  44. package/src/sections/index.ts +26 -0
  45. package/src/sections/rewards/RewardsLadder.tsx +167 -0
  46. package/src/sections/rewards/RewardsTree.tsx +117 -0
  47. package/src/storage.ts +8 -9
  48. package/src/ui/CheckoutProvider.tsx +25 -13
  49. package/src/ui/Field.tsx +21 -3
  50. package/src/ui/Frozen.tsx +22 -0
  51. package/src/ui/InstallmentPicker.tsx +15 -18
  52. package/src/ui/PaymentMethods.tsx +6 -3
  53. package/src/ui/ProviderPages.tsx +134 -0
  54. package/src/ui/QuantitySelect.tsx +26 -2
  55. package/src/ui/Select.tsx +4 -1
  56. package/src/ui/SignaturePad.tsx +218 -0
  57. package/src/ui/index.ts +1 -0
  58. package/src/ui/primitives.tsx +13 -2
  59. package/src/ui/useAccountT.ts +16 -0
  60. package/src/useCheckout.ts +3 -1
@@ -0,0 +1,91 @@
1
+ import React, { useState, type FC } from 'react'
2
+ import { View } from 'react-native'
3
+ import { Button, ErrorText, MutedText } from '../ui/primitives'
4
+ import { Field, Input } from '../ui/Field'
5
+ export interface EmailCodeFormProps {
6
+ initialEmail: string
7
+ sent: boolean
8
+ target: string
9
+ retryAfter: number
10
+ loading: boolean
11
+ error: string
12
+ successMessage: string
13
+ onRequest: (email: string) => Promise<boolean>
14
+ onVerify: (code: string) => Promise<unknown>
15
+ onBack: () => void
16
+ }
17
+ export const EmailCodeForm: FC<EmailCodeFormProps> = ({
18
+ initialEmail,
19
+ sent,
20
+ target,
21
+ retryAfter,
22
+ loading,
23
+ error,
24
+ successMessage,
25
+ onRequest,
26
+ onVerify,
27
+ onBack,
28
+ }) => {
29
+ const [email, setEmail] = useState(initialEmail)
30
+ const [code, setCode] = useState('')
31
+ return (
32
+ <View style={{ gap: 14 }}>
33
+ <MutedText>
34
+ {sent
35
+ ? `Enter the sign-in code sent to ${target}.`
36
+ : 'Receive a one-time sign-in code by email.'}
37
+ </MutedText>
38
+ {error ? <ErrorText>{error}</ErrorText> : null}
39
+ {successMessage ? <MutedText>{successMessage}</MutedText> : null}
40
+ <Field label="Email" required>
41
+ <Input
42
+ value={email}
43
+ onChangeText={setEmail}
44
+ keyboardType="email-address"
45
+ autoCapitalize="none"
46
+ editable={!sent && !loading}
47
+ accessibilityLabel="Email"
48
+ />
49
+ </Field>
50
+ {sent ? (
51
+ <>
52
+ <Field label="Sign-in code" required>
53
+ <Input
54
+ value={code}
55
+ onChangeText={(value) => setCode(value.replace(/\D/g, '').slice(0, 6))}
56
+ accessibilityLabel="Sign-in code"
57
+ keyboardType="number-pad"
58
+ textContentType="oneTimeCode"
59
+ autoComplete="one-time-code"
60
+ maxLength={6}
61
+ autoFocus
62
+ editable={!loading}
63
+ />
64
+ </Field>
65
+ <Button
66
+ loading={loading}
67
+ disabled={!/^\d{6}$/.test(code)}
68
+ onPress={() => void onVerify(code)}
69
+ >
70
+ Verify code
71
+ </Button>
72
+ </>
73
+ ) : null}
74
+ <Button
75
+ loading={!sent && loading}
76
+ variant={sent ? 'ghost' : 'primary'}
77
+ disabled={loading || retryAfter > 0 || !email.trim()}
78
+ onPress={() => void onRequest(email)}
79
+ >
80
+ {retryAfter > 0
81
+ ? `Send another code in ${retryAfter}s`
82
+ : sent
83
+ ? 'Resend code'
84
+ : 'Send sign-in code'}
85
+ </Button>
86
+ <Button variant="ghost" disabled={loading} onPress={onBack}>
87
+ Use password instead
88
+ </Button>
89
+ </View>
90
+ )
91
+ }
@@ -7,29 +7,38 @@
7
7
  * `phone-pad` input (the US default still seeds it with "+1").
8
8
  */
9
9
  import React, { useEffect, useMemo, useState, type FC } from 'react'
10
- import { StyleSheet, View } from 'react-native'
10
+ import { Linking, Pressable, StyleSheet, View } from 'react-native'
11
11
 
12
12
  import {
13
13
  hooks,
14
+ createTranslator,
15
+ type LocaleCode,
14
16
  type AuthFieldErrors,
15
17
  type AuthFieldName,
16
18
  type AuthRegistrationInput,
17
19
  } from 'tf-checkout-shared'
18
20
 
19
- import { Button, ErrorText } from '../ui/primitives'
21
+ import { Button, ErrorText, MutedText } from '../ui/primitives'
20
22
  import { Field, Input } from '../ui/Field'
21
23
  import { Select, type SelectOption } from '../ui/Select'
22
24
  import { PasswordInput } from './PasswordInput'
23
25
 
24
26
  export interface RegisterFormProps {
25
27
  initialEmail?: string
28
+ locale?: LocaleCode
26
29
  errors?: AuthFieldErrors
27
30
  error?: string
28
31
  loading?: boolean
29
32
  requireAddress?: boolean
33
+ requireTerms?: boolean
34
+ completionRefresh?: {
35
+ retryAfter: number
36
+ onRequest: () => Promise<boolean>
37
+ onVerify: (code: string) => Promise<unknown>
38
+ }
30
39
  onSubmit: (
31
40
  input: AuthRegistrationInput,
32
- options: { requireState: boolean }
41
+ options: { requireState: boolean; termsAccepted?: boolean }
33
42
  ) => void | Promise<unknown>
34
43
  onFieldChange?: (field: AuthFieldName) => void
35
44
  }
@@ -50,16 +59,23 @@ const emptyRegistration = (email: string): AuthRegistrationInput => ({
50
59
 
51
60
  export const RegisterForm: FC<RegisterFormProps> = ({
52
61
  initialEmail = '',
62
+ locale,
53
63
  errors = {},
54
64
  error,
55
65
  loading = false,
56
66
  requireAddress = true,
67
+ requireTerms = false,
68
+ completionRefresh,
57
69
  onSubmit,
58
70
  onFieldChange,
59
71
  }) => {
72
+ const t = useMemo(() => createTranslator({ locale }), [locale])
60
73
  const [values, setValues] = useState<AuthRegistrationInput>(() =>
61
74
  emptyRegistration(initialEmail)
62
75
  )
76
+ const [termsAccepted, setTermsAccepted] = useState(false)
77
+ const [refreshingProof, setRefreshingProof] = useState(false)
78
+ const [refreshCode, setRefreshCode] = useState('')
63
79
  const countriesQuery = hooks.useCountries()
64
80
  const countries = useMemo(() => countriesQuery.data ?? [], [countriesQuery.data])
65
81
  const statesQuery = hooks.useStates(values.countryId, {
@@ -103,7 +119,8 @@ export const RegisterForm: FC<RegisterFormProps> = ({
103
119
  [states]
104
120
  )
105
121
 
106
- const submit = () => void onSubmit(values, { requireState: states.length > 0 })
122
+ const submit = () =>
123
+ void onSubmit(values, { requireState: states.length > 0, termsAccepted })
107
124
 
108
125
  return (
109
126
  <View style={styles.form}>
@@ -137,6 +154,7 @@ export const RegisterForm: FC<RegisterFormProps> = ({
137
154
  <Field label="Email" required error={errors.email}>
138
155
  <Input
139
156
  value={values.email}
157
+ editable={!requireTerms}
140
158
  onChangeText={(text) => setField('email', text)}
141
159
  placeholder="Email"
142
160
  keyboardType="email-address"
@@ -233,7 +251,75 @@ export const RegisterForm: FC<RegisterFormProps> = ({
233
251
  />
234
252
  </Field>
235
253
 
236
- <Button onPress={submit} loading={loading}>
254
+ {completionRefresh ? (
255
+ <View style={{ gap: 12 }}>
256
+ <Button
257
+ disabled={loading || completionRefresh.retryAfter > 0}
258
+ onPress={() => {
259
+ setRefreshingProof(true)
260
+ void completionRefresh.onRequest()
261
+ }}
262
+ >
263
+ {t('auth.completionVerifyAgain')}
264
+ {completionRefresh.retryAfter > 0
265
+ ? ` (${completionRefresh.retryAfter}s)`
266
+ : ''}
267
+ </Button>
268
+ {refreshingProof ? (
269
+ <>
270
+ <Field label={t('auth.completionNewCode')}>
271
+ <Input
272
+ value={refreshCode}
273
+ keyboardType="number-pad"
274
+ textContentType="oneTimeCode"
275
+ maxLength={6}
276
+ onChangeText={(value) =>
277
+ setRefreshCode(value.replace(/\D/g, '').slice(0, 6))
278
+ }
279
+ />
280
+ </Field>
281
+ <Button
282
+ disabled={loading || !/^\d{6}$/.test(refreshCode)}
283
+ onPress={() => void completionRefresh.onVerify(refreshCode)}
284
+ >
285
+ {t('auth.completionVerifyCode')}
286
+ </Button>
287
+ </>
288
+ ) : null}
289
+ </View>
290
+ ) : null}
291
+ {requireTerms ? (
292
+ <View style={{ gap: 8 }}>
293
+ <Pressable
294
+ accessibilityRole="checkbox"
295
+ accessibilityState={{ checked: termsAccepted }}
296
+ onPress={() => setTermsAccepted((current) => !current)}
297
+ >
298
+ <MutedText>
299
+ {termsAccepted ? '☑' : '☐'} {t('auth.completionAgree')}
300
+ </MutedText>
301
+ </Pressable>
302
+ <Pressable
303
+ accessibilityRole="link"
304
+ onPress={() =>
305
+ void Linking.openURL('https://www.ticketfairy.com/terms-of-service')
306
+ }
307
+ >
308
+ <MutedText>{t('auth.completionTerms')}</MutedText>
309
+ </Pressable>
310
+ <Pressable
311
+ accessibilityRole="link"
312
+ onPress={() => void Linking.openURL('https://www.ticketfairy.com/privacy')}
313
+ >
314
+ <MutedText>{t('auth.completionPrivacy')}</MutedText>
315
+ </Pressable>
316
+ </View>
317
+ ) : null}
318
+ <Button
319
+ onPress={submit}
320
+ loading={loading}
321
+ disabled={requireTerms && !termsAccepted}
322
+ >
237
323
  Create Account
238
324
  </Button>
239
325
  </View>
@@ -0,0 +1,74 @@
1
+ import React, { useState, type FC } from 'react'
2
+ import { View } from 'react-native'
3
+ import { Button, ErrorText, MutedText } from '../ui/primitives'
4
+ import { Field, Input } from '../ui/Field'
5
+
6
+ export interface TwoFactorFormProps {
7
+ loading: boolean
8
+ error: string
9
+ successMessage: string
10
+ smsAvailable: boolean
11
+ smsRetryAfter: number
12
+ onSubmit: (code: string) => Promise<unknown>
13
+ onSendSms: () => Promise<boolean>
14
+ onCancel: () => Promise<void>
15
+ }
16
+
17
+ /** Presentation only; the shared headless flow owns the pending identity. */
18
+ export const TwoFactorForm: FC<TwoFactorFormProps> = ({
19
+ loading,
20
+ error,
21
+ successMessage,
22
+ smsAvailable,
23
+ smsRetryAfter,
24
+ onSubmit,
25
+ onSendSms,
26
+ onCancel,
27
+ }) => {
28
+ const [code, setCode] = useState('')
29
+ return (
30
+ <View style={{ gap: 14 }}>
31
+ <MutedText>
32
+ Enter the six-digit code from your authenticator app or registered phone to
33
+ complete sign-in.
34
+ </MutedText>
35
+ {error ? <ErrorText>{error}</ErrorText> : null}
36
+ {successMessage ? <MutedText>{successMessage}</MutedText> : null}
37
+ <Field label="Verification code" required>
38
+ <Input
39
+ accessibilityLabel="Verification code"
40
+ value={code}
41
+ onChangeText={(value) => setCode(value.replace(/\D/g, '').slice(0, 6))}
42
+ keyboardType="number-pad"
43
+ textContentType="oneTimeCode"
44
+ autoComplete="one-time-code"
45
+ maxLength={6}
46
+ autoFocus
47
+ editable={!loading}
48
+ onSubmitEditing={() => void onSubmit(code)}
49
+ />
50
+ </Field>
51
+ <Button
52
+ loading={loading}
53
+ disabled={!/^\d{6}$/.test(code)}
54
+ onPress={() => void onSubmit(code)}
55
+ >
56
+ Verify code
57
+ </Button>
58
+ {smsAvailable ? (
59
+ <Button
60
+ variant="ghost"
61
+ disabled={loading || smsRetryAfter > 0}
62
+ onPress={() => void onSendSms()}
63
+ >
64
+ {smsRetryAfter > 0
65
+ ? `Send another code in ${smsRetryAfter}s`
66
+ : 'Send a code by SMS'}
67
+ </Button>
68
+ ) : null}
69
+ <Button variant="ghost" disabled={loading} onPress={() => void onCancel()}>
70
+ Use password again
71
+ </Button>
72
+ </View>
73
+ )
74
+ }
package/src/auth/index.ts CHANGED
@@ -5,3 +5,7 @@ export { RegisterForm } from './RegisterForm'
5
5
  export { ForgotPasswordForm } from './ForgotPasswordForm'
6
6
  export { ResetPasswordForm } from './ResetPasswordForm'
7
7
  export { PasswordInput } from './PasswordInput'
8
+
9
+ export { TwoFactorForm, type TwoFactorFormProps } from './TwoFactorForm'
10
+
11
+ export { EmailCodeForm, type EmailCodeFormProps } from './EmailCodeForm'
package/src/configure.ts CHANGED
@@ -23,4 +23,11 @@ import { createMmkvStorage } from './storage'
23
23
  export const mmkvStorageAdapter = createMmkvStorage()
24
24
 
25
25
  export const configureCheckout = (config: TtfConfig): void =>
26
- configure({ storage: mmkvStorageAdapter, cookieAuth: true, ...config })
26
+ configure({
27
+ storage: mmkvStorageAdapter,
28
+ cookieAuth: true,
29
+ ...config,
30
+ // NSURLSession/RCTNetworking would otherwise accept Set-Cookie outside
31
+ // the SDK epoch guard. Only the SDK's header-backed jar owns native auth.
32
+ withCredentials: false,
33
+ })
package/src/confirmer.ts CHANGED
@@ -10,11 +10,8 @@
10
10
  * flows from the mounted `<CardInput/>` (`<CardField/>`) into `confirmPayment`
11
11
  * natively — there is no element to register the way web does.
12
12
  *
13
- * Deferred (optional ports, left unimplemented — the engine falls back to a
14
- * typed "unsupported gateway" for them): WebView-based rails — Razorpay / PayPal
15
- * / Stripe redirect methods (`providerPage`, `createRedirectPaymentMethod`) and
16
- * Xendit tokenization (`tokenizeXenditCard`). They need `react-native-webview`,
17
- * which this package injects rather than depends on; wiring them is a follow-up.
13
+ * CheckoutProvider owns provider-page windows for PayPal, Razorpay, Xendit and
14
+ * Stripe redirect rails. Imperative hosts supply their own providerPage port.
18
15
  */
19
16
  import { Linking } from 'react-native'
20
17
  import {
@@ -29,10 +26,14 @@ import {
29
26
  type BillingDetails,
30
27
  } from '@stripe/stripe-react-native'
31
28
 
32
- import type {
33
- CheckoutBilling,
34
- CheckoutPorts,
35
- PaymentConfirmer,
29
+ import {
30
+ nativeStripeErrorOutcome,
31
+ confirmedStripeIntentStatus,
32
+ paymentProviderError,
33
+ type CheckoutBilling,
34
+ type CheckoutPorts,
35
+ type NativeStripeErrorLike,
36
+ type PaymentConfirmer,
36
37
  } from 'tf-checkout-shared'
37
38
 
38
39
  /** `CheckoutBilling` → the native SDK's `BillingDetails`. Mirrors the web
@@ -55,6 +56,18 @@ const toBillingDetails = (billing: CheckoutBilling): BillingDetails => ({
55
56
  const failed = (message: string | undefined, fallback: string): Error =>
56
57
  new Error(message || fallback)
57
58
 
59
+ /** A stripe-react-native error as the error the engine classifies: only an
60
+ * explicit decline or cancel lets the checkout reset
61
+ * (`nativeStripeErrorOutcome`). */
62
+ const stripeFailure = (
63
+ error: NativeStripeErrorLike & { message?: string },
64
+ fallback: string
65
+ ): Error =>
66
+ paymentProviderError(error.message || fallback, nativeStripeErrorOutcome(error), {
67
+ type: error.type,
68
+ code: error.stripeErrorCode ?? error.code,
69
+ })
70
+
58
71
  /**
59
72
  * The confirmer every `useCheckout`/`<CardInput/>` binds to. `init()` is
60
73
  * idempotent per key so an event on a connected Stripe account re-initializes
@@ -62,6 +75,8 @@ const failed = (message: string | undefined, fallback: string): Error =>
62
75
  */
63
76
  export const createRnStripeConfirmer = (): PaymentConfirmer => {
64
77
  let initKey: string | null = null
78
+ let accountKey: string | null = null
79
+ const listeners = new Set<() => void>()
65
80
 
66
81
  return {
67
82
  init: async ({ publishableKey, accountId, merchantIdentifier, urlScheme }) => {
@@ -74,6 +89,18 @@ export const createRnStripeConfirmer = (): PaymentConfirmer => {
74
89
  ...(urlScheme ? { urlScheme } : {}),
75
90
  })
76
91
  initKey = key
92
+ accountKey = `${publishableKey}:${accountId ?? ''}`
93
+ listeners.forEach((listener) => listener())
94
+ },
95
+
96
+ // `publishableKey:accountId`, the shape `bundleConfirmerKey` expects.
97
+ currentKey: () => accountKey,
98
+
99
+ subscribe: (listener) => {
100
+ listeners.add(listener)
101
+ return () => {
102
+ listeners.delete(listener)
103
+ }
77
104
  },
78
105
 
79
106
  // Native statuses are already the port's PascalCase vocabulary — no normalize.
@@ -87,8 +114,8 @@ export const createRnStripeConfirmer = (): PaymentConfirmer => {
87
114
  paymentMethodType: 'Card',
88
115
  paymentMethodData: { billingDetails: toBillingDetails(billing) },
89
116
  })
90
- if (error) throw failed(error.message, 'Payment failed.')
91
- return paymentIntent?.status
117
+ if (error) throw stripeFailure(error, 'Payment failed.')
118
+ return confirmedStripeIntentStatus(paymentIntent)
92
119
  },
93
120
 
94
121
  confirmApplePay: async (clientSecret, total, currencyCode, merchant) => {
@@ -105,8 +132,8 @@ export const createRnStripeConfirmer = (): PaymentConfirmer => {
105
132
  ],
106
133
  },
107
134
  })
108
- if (error) throw failed(error.message, 'Apple Pay failed.')
109
- return paymentIntent?.status
135
+ if (error) throw stripeFailure(error, 'Apple Pay failed.')
136
+ return confirmedStripeIntentStatus(paymentIntent)
110
137
  },
111
138
 
112
139
  confirmGooglePay: async (clientSecret, total, currencyCode, merchant) => {
@@ -118,8 +145,8 @@ export const createRnStripeConfirmer = (): PaymentConfirmer => {
118
145
  merchantName: merchant.merchantName ?? 'Order',
119
146
  },
120
147
  })
121
- if (error) throw failed(error.message, 'Google Pay failed.')
122
- return paymentIntent?.status
148
+ if (error) throw stripeFailure(error, 'Google Pay failed.')
149
+ return confirmedStripeIntentStatus(paymentIntent)
123
150
  },
124
151
  }
125
152
  }
@@ -130,9 +157,9 @@ export const createRnStripeConfirmer = (): PaymentConfirmer => {
130
157
  * `Linking`. WebView rails are intentionally absent (see file header).
131
158
  */
132
159
  export const createRnPorts = (): Omit<CheckoutPorts, 'confirmer'> => ({
133
- openUrl: (url: string) => {
134
- void Linking.openURL(url)
135
- },
160
+ // The promise is handed back: a URL the device cannot open rejects, and the
161
+ // caller shows its failure (for example "We could not open the payment page").
162
+ openUrl: (url: string) => Linking.openURL(url).then(() => undefined),
136
163
 
137
164
  createCardPaymentMethod: async (billing) => {
138
165
  const { paymentMethod, error } = await createPaymentMethod({
@@ -143,9 +170,74 @@ export const createRnPorts = (): Omit<CheckoutPorts, 'confirmer'> => ({
143
170
  return paymentMethod.id
144
171
  },
145
172
 
173
+ createRedirectPaymentMethod: async (methodType, billing) => {
174
+ const details = toBillingDetails(billing)
175
+ const result = await (() => {
176
+ switch (methodType) {
177
+ case 'klarna':
178
+ return createPaymentMethod({
179
+ paymentMethodType: 'Klarna',
180
+ paymentMethodData: { billingDetails: details },
181
+ })
182
+ case 'afterpay_clearpay':
183
+ return createPaymentMethod({
184
+ paymentMethodType: 'AfterpayClearpay',
185
+ paymentMethodData: { billingDetails: details, shippingDetails: details },
186
+ })
187
+ case 'affirm':
188
+ return createPaymentMethod({
189
+ paymentMethodType: 'Affirm',
190
+ paymentMethodData: { billingDetails: details },
191
+ })
192
+ case 'ideal':
193
+ return createPaymentMethod({
194
+ paymentMethodType: 'Ideal',
195
+ paymentMethodData: { billingDetails: details },
196
+ })
197
+ case 'bancontact':
198
+ return createPaymentMethod({
199
+ paymentMethodType: 'Bancontact',
200
+ paymentMethodData: { billingDetails: details },
201
+ })
202
+ case 'eps':
203
+ return createPaymentMethod({
204
+ paymentMethodType: 'Eps',
205
+ paymentMethodData: { billingDetails: details },
206
+ })
207
+ case 'p24':
208
+ return createPaymentMethod({
209
+ paymentMethodType: 'P24',
210
+ paymentMethodData: { billingDetails: details },
211
+ })
212
+ case 'alipay':
213
+ return createPaymentMethod({ paymentMethodType: 'Alipay' })
214
+ case 'grabpay':
215
+ return createPaymentMethod({
216
+ paymentMethodType: 'GrabPay',
217
+ paymentMethodData: { billingDetails: details },
218
+ })
219
+ case 'cashapp':
220
+ return createPaymentMethod({
221
+ paymentMethodType: 'CashApp',
222
+ paymentMethodData: { billingDetails: details },
223
+ })
224
+ case 'paypal':
225
+ return createPaymentMethod({
226
+ paymentMethodType: 'PayPal',
227
+ paymentMethodData: { billingDetails: details },
228
+ })
229
+ default:
230
+ throw new Error('This payment method is not available on this device.')
231
+ }
232
+ })()
233
+ if (result.error || !result.paymentMethod?.id)
234
+ throw failed(result.error?.message, 'Unable to start this payment method.')
235
+ return result.paymentMethod.id
236
+ },
237
+
146
238
  handleNextAction: async (clientSecret) => {
147
239
  const { error } = await handleNextAction(clientSecret)
148
- if (error) throw failed(error.message, 'Payment failed')
240
+ if (error) throw stripeFailure(error, 'Payment failed')
149
241
  },
150
242
 
151
243
  confirmOxxo: async (clientSecret, billing) => {
@@ -175,6 +267,6 @@ export const createRnPorts = (): Omit<CheckoutPorts, 'confirmer'> => ({
175
267
  paymentMethodType: 'Card',
176
268
  paymentMethodData: { paymentMethodId },
177
269
  })
178
- if (error) throw failed(error.message, 'Payment failed')
270
+ if (error) throw stripeFailure(error, 'Payment failed')
179
271
  },
180
272
  })