@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.
- package/build-cdn.js +397 -11
- package/dist/data/iso3166.d.ts +23 -33
- package/dist/data/iso3166.js +134 -198
- package/dist/data/languages.d.ts +5 -64
- package/dist/data/languages.js +23 -143
- package/dist/external-tracker.js +623 -3426
- package/dist/external-tracker.min.js +2 -25
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +14 -4
- package/dist/react/config/payment.js +47 -9
- package/dist/react/hooks/useCheckout.d.ts +3 -0
- package/dist/react/hooks/useCheckout.js +4 -1
- package/dist/react/hooks/useISOData.js +1 -1
- package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/react/hooks/usePluginConfig.js +9 -10
- package/dist/react/providers/TagadaProvider.js +1 -1
- package/dist/tagada-react-sdk-minimal.min.js +36 -0
- package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
- package/dist/tagada-react-sdk.js +37821 -0
- package/dist/tagada-react-sdk.min.js +78 -0
- package/dist/tagada-react-sdk.min.js.map +7 -0
- package/dist/tagada-sdk.js +16044 -0
- package/dist/tagada-sdk.min.js +32 -0
- package/dist/tagada-sdk.min.js.map +7 -0
- package/dist/v2/cdn-react-minimal.d.ts +23 -0
- package/dist/v2/cdn-react-minimal.js +26 -0
- package/dist/v2/core/client.d.ts +4 -2
- package/dist/v2/core/client.js +5 -4
- package/dist/v2/core/config/environment.js +2 -1
- package/dist/v2/core/errors.d.ts +75 -0
- package/dist/v2/core/errors.js +104 -0
- package/dist/v2/core/funnelClient.d.ts +100 -10
- package/dist/v2/core/funnelClient.js +121 -27
- package/dist/v2/core/isoData.d.ts +4 -4
- package/dist/v2/core/isoData.js +7 -7
- package/dist/v2/core/pixelMapping.d.ts +49 -0
- package/dist/v2/core/pixelMapping.js +363 -0
- package/dist/v2/core/resources/apiClient.d.ts +2 -0
- package/dist/v2/core/resources/apiClient.js +52 -9
- package/dist/v2/core/resources/checkout.d.ts +99 -30
- package/dist/v2/core/resources/checkout.js +14 -0
- package/dist/v2/core/resources/customer.d.ts +20 -19
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
- package/dist/v2/core/resources/funnel.d.ts +17 -17
- package/dist/v2/core/resources/payments.d.ts +89 -13
- package/dist/v2/core/resources/payments.js +27 -9
- package/dist/v2/core/resources/postPurchases.d.ts +17 -0
- package/dist/v2/core/resources/postPurchases.js +20 -0
- package/dist/v2/core/types.d.ts +50 -12
- package/dist/v2/core/types.js +0 -3
- package/dist/v2/core/utils/checkout.d.ts +2 -2
- package/dist/v2/core/utils/checkout.js +7 -2
- package/dist/v2/core/utils/currency.d.ts +14 -0
- package/dist/v2/core/utils/currency.js +40 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +0 -10
- package/dist/v2/core/utils/deviceInfo.js +152 -76
- package/dist/v2/core/utils/index.d.ts +1 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/order.d.ts +13 -9
- package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
- package/dist/v2/core/utils/pluginConfig.js +36 -12
- package/dist/v2/index.d.ts +6 -3
- package/dist/v2/index.js +4 -2
- package/dist/v2/react/components/FunnelScriptInjector.js +166 -77
- package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
- package/dist/v2/react/components/StripeExpressButton.js +171 -0
- package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
- package/dist/v2/react/components/WhopCheckout.js +237 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +31 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
- package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +16 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +63 -10
- package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
- package/dist/v2/react/hooks/useFunnel.js +8 -4
- package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
- package/dist/v2/react/hooks/useGeoLocation.js +4 -2
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
- package/dist/v2/react/hooks/useISOData.d.ts +2 -5
- package/dist/v2/react/hooks/useISOData.js +26 -27
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
- package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
- package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
- package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
- package/dist/v2/react/hooks/usePixelTracking.js +283 -504
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
- package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
- package/dist/v2/react/hooks/useRemappableParams.js +23 -23
- package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
- package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
- package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
- package/dist/v2/react/hooks/useStepConfig.js +14 -7
- package/dist/v2/react/hooks/useTranslation.js +23 -8
- package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
- package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
- package/dist/v2/react/index.d.ts +15 -1
- package/dist/v2/react/index.js +7 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +3 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +12 -2
- package/dist/v2/react/providers/TagadaProvider.js +74 -5
- package/dist/v2/standalone/external-tracker.d.ts +52 -46
- package/dist/v2/standalone/external-tracker.js +205 -98
- package/dist/v2/standalone/index.d.ts +40 -0
- package/dist/v2/standalone/index.js +148 -1
- package/dist/v2/standalone/payment-service.d.ts +134 -0
- package/dist/v2/standalone/payment-service.js +928 -0
- package/package.json +6 -4
- package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
- package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
- package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
- 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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
console.error(
|
|
208
|
-
return;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
267
|
-
|
|
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
|
-
//
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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>>;
|