@tagadapay/plugin-sdk 3.1.12 → 3.1.24

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 (144) hide show
  1. package/build-cdn.js +397 -11
  2. package/dist/data/iso3166.d.ts +23 -33
  3. package/dist/data/iso3166.js +134 -198
  4. package/dist/data/languages.d.ts +5 -64
  5. package/dist/data/languages.js +23 -143
  6. package/dist/external-tracker.js +623 -3426
  7. package/dist/external-tracker.min.js +2 -25
  8. package/dist/external-tracker.min.js.map +4 -4
  9. package/dist/react/config/payment.d.ts +14 -4
  10. package/dist/react/config/payment.js +47 -9
  11. package/dist/react/hooks/useCheckout.d.ts +3 -0
  12. package/dist/react/hooks/useCheckout.js +4 -1
  13. package/dist/react/hooks/useISOData.js +1 -1
  14. package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
  15. package/dist/react/hooks/usePluginConfig.js +9 -10
  16. package/dist/react/providers/TagadaProvider.js +1 -1
  17. package/dist/tagada-react-sdk-minimal.min.js +36 -0
  18. package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
  19. package/dist/tagada-react-sdk.js +37821 -0
  20. package/dist/tagada-react-sdk.min.js +78 -0
  21. package/dist/tagada-react-sdk.min.js.map +7 -0
  22. package/dist/tagada-sdk.js +16044 -0
  23. package/dist/tagada-sdk.min.js +32 -0
  24. package/dist/tagada-sdk.min.js.map +7 -0
  25. package/dist/v2/cdn-react-minimal.d.ts +23 -0
  26. package/dist/v2/cdn-react-minimal.js +26 -0
  27. package/dist/v2/core/client.d.ts +4 -2
  28. package/dist/v2/core/client.js +5 -4
  29. package/dist/v2/core/config/environment.js +2 -1
  30. package/dist/v2/core/errors.d.ts +75 -0
  31. package/dist/v2/core/errors.js +104 -0
  32. package/dist/v2/core/funnelClient.d.ts +100 -10
  33. package/dist/v2/core/funnelClient.js +121 -27
  34. package/dist/v2/core/isoData.d.ts +4 -4
  35. package/dist/v2/core/isoData.js +7 -7
  36. package/dist/v2/core/pixelMapping.d.ts +49 -0
  37. package/dist/v2/core/pixelMapping.js +363 -0
  38. package/dist/v2/core/resources/apiClient.d.ts +2 -0
  39. package/dist/v2/core/resources/apiClient.js +52 -9
  40. package/dist/v2/core/resources/checkout.d.ts +99 -30
  41. package/dist/v2/core/resources/checkout.js +14 -0
  42. package/dist/v2/core/resources/customer.d.ts +20 -19
  43. package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
  44. package/dist/v2/core/resources/funnel.d.ts +17 -17
  45. package/dist/v2/core/resources/payments.d.ts +89 -13
  46. package/dist/v2/core/resources/payments.js +27 -9
  47. package/dist/v2/core/resources/postPurchases.d.ts +17 -0
  48. package/dist/v2/core/resources/postPurchases.js +20 -0
  49. package/dist/v2/core/types.d.ts +50 -12
  50. package/dist/v2/core/types.js +0 -3
  51. package/dist/v2/core/utils/checkout.d.ts +2 -2
  52. package/dist/v2/core/utils/checkout.js +7 -2
  53. package/dist/v2/core/utils/currency.d.ts +14 -0
  54. package/dist/v2/core/utils/currency.js +40 -0
  55. package/dist/v2/core/utils/deviceInfo.d.ts +0 -10
  56. package/dist/v2/core/utils/deviceInfo.js +152 -76
  57. package/dist/v2/core/utils/index.d.ts +1 -0
  58. package/dist/v2/core/utils/index.js +2 -0
  59. package/dist/v2/core/utils/order.d.ts +13 -9
  60. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  61. package/dist/v2/core/utils/pluginConfig.js +36 -12
  62. package/dist/v2/index.d.ts +6 -3
  63. package/dist/v2/index.js +4 -2
  64. package/dist/v2/react/components/FunnelScriptInjector.js +166 -77
  65. package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
  66. package/dist/v2/react/components/StripeExpressButton.js +171 -0
  67. package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
  68. package/dist/v2/react/components/WhopCheckout.js +237 -0
  69. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
  70. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
  71. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
  72. package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
  73. package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
  74. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
  75. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
  76. package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
  77. package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
  78. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
  79. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
  80. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
  81. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
  82. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
  83. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +31 -0
  84. package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
  85. package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
  86. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
  87. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
  88. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
  89. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
  90. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
  91. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
  92. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
  93. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
  94. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
  95. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
  96. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
  97. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
  98. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
  99. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
  100. package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
  101. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +16 -0
  102. package/dist/v2/react/hooks/useCheckoutQuery.js +63 -10
  103. package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
  104. package/dist/v2/react/hooks/useFunnel.js +8 -4
  105. package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
  106. package/dist/v2/react/hooks/useGeoLocation.js +4 -2
  107. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
  108. package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
  109. package/dist/v2/react/hooks/useISOData.d.ts +2 -5
  110. package/dist/v2/react/hooks/useISOData.js +26 -27
  111. package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
  112. package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
  113. package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
  114. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
  115. package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
  116. package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
  117. package/dist/v2/react/hooks/usePixelTracking.js +283 -504
  118. package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
  119. package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
  120. package/dist/v2/react/hooks/useRemappableParams.js +23 -23
  121. package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
  122. package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
  123. package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
  124. package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
  125. package/dist/v2/react/hooks/useStepConfig.js +14 -7
  126. package/dist/v2/react/hooks/useTranslation.js +23 -8
  127. package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
  128. package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
  129. package/dist/v2/react/index.d.ts +15 -1
  130. package/dist/v2/react/index.js +7 -0
  131. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +3 -1
  132. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +12 -2
  133. package/dist/v2/react/providers/TagadaProvider.js +74 -5
  134. package/dist/v2/standalone/external-tracker.d.ts +52 -46
  135. package/dist/v2/standalone/external-tracker.js +205 -98
  136. package/dist/v2/standalone/index.d.ts +40 -0
  137. package/dist/v2/standalone/index.js +148 -1
  138. package/dist/v2/standalone/payment-service.d.ts +134 -0
  139. package/dist/v2/standalone/payment-service.js +928 -0
  140. package/package.json +6 -4
  141. package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
  142. package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
  143. package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
  144. package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
@@ -1,6 +1,100 @@
1
1
  'use client';
2
2
  import { useCallback, useEffect, useMemo, useRef } from 'react';
3
3
  import { getAssignedStepConfig } from '../../core';
4
+ /**
5
+ * Parse script content that may contain multiple <script> and <noscript> tags.
6
+ * Handles: external scripts (<script src="...">), inline scripts, noscript blocks,
7
+ * and bare JS without any HTML tags.
8
+ */
9
+ function parseScriptContent(content) {
10
+ const trimmed = content.trim();
11
+ // If content doesn't contain any HTML script/noscript tags, treat as raw JS
12
+ if (!/<(?:script|noscript)[\s>]/i.test(trimmed)) {
13
+ return trimmed ? [{ type: 'inline', code: trimmed }] : [];
14
+ }
15
+ const elements = [];
16
+ const tagRegex = /<(script|noscript)([^>]*)>([\s\S]*?)<\/\1>/gi;
17
+ let lastIndex = 0;
18
+ let match;
19
+ while ((match = tagRegex.exec(trimmed)) !== null) {
20
+ // Capture any bare text between tags (could be JS or HTML comments)
21
+ const between = trimmed.slice(lastIndex, match.index).trim();
22
+ // Skip HTML comments and empty strings
23
+ if (between && !/^<!--[\s\S]*?-->$/.test(between)) {
24
+ elements.push({ type: 'inline', code: between });
25
+ }
26
+ const [, tagName, attrs, body] = match;
27
+ if (tagName.toLowerCase() === 'noscript') {
28
+ if (body.trim()) {
29
+ elements.push({ type: 'noscript', html: body.trim() });
30
+ }
31
+ }
32
+ else {
33
+ // Script tag — check for src attribute (external script)
34
+ const srcMatch = attrs.match(/src=["']([^"']+)["']/i);
35
+ if (srcMatch) {
36
+ elements.push({
37
+ type: 'external',
38
+ src: srcMatch[1],
39
+ async: /\basync\b/i.test(attrs),
40
+ defer: /\bdefer\b/i.test(attrs),
41
+ });
42
+ }
43
+ // Inline content (can exist alongside src, though unusual)
44
+ if (body.trim()) {
45
+ elements.push({ type: 'inline', code: body.trim() });
46
+ }
47
+ }
48
+ lastIndex = match.index + match[0].length;
49
+ }
50
+ // Trailing content after last tag
51
+ const trailing = trimmed.slice(lastIndex).trim();
52
+ if (trailing && !/^<!--[\s\S]*?-->$/.test(trailing)) {
53
+ elements.push({ type: 'inline', code: trailing });
54
+ }
55
+ return elements;
56
+ }
57
+ /** Wrap inline JS in an error-handling IIFE */
58
+ function wrapInErrorHandler(scriptName, code) {
59
+ return `
60
+ (function() {
61
+ try {
62
+ // Script: ${scriptName}
63
+ ${code}
64
+ } catch (error) {
65
+ console.error('[TagadaPay] StepConfig script "${scriptName}" error:', error);
66
+ }
67
+ })();
68
+ `;
69
+ }
70
+ /** Inject a DOM element at the specified position */
71
+ function injectAtPosition(element, position) {
72
+ switch (position) {
73
+ case 'head-start':
74
+ if (document.head.firstChild) {
75
+ document.head.insertBefore(element, document.head.firstChild);
76
+ }
77
+ else {
78
+ document.head.appendChild(element);
79
+ }
80
+ break;
81
+ case 'head-end':
82
+ document.head.appendChild(element);
83
+ break;
84
+ case 'body-start':
85
+ if (document.body.firstChild) {
86
+ document.body.insertBefore(element, document.body.firstChild);
87
+ }
88
+ else {
89
+ document.body.appendChild(element);
90
+ }
91
+ break;
92
+ case 'body-end':
93
+ default:
94
+ document.body.appendChild(element);
95
+ break;
96
+ }
97
+ }
4
98
  /**
5
99
  * FunnelScriptInjector - Handles injection of funnel scripts into the page.
6
100
  *
@@ -143,9 +237,33 @@ export function FunnelScriptInjector({ context, isInitialized }) {
143
237
  observer.disconnect();
144
238
  }, timeout);
145
239
  },
240
+ waitForElements: (selector, callback, timeout = 10000) => {
241
+ const elements = document.querySelectorAll(selector);
242
+ if (elements.length > 0) {
243
+ callback(elements);
244
+ return;
245
+ }
246
+ const observer = new MutationObserver(() => {
247
+ const elements = document.querySelectorAll(selector);
248
+ if (elements.length > 0) {
249
+ observer.disconnect();
250
+ callback(elements);
251
+ }
252
+ });
253
+ observer.observe(document.body, {
254
+ childList: true,
255
+ subtree: true,
256
+ });
257
+ setTimeout(() => {
258
+ observer.disconnect();
259
+ }, timeout);
260
+ },
146
261
  pageType: context?.currentStepId || null,
147
262
  isInitialized: isInitialized,
148
263
  ressources: context?.resources || null,
264
+ // Convenience getters so scripts can use Tagada.order / Tagada.checkout
265
+ get order() { return this.ressources?.order || null; },
266
+ get checkout() { return this.ressources?.checkout || null; },
149
267
  funnel: context
150
268
  ? {
151
269
  sessionId: context.sessionId,
@@ -199,28 +317,18 @@ export function FunnelScriptInjector({ context, isInitialized }) {
199
317
  existingScript.remove();
200
318
  }
201
319
  // Wrap script content with error handling and context checks
202
- const wrappedScript = `
203
- (function() {
204
- try {
205
- // Check if we have basic DOM access
206
- if (typeof document === 'undefined') {
207
- console.error('[TagadaPay] Document not available');
208
- return;
209
- }
210
-
211
- // Check if we have Tagada
212
- if (!window.Tagada) {
213
- console.error('[TagadaPay] Tagada not available');
214
- return;
215
- }
216
-
217
- // Execute the original script
218
- ${scriptBody}
219
- } catch (error) {
220
- console.error('[TagadaPay] Script execution error:', error);
221
- }
222
- })();
223
- `;
320
+ // NOTE: We use indirect eval (0,eval)() so that SyntaxErrors in user scripts
321
+ // are thrown at runtime (catchable by try/catch) rather than at parse time (uncatchable).
322
+ // JSON.stringify safely escapes the script content (backticks, quotes, ${...}, etc.)
323
+ const wrappedScript = '(function() {\n' +
324
+ ' try {\n' +
325
+ ' if (typeof document === "undefined") { console.error("[TagadaPay] Document not available"); return; }\n' +
326
+ ' if (!window.Tagada) { console.error("[TagadaPay] Tagada not available"); return; }\n' +
327
+ ' (0, eval)(' + JSON.stringify(scriptBody) + ');\n' +
328
+ ' } catch (error) {\n' +
329
+ ' console.error("[TagadaPay] Script execution error:", error);\n' +
330
+ ' }\n' +
331
+ '})();';
224
332
  // Create and inject new script element
225
333
  const scriptElement = document.createElement('script');
226
334
  scriptElement.id = scriptId;
@@ -263,62 +371,43 @@ export function FunnelScriptInjector({ context, isInitialized }) {
263
371
  // Inject each enabled script at its correct position
264
372
  stepConfigScripts.forEach((script, index) => {
265
373
  const position = script.position || 'head-end';
266
- const scriptId = `tagada-stepconfig-script-${index}`;
267
- // Extract script content (remove <script> tags if present)
268
- let scriptBody = script.content.trim();
269
- const scriptTagMatch = scriptBody.match(/^<script[^>]*>([\s\S]*)<\/script>$/i);
270
- if (scriptTagMatch) {
271
- scriptBody = scriptTagMatch[1].trim();
272
- }
273
- if (!scriptBody)
374
+ const content = script.content.trim();
375
+ if (!content)
274
376
  return;
275
- // Wrap script content with error handling
276
- const wrappedScript = `
277
- (function() {
278
- try {
279
- // Script: ${script.name}
280
- ${scriptBody}
281
- } catch (error) {
282
- console.error('[TagadaPay] StepConfig script "${script.name}" error:', error);
283
- }
284
- })();
285
- `;
286
- // Create script element
287
- const scriptElement = document.createElement('script');
288
- scriptElement.id = scriptId;
289
- scriptElement.setAttribute('data-tagada-stepconfig-script', 'true');
290
- scriptElement.setAttribute('data-script-name', script.name);
291
- scriptElement.textContent = wrappedScript;
292
- // Inject at the correct position
293
- switch (position) {
294
- case 'head-start':
295
- // Insert at the beginning of <head>
296
- if (document.head.firstChild) {
297
- document.head.insertBefore(scriptElement, document.head.firstChild);
298
- }
299
- else {
300
- document.head.appendChild(scriptElement);
301
- }
302
- break;
303
- case 'head-end':
304
- // Insert at the end of <head>
305
- document.head.appendChild(scriptElement);
306
- break;
307
- case 'body-start':
308
- // Insert at the beginning of <body>
309
- if (document.body.firstChild) {
310
- document.body.insertBefore(scriptElement, document.body.firstChild);
311
- }
312
- else {
313
- document.body.appendChild(scriptElement);
314
- }
315
- break;
316
- case 'body-end':
317
- default:
318
- // Insert at the end of <body>
319
- document.body.appendChild(scriptElement);
320
- break;
321
- }
377
+ // Parse content into individual elements (handles multi-tag scripts)
378
+ const parsed = parseScriptContent(content);
379
+ parsed.forEach((element, elemIndex) => {
380
+ const elemId = `tagada-stepconfig-script-${index}-${elemIndex}`;
381
+ if (element.type === 'external') {
382
+ const el = document.createElement('script');
383
+ el.id = elemId;
384
+ el.setAttribute('data-tagada-stepconfig-script', 'true');
385
+ el.setAttribute('data-script-name', script.name);
386
+ el.src = element.src;
387
+ if (element.async)
388
+ el.async = true;
389
+ if (element.defer)
390
+ el.defer = true;
391
+ injectAtPosition(el, position);
392
+ }
393
+ else if (element.type === 'inline') {
394
+ // Inline script wrap in error handler
395
+ const el = document.createElement('script');
396
+ el.id = elemId;
397
+ el.setAttribute('data-tagada-stepconfig-script', 'true');
398
+ el.setAttribute('data-script-name', script.name);
399
+ el.textContent = wrapInErrorHandler(script.name, element.code);
400
+ injectAtPosition(el, position);
401
+ }
402
+ else if (element.type === 'noscript') {
403
+ // Noscript block — inject as <noscript> element
404
+ const el = document.createElement('noscript');
405
+ el.id = elemId;
406
+ el.setAttribute('data-tagada-stepconfig-script', 'true');
407
+ el.innerHTML = element.html;
408
+ injectAtPosition(el, position);
409
+ }
410
+ });
322
411
  });
323
412
  // Track injected scripts to prevent re-injection
324
413
  lastInjectedStepConfigScriptsRef.current = scriptsHash;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { CheckoutData } from '../../core/resources/checkout';
3
+ export interface StripeExpressButtonProps {
4
+ checkout: CheckoutData;
5
+ onSuccess?: (result: {
6
+ payment: any;
7
+ order: any;
8
+ }) => void;
9
+ onError?: (error: string) => void;
10
+ onCancel?: () => void;
11
+ }
12
+ export declare const StripeExpressButton: React.FC<StripeExpressButtonProps>;
13
+ export default StripeExpressButton;
@@ -0,0 +1,171 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Elements, ExpressCheckoutElement, useElements, useStripe } from '@stripe/react-stripe-js';
3
+ import { loadStripe } from '@stripe/stripe-js';
4
+ import { useMemo, useRef, useState } from 'react';
5
+ import { PaymentsResource } from '../../core/resources/payments';
6
+ import { useExpressPaymentMethods } from '../hooks/useExpressPaymentMethods';
7
+ import { usePaymentPolling } from '../hooks/usePaymentPolling';
8
+ import { getGlobalApiClient } from '../hooks/useApiQuery';
9
+ // Express method keys — drives processorGroup detection and ECE paymentMethods options.
10
+ // 'klarna_express' is the CRM config key for Klarna via ECE, distinct from 'klarna' (redirect flow).
11
+ // Backward compatible: existing configs with only apple_pay/google_pay continue to match.
12
+ const EXPRESS_METHOD_KEYS = ['apple_pay', 'google_pay', 'paypal', 'link', 'klarna_express'];
13
+ // Inner component — must be a child of <Elements> to use useStripe() and useElements()
14
+ function StripeExpressButtonInner({ checkout, processorId, enabledExpressMethods, onSuccess, onError, onCancel, }) {
15
+ const stripe = useStripe();
16
+ const elements = useElements();
17
+ const { handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail } = useExpressPaymentMethods();
18
+ const { startPolling } = usePaymentPolling();
19
+ const isProcessingRef = useRef(false);
20
+ const [visible, setVisible] = useState(false);
21
+ const paymentsResource = useMemo(() => new PaymentsResource(getGlobalApiClient()), []);
22
+ const onConfirm = async (event) => {
23
+ if (!stripe || !elements || isProcessingRef.current)
24
+ return;
25
+ isProcessingRef.current = true;
26
+ // Use the wallet type directly from Stripe's event — 'apple_pay', 'google_pay', 'paypal', etc.
27
+ const paymentMethod = event.expressPaymentType;
28
+ try {
29
+ // Step 1: save billing address + email from the wallet to the session/customer record.
30
+ // This mirrors what ApplePayButton and GooglePayButton do before processing payment,
31
+ // and ensures customer.email is present for the backend email validation.
32
+ const billing = event.billingDetails;
33
+ if (billing) {
34
+ const nameParts = (billing.name ?? '').trim().split(/\s+/);
35
+ const firstName = nameParts[0] ?? '';
36
+ const lastName = nameParts.slice(1).join(' ');
37
+ const billingAddress = {
38
+ firstName,
39
+ lastName,
40
+ address1: billing.address?.line1 ?? '',
41
+ address2: billing.address?.line2 ?? '',
42
+ city: billing.address?.city ?? '',
43
+ state: billing.address?.state ?? '',
44
+ country: billing.address?.country ?? '',
45
+ postal: billing.address?.postal_code ?? '',
46
+ phone: billing.phone ?? '',
47
+ email: billing.email ?? '',
48
+ };
49
+ await updateCheckoutSessionValues({
50
+ data: {
51
+ shippingAddress: billingAddress,
52
+ billingAddress,
53
+ },
54
+ });
55
+ }
56
+ if (billing?.email) {
57
+ await updateCustomerEmail({ data: { email: billing.email } });
58
+ }
59
+ // Step 2: validate the Elements form
60
+ const { error: submitError } = await elements.submit();
61
+ if (submitError) {
62
+ onError?.(submitError.message ?? 'Validation failed');
63
+ isProcessingRef.current = false;
64
+ return;
65
+ }
66
+ // Step 3: call backend via existing APM path
67
+ // paymentsResource.processPaymentDirect returns the raw PaymentResponse (no action routing)
68
+ const response = await paymentsResource.processPaymentDirect(checkout.checkoutSession.id, '', // empty — backend identifies via processorId + paymentMethod
69
+ undefined, {
70
+ processorId,
71
+ paymentMethod,
72
+ isExpress: true,
73
+ });
74
+ const clientSecret = response?.payment?.requireActionData?.metadata?.stripeExpressCheckout?.clientSecret;
75
+ if (!clientSecret) {
76
+ onError?.('Express checkout configuration missing — no client secret returned');
77
+ isProcessingRef.current = false;
78
+ return;
79
+ }
80
+ // Step 4: confirm the PaymentIntent inline — must happen while the Apple Pay sheet is open
81
+ const { error: confirmError } = await stripe.confirmPayment({
82
+ elements,
83
+ clientSecret,
84
+ confirmParams: {
85
+ // redirect: 'if_required' avoids a full-page redirect for wallet payments
86
+ return_url: window.location.href,
87
+ },
88
+ redirect: 'if_required',
89
+ });
90
+ if (confirmError) {
91
+ onError?.(confirmError.message ?? 'Payment confirmation failed');
92
+ isProcessingRef.current = false;
93
+ return;
94
+ }
95
+ // Step 5: poll for the final status — the webhook (payment_intent.succeeded) updates the DB
96
+ const paymentId = response.payment.id;
97
+ startPolling(paymentId, {
98
+ onRequireAction: (payment, stop) => {
99
+ // The payment still has requireAction='stripe_express_checkout' from when we created
100
+ // the intent — this has already been handled inline by stripe.confirmPayment.
101
+ // Ignore it and let polling continue waiting for the webhook to mark it succeeded.
102
+ if (payment.requireActionData?.type === 'stripe_express_checkout')
103
+ return;
104
+ // Any other unexpected action type: stop and surface as error
105
+ stop();
106
+ isProcessingRef.current = false;
107
+ onError?.('Unexpected payment action required');
108
+ },
109
+ onSuccess: async (completedPayment) => {
110
+ isProcessingRef.current = false;
111
+ onSuccess?.({ payment: completedPayment, order: completedPayment.order });
112
+ },
113
+ onFailure: async (errorMsg) => {
114
+ isProcessingRef.current = false;
115
+ onError?.(errorMsg);
116
+ },
117
+ });
118
+ }
119
+ catch (err) {
120
+ isProcessingRef.current = false;
121
+ const msg = err instanceof Error ? err.message : 'Express checkout failed';
122
+ console.error('[StripeExpressButton] Payment error:', err);
123
+ onError?.(msg);
124
+ }
125
+ };
126
+ return (_jsx("div", { style: { visibility: visible ? 'visible' : 'hidden' }, children: _jsx(ExpressCheckoutElement, { onReady: ({ availablePaymentMethods }) => {
127
+ if (availablePaymentMethods) {
128
+ setVisible(true);
129
+ handleAddExpressId('stripe_express_checkout');
130
+ }
131
+ }, onConfirm: onConfirm, onCancel: () => {
132
+ isProcessingRef.current = false;
133
+ onCancel?.();
134
+ }, options: {
135
+ buttonType: {
136
+ applePay: 'buy',
137
+ googlePay: 'buy',
138
+ },
139
+ buttonHeight: 48,
140
+ paymentMethods: {
141
+ applePay: enabledExpressMethods.includes('apple_pay') ? 'always' : 'never',
142
+ googlePay: enabledExpressMethods.includes('google_pay') ? 'always' : 'never',
143
+ link: enabledExpressMethods.includes('link') ? 'auto' : 'never',
144
+ paypal: enabledExpressMethods.includes('paypal') ? 'auto' : 'never',
145
+ klarna: enabledExpressMethods.includes('klarna_express') ? 'auto' : 'never',
146
+ },
147
+ emailRequired: true,
148
+ billingAddressRequired: true,
149
+ } }) }));
150
+ }
151
+ // Outer component — resolves Stripe instance and provides <Elements> context
152
+ export const StripeExpressButton = (props) => {
153
+ const { stripeExpressPaymentMethod } = useExpressPaymentMethods();
154
+ const processorGroup = stripeExpressPaymentMethod?.settings?.processors?.find((g) => EXPRESS_METHOD_KEYS.some((key) => g?.methods?.[key]?.enabled === true));
155
+ const processorId = processorGroup?.processorId;
156
+ const publishableKey = stripeExpressPaymentMethod?.settings?.publishableKey;
157
+ // Derive which express methods are enabled in this processor group
158
+ const enabledExpressMethods = EXPRESS_METHOD_KEYS.filter((key) => processorGroup?.methods?.[key]?.enabled === true);
159
+ const stripePromise = useMemo(() => (publishableKey ? loadStripe(publishableKey) : null), [publishableKey]);
160
+ if (!stripeExpressPaymentMethod || !processorId || !stripePromise)
161
+ return null;
162
+ const amount = Math.round(props.checkout.summary?.totalAdjustedAmount ?? 0);
163
+ const currency = (props.checkout.summary?.currency || 'usd').toLowerCase();
164
+ const elementsOptions = {
165
+ mode: 'payment',
166
+ amount,
167
+ currency,
168
+ };
169
+ return (_jsx(Elements, { stripe: stripePromise, options: elementsOptions, children: _jsx(StripeExpressButtonInner, { ...props, processorId: processorId, enabledExpressMethods: enabledExpressMethods }) }));
170
+ };
171
+ export default StripeExpressButton;
@@ -0,0 +1,24 @@
1
+ import type { WhopPayment } from '../hooks/useWhopPaymentPolling';
2
+ export interface WhopCheckoutHandle {
3
+ submit: () => Promise<void>;
4
+ }
5
+ export interface WhopCheckoutProps {
6
+ checkoutSessionId: string;
7
+ storeId: string;
8
+ customerEmail?: string;
9
+ shippingAddress?: {
10
+ firstName?: string;
11
+ lastName?: string;
12
+ country?: string;
13
+ address1?: string;
14
+ city?: string;
15
+ state?: string;
16
+ postal?: string;
17
+ };
18
+ orderItemsCount?: number;
19
+ orderTotalAmount?: number;
20
+ onPaymentCompleted?: (payment: WhopPayment) => void;
21
+ onPaymentFailed?: (error: string) => void;
22
+ onReady?: () => void;
23
+ }
24
+ export declare const WhopCheckout: import("react").NamedExoticComponent<WhopCheckoutProps & import("react").RefAttributes<WhopCheckoutHandle>>;