@tagadapay/plugin-sdk 3.1.22 → 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 +274 -6
- package/dist/external-tracker.js +236 -3906
- 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/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 +10309 -6331
- package/dist/tagada-sdk.min.js +4 -22
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/cdn-react-minimal.d.ts +23 -0
- package/dist/v2/cdn-react-minimal.js +26 -0
- package/dist/v2/core/client.js +1 -1
- package/dist/v2/core/config/environment.js +2 -1
- package/dist/v2/core/funnelClient.d.ts +98 -10
- package/dist/v2/core/funnelClient.js +121 -27
- package/dist/v2/core/index.d.ts +0 -1
- package/dist/v2/core/index.js +0 -2
- package/dist/v2/core/isoData.d.ts +4 -4
- package/dist/v2/core/isoData.js +7 -7
- package/dist/v2/core/pixelMapping.js +64 -26
- package/dist/v2/core/resources/checkout.d.ts +10 -0
- package/dist/v2/core/resources/checkout.js +6 -0
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
- package/dist/v2/core/resources/payments.d.ts +7 -2
- package/dist/v2/core/resources/payments.js +1 -0
- package/dist/v2/core/resources/postPurchases.d.ts +17 -0
- package/dist/v2/core/resources/postPurchases.js +20 -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/order.d.ts +2 -0
- package/dist/v2/core/utils/pluginConfig.js +18 -22
- package/dist/v2/index.d.ts +4 -3
- package/dist/v2/index.js +4 -2
- package/dist/v2/react/components/FunnelScriptInjector.js +145 -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.js +7 -1
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +21 -3
- package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +10 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +21 -13
- package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
- package/dist/v2/react/hooks/useFunnel.js +8 -4
- 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 +25 -26
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +2 -2
- package/dist/v2/react/hooks/usePixelTracking.js +151 -70
- 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/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/index.d.ts +8 -1
- package/dist/v2/react/index.js +3 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +9 -1
- package/dist/v2/react/providers/TagadaProvider.js +4 -4
- package/dist/v2/standalone/index.d.ts +21 -3
- package/dist/v2/standalone/index.js +25 -3
- package/dist/v2/standalone/payment-service.d.ts +134 -0
- package/dist/v2/standalone/payment-service.js +928 -0
- package/package.json +4 -2
package/dist/v2/index.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export * from './core/utils/pluginConfig';
|
|
|
13
13
|
export * from './core/utils/previewMode';
|
|
14
14
|
export { injectPreviewModeIndicator, isIndicatorInjected, removePreviewModeIndicator } from './core/utils/previewModeIndicator';
|
|
15
15
|
export * from './core/utils/products';
|
|
16
|
-
export { getAssignedPaymentFlowId, getAssignedScripts, getAssignedStaticResources, getAssignedStepConfig, getLocalFunnelConfig, loadLocalFunnelConfig } from './core/funnelClient';
|
|
17
|
-
export type { LocalFunnelConfig, PixelsConfig, RuntimeStepConfig } from './core/funnelClient';
|
|
16
|
+
export { getAssignedPaymentFlowId, getAssignedScripts, getAssignedStaticResources, getAssignedResources, getAssignedStepConfig, getAssignedOrderBumpOfferIds, getAssignedUpsellOfferIds, getLocalFunnelConfig, getEnabledMethods, getExpressMethods, getExpressMethodsByProcessor, findMethod, isMethodEnabled, loadLocalFunnelConfig } from './core/funnelClient';
|
|
17
|
+
export type { LocalFunnelConfig, PaymentMethodConfig, PaymentSetupConfig, PaymentSetupMethod, PixelsConfig, RuntimeStepConfig } from './core/funnelClient';
|
|
18
18
|
export * from './core/pathRemapping';
|
|
19
19
|
export type { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutSessionPreview, Promotion } from './core/resources/checkout';
|
|
20
20
|
export type { Order, OrderLineItem } from './core/utils/order';
|
|
@@ -28,8 +28,9 @@ export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './co
|
|
|
28
28
|
export type { StoreConfig } from './core/resources/storeConfig';
|
|
29
29
|
export { FunnelActionType } from './core/resources/funnel';
|
|
30
30
|
export type { BackNavigationActionData, CartUpdatedActionData, DirectNavigationActionData, FormSubmitActionData, FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelAction as FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, NextAction, OfferAcceptedActionData, OfferDeclinedActionData, PaymentFailedActionData, PaymentSuccessActionData, SimpleFunnelContext } from './core/resources/funnel';
|
|
31
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
31
|
+
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
32
32
|
export type { DebugScript } from './react';
|
|
33
|
+
export type { PaymentMethodName } from './react';
|
|
33
34
|
export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react/hooks/useTranslation';
|
|
34
35
|
export type { FunnelContextValue } from './react/hooks/useFunnel';
|
|
35
36
|
export type { UseApplePayCheckoutOptions } from './react/hooks/useApplePayCheckout';
|
package/dist/v2/index.js
CHANGED
|
@@ -15,11 +15,13 @@ export * from './core/utils/previewMode';
|
|
|
15
15
|
export { injectPreviewModeIndicator, isIndicatorInjected, removePreviewModeIndicator } from './core/utils/previewModeIndicator';
|
|
16
16
|
export * from './core/utils/products';
|
|
17
17
|
// Step config utilities (for reading runtime configuration from HTML)
|
|
18
|
-
export { getAssignedPaymentFlowId, getAssignedScripts, getAssignedStaticResources, getAssignedStepConfig, getLocalFunnelConfig,
|
|
18
|
+
export { getAssignedPaymentFlowId, getAssignedScripts, getAssignedStaticResources, getAssignedResources, getAssignedStepConfig, getAssignedOrderBumpOfferIds, getAssignedUpsellOfferIds, getLocalFunnelConfig,
|
|
19
|
+
// Payment setup config helpers
|
|
20
|
+
getEnabledMethods, getExpressMethods, getExpressMethodsByProcessor, findMethod, isMethodEnabled,
|
|
19
21
|
// Local development helpers
|
|
20
22
|
loadLocalFunnelConfig } from './core/funnelClient';
|
|
21
23
|
// Path remapping helpers (framework-agnostic)
|
|
22
24
|
export * from './core/pathRemapping';
|
|
23
25
|
export { FunnelActionType } from './core/resources/funnel';
|
|
24
26
|
// React exports (hooks and components only, types are exported above)
|
|
25
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
27
|
+
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
@@ -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
|
*
|
|
@@ -167,6 +261,9 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
167
261
|
pageType: context?.currentStepId || null,
|
|
168
262
|
isInitialized: isInitialized,
|
|
169
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; },
|
|
170
267
|
funnel: context
|
|
171
268
|
? {
|
|
172
269
|
sessionId: context.sessionId,
|
|
@@ -220,28 +317,18 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
220
317
|
existingScript.remove();
|
|
221
318
|
}
|
|
222
319
|
// Wrap script content with error handling and context checks
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.error(
|
|
229
|
-
return;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Execute the original script
|
|
239
|
-
${scriptBody}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error('[TagadaPay] Script execution error:', error);
|
|
242
|
-
}
|
|
243
|
-
})();
|
|
244
|
-
`;
|
|
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
|
+
'})();';
|
|
245
332
|
// Create and inject new script element
|
|
246
333
|
const scriptElement = document.createElement('script');
|
|
247
334
|
scriptElement.id = scriptId;
|
|
@@ -284,62 +371,43 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
284
371
|
// Inject each enabled script at its correct position
|
|
285
372
|
stepConfigScripts.forEach((script, index) => {
|
|
286
373
|
const position = script.position || 'head-end';
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
let scriptBody = script.content.trim();
|
|
290
|
-
const scriptTagMatch = scriptBody.match(/^<script[^>]*>([\s\S]*)<\/script>$/i);
|
|
291
|
-
if (scriptTagMatch) {
|
|
292
|
-
scriptBody = scriptTagMatch[1].trim();
|
|
293
|
-
}
|
|
294
|
-
if (!scriptBody)
|
|
374
|
+
const content = script.content.trim();
|
|
375
|
+
if (!content)
|
|
295
376
|
return;
|
|
296
|
-
//
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (document.body.firstChild) {
|
|
331
|
-
document.body.insertBefore(scriptElement, document.body.firstChild);
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
document.body.appendChild(scriptElement);
|
|
335
|
-
}
|
|
336
|
-
break;
|
|
337
|
-
case 'body-end':
|
|
338
|
-
default:
|
|
339
|
-
// Insert at the end of <body>
|
|
340
|
-
document.body.appendChild(scriptElement);
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
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
|
+
});
|
|
343
411
|
});
|
|
344
412
|
// Track injected scripts to prevent re-injection
|
|
345
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;
|
|
@@ -128,9 +128,15 @@ export const WhopCheckout = memo(forwardRef(({ checkoutSessionId, storeId, custo
|
|
|
128
128
|
isWhopPaymentSubmitted.current = true;
|
|
129
129
|
setError(null);
|
|
130
130
|
try {
|
|
131
|
+
if (!planId) {
|
|
132
|
+
isWhopPaymentSubmitted.current = false;
|
|
133
|
+
setError('No plan available for payment.');
|
|
134
|
+
onPaymentFailed?.('No plan available for payment.');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
131
137
|
const result = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/whop-prepare-order`, {
|
|
132
138
|
method: 'POST',
|
|
133
|
-
body: JSON.stringify({ checkoutSessionId, storeId }),
|
|
139
|
+
body: JSON.stringify({ checkoutSessionId, storeId, planId }),
|
|
134
140
|
headers: { 'Content-Type': 'application/json' },
|
|
135
141
|
});
|
|
136
142
|
if (!result) {
|
|
@@ -4,9 +4,27 @@
|
|
|
4
4
|
import { useCallback } from 'react';
|
|
5
5
|
export function useProcessorAuthAction() {
|
|
6
6
|
const handleProcessorAuth = useCallback(async (actionData) => {
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
|
|
7
|
+
const redirect = actionData.metadata?.redirect;
|
|
8
|
+
if (!redirect?.redirectUrl)
|
|
9
|
+
return;
|
|
10
|
+
if (redirect.method === 'POST') {
|
|
11
|
+
const form = document.createElement('form');
|
|
12
|
+
form.method = 'POST';
|
|
13
|
+
form.action = redirect.redirectUrl;
|
|
14
|
+
if (redirect.data && typeof redirect.data === 'object') {
|
|
15
|
+
Object.entries(redirect.data).forEach(([key, value]) => {
|
|
16
|
+
const input = document.createElement('input');
|
|
17
|
+
input.type = 'hidden';
|
|
18
|
+
input.name = key;
|
|
19
|
+
input.value = String(value);
|
|
20
|
+
form.appendChild(input);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
document.body.appendChild(form);
|
|
24
|
+
form.submit();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
window.location.href = redirect.redirectUrl;
|
|
10
28
|
}
|
|
11
29
|
}, []);
|
|
12
30
|
return { handleProcessorAuth };
|
|
@@ -120,12 +120,17 @@ export function useApplePayCheckout({ checkout, onSuccess, onError, onCancel, })
|
|
|
120
120
|
setProcessingPayment(true);
|
|
121
121
|
// Tokenize payment
|
|
122
122
|
const applePayToken = await tokenizeApplePay(event);
|
|
123
|
-
// Complete Apple Pay sheet
|
|
123
|
+
// Complete Apple Pay sheet immediately after tokenization
|
|
124
|
+
// Apple Pay requires completePayment within ~30s of authorization
|
|
124
125
|
session.completePayment(window.ApplePaySession.STATUS_SUCCESS);
|
|
125
126
|
// Process payment via SDK hook
|
|
126
|
-
|
|
127
|
+
// onSuccess/redirect is gated through onPaymentSuccess callback only,
|
|
128
|
+
// so we never redirect until the backend confirms success
|
|
129
|
+
await processApplePayPayment(checkout.checkoutSession.id, applePayToken, {
|
|
127
130
|
onPaymentSuccess: (response) => {
|
|
128
|
-
|
|
131
|
+
if (onSuccess) {
|
|
132
|
+
onSuccess(response);
|
|
133
|
+
}
|
|
129
134
|
},
|
|
130
135
|
onPaymentFailed: (err) => {
|
|
131
136
|
setProcessingPayment(false);
|
|
@@ -135,14 +140,9 @@ export function useApplePayCheckout({ checkout, onSuccess, onError, onCancel, })
|
|
|
135
140
|
}
|
|
136
141
|
},
|
|
137
142
|
});
|
|
138
|
-
// Call success callback
|
|
139
|
-
if (onSuccess) {
|
|
140
|
-
onSuccess(result);
|
|
141
|
-
}
|
|
142
143
|
}
|
|
143
144
|
catch (error) {
|
|
144
145
|
console.error('Payment failed:', error);
|
|
145
|
-
session.completePayment(window.ApplePaySession.STATUS_FAILURE);
|
|
146
146
|
setProcessingPayment(false);
|
|
147
147
|
const errorMsg = error instanceof Error ? error.message : 'Payment failed';
|
|
148
148
|
setError(errorMsg);
|
|
@@ -3,9 +3,19 @@
|
|
|
3
3
|
* Replaces the coordinator pattern with automatic cache invalidation
|
|
4
4
|
*/
|
|
5
5
|
import { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSessionPreview } from '../../core/resources/checkout';
|
|
6
|
+
export interface CheckoutMessages {
|
|
7
|
+
/** Error shown when CMS session takes too long to initialize. Default: 'Session initialization timeout. Please refresh the page and try again.' */
|
|
8
|
+
sessionTimeout?: string;
|
|
9
|
+
/** Error shown when checkout resource fails to initialize. Default: 'Failed to initialize checkout resource' */
|
|
10
|
+
initFailed?: string;
|
|
11
|
+
/** Error shown when an operation is attempted without an active checkout session. Default: 'No checkout session available' */
|
|
12
|
+
noCheckoutSession?: string;
|
|
13
|
+
}
|
|
6
14
|
export interface UseCheckoutQueryOptions {
|
|
7
15
|
checkoutToken?: string;
|
|
8
16
|
enabled?: boolean;
|
|
17
|
+
/** Override default error messages (e.g. for i18n). Use with useTranslation's t() to pass translated strings. */
|
|
18
|
+
messages?: CheckoutMessages;
|
|
9
19
|
}
|
|
10
20
|
export interface UseCheckoutQueryResult {
|
|
11
21
|
checkout: CheckoutData | undefined;
|