@tagadapay/plugin-sdk 3.1.5 → 3.1.9

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 (71) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +220 -113
  3. package/dist/external-tracker.js +1225 -558
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/hooks/useApplePay.js +25 -36
  7. package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
  8. package/dist/react/providers/TagadaProvider.js +5 -5
  9. package/dist/react/utils/money.d.ts +4 -3
  10. package/dist/react/utils/money.js +39 -6
  11. package/dist/react/utils/trackingUtils.js +1 -0
  12. package/dist/tagada-sdk.js +10142 -0
  13. package/dist/tagada-sdk.min.js +43 -0
  14. package/dist/tagada-sdk.min.js.map +7 -0
  15. package/dist/v2/core/client.js +34 -2
  16. package/dist/v2/core/config/environment.js +9 -2
  17. package/dist/v2/core/funnelClient.d.ts +180 -2
  18. package/dist/v2/core/funnelClient.js +289 -6
  19. package/dist/v2/core/resources/apiClient.js +1 -1
  20. package/dist/v2/core/resources/checkout.d.ts +68 -0
  21. package/dist/v2/core/resources/funnel.d.ts +25 -0
  22. package/dist/v2/core/resources/payments.d.ts +70 -3
  23. package/dist/v2/core/resources/payments.js +72 -7
  24. package/dist/v2/core/utils/index.d.ts +1 -0
  25. package/dist/v2/core/utils/index.js +2 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  27. package/dist/v2/core/utils/pluginConfig.js +68 -5
  28. package/dist/v2/core/utils/previewMode.d.ts +7 -0
  29. package/dist/v2/core/utils/previewMode.js +72 -14
  30. package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
  31. package/dist/v2/core/utils/previewModeIndicator.js +414 -0
  32. package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
  33. package/dist/v2/core/utils/tokenStorage.js +15 -1
  34. package/dist/v2/index.d.ts +9 -3
  35. package/dist/v2/index.js +8 -3
  36. package/dist/v2/react/components/ApplePayButton.d.ts +22 -123
  37. package/dist/v2/react/components/ApplePayButton.js +247 -317
  38. package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
  39. package/dist/v2/react/components/FunnelScriptInjector.js +255 -162
  40. package/dist/v2/react/components/GooglePayButton.d.ts +2 -0
  41. package/dist/v2/react/components/GooglePayButton.js +80 -64
  42. package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
  43. package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
  44. package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
  45. package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
  46. package/dist/v2/react/hooks/useFunnel.d.ts +48 -6
  47. package/dist/v2/react/hooks/useFunnel.js +25 -5
  48. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +10 -0
  49. package/dist/v2/react/hooks/useGoogleAutocomplete.js +48 -0
  50. package/dist/v2/react/hooks/useGooglePayCheckout.d.ts +21 -0
  51. package/dist/v2/react/hooks/useGooglePayCheckout.js +198 -0
  52. package/dist/v2/react/hooks/usePaymentPolling.d.ts +15 -3
  53. package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
  54. package/dist/v2/react/hooks/usePaymentQuery.d.ts +34 -2
  55. package/dist/v2/react/hooks/usePaymentQuery.js +731 -7
  56. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
  57. package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
  58. package/dist/v2/react/hooks/usePixelTracking.d.ts +56 -0
  59. package/dist/v2/react/hooks/usePixelTracking.js +508 -0
  60. package/dist/v2/react/hooks/useStepConfig.d.ts +64 -0
  61. package/dist/v2/react/hooks/useStepConfig.js +53 -0
  62. package/dist/v2/react/index.d.ts +15 -5
  63. package/dist/v2/react/index.js +8 -2
  64. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
  65. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +41 -13
  66. package/dist/v2/react/providers/TagadaProvider.js +24 -23
  67. package/dist/v2/standalone/external-tracker.d.ts +2 -0
  68. package/dist/v2/standalone/external-tracker.js +6 -3
  69. package/package.json +112 -112
  70. package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
  71. package/dist/v2/react/hooks/useApplePay.js +0 -247
@@ -6,7 +6,9 @@ interface FunnelScriptInjectorProps extends FunnelState {
6
6
  *
7
7
  * This component:
8
8
  * - Sets up Tagada on the window object
9
- * - Injects and manages funnel scripts from the context
9
+ * - Injects scripts from stepConfig.scripts (NEW: from HTML injection, supports A/B variants)
10
+ * - Falls back to context.script (LEGACY: from funnel context)
11
+ * - Handles script positions: head-start, head-end, body-start, body-end
10
12
  * - Prevents duplicate script injection (handles React StrictMode)
11
13
  * - Cleans up scripts when context changes or component unmounts
12
14
  */
@@ -1,167 +1,169 @@
1
1
  'use client';
2
- import { useEffect, useRef } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import { getAssignedStepConfig } from '../../core';
3
4
  /**
4
5
  * FunnelScriptInjector - Handles injection of funnel scripts into the page.
5
6
  *
6
7
  * This component:
7
8
  * - Sets up Tagada on the window object
8
- * - Injects and manages funnel scripts from the context
9
+ * - Injects scripts from stepConfig.scripts (NEW: from HTML injection, supports A/B variants)
10
+ * - Falls back to context.script (LEGACY: from funnel context)
11
+ * - Handles script positions: head-start, head-end, body-start, body-end
9
12
  * - Prevents duplicate script injection (handles React StrictMode)
10
13
  * - Cleans up scripts when context changes or component unmounts
11
14
  */
12
15
  export function FunnelScriptInjector({ context, isInitialized }) {
13
- console.log('FunnelScriptInjector here', context, isInitialized); // Track last injected script to prevent duplicate execution
16
+ // Track last injected scripts to prevent duplicate execution
14
17
  const lastInjectedScriptRef = useRef(null);
15
- useEffect(() => {
16
- // Only run in browser environment
17
- if (typeof document === 'undefined') {
18
- return;
18
+ const lastInjectedStepConfigScriptsRef = useRef(null);
19
+ // Get stepConfig scripts from HTML injection or local config
20
+ // Re-compute when initialized (local config loads async, so we need to re-check)
21
+ const stepConfigScripts = useMemo(() => {
22
+ const stepConfig = getAssignedStepConfig();
23
+ const scripts = stepConfig?.scripts?.filter(s => s.enabled) || [];
24
+ if (scripts.length > 0) {
25
+ console.log('📜 [SDK] Found stepConfig scripts:', scripts.length, scripts.map(s => s.name));
19
26
  }
20
- // Set up Tagada for funnel scripts (similar to HtmlScript.tsx)
21
- const setupTagada = () => {
22
- // @ts-expect-error - Adding utilities to window
23
- if (window.Tagada) {
24
- // Update pageType if context is available
25
- if (context?.currentStepId) {
26
- // @ts-expect-error - Updating window property
27
- window.Tagada.pageType = context.currentStepId;
28
- }
29
- // Update isInitialized
30
- // @ts-expect-error - Updating window property
31
- window.Tagada.isInitialized = isInitialized;
32
- // Update ressources
27
+ return scripts;
28
+ }, [isInitialized]); // Re-compute when funnel initializes (local config should be loaded by then)
29
+ // Set up Tagada on window object - must be available before any scripts run
30
+ // Memoized to prevent unnecessary recreations across re-renders
31
+ const setupTagada = useCallback(() => {
32
+ if (typeof window === 'undefined')
33
+ return;
34
+ // @ts-expect-error - Adding utilities to window
35
+ if (window.Tagada) {
36
+ // Update properties if Tagada already exists
37
+ if (context?.currentStepId) {
33
38
  // @ts-expect-error - Updating window property
34
- window.Tagada.ressources = context?.resources || null;
35
- return; // Utils already exist, just update properties
39
+ window.Tagada.pageType = context.currentStepId;
36
40
  }
37
- // @ts-expect-error - Adding utilities to window
38
- window.Tagada = {
39
- // Wait for DOM to be ready
40
- ready: (callback) => {
41
- if (document.readyState === 'loading') {
42
- document.addEventListener('DOMContentLoaded', callback);
43
- }
44
- else {
41
+ // @ts-expect-error - Updating window property
42
+ window.Tagada.isInitialized = isInitialized;
43
+ // @ts-expect-error - Updating window property
44
+ window.Tagada.ressources = context?.resources || null;
45
+ return;
46
+ }
47
+ // @ts-expect-error - Adding utilities to window
48
+ window.Tagada = {
49
+ // Wait for DOM to be ready
50
+ ready: (callback) => {
51
+ if (document.readyState === 'loading') {
52
+ document.addEventListener('DOMContentLoaded', callback);
53
+ }
54
+ else {
55
+ callback();
56
+ }
57
+ },
58
+ // Wait for window to be fully loaded AND funnel to be initialized
59
+ loaded: (callback) => {
60
+ const checkBothConditions = () => {
61
+ const pageLoaded = document.readyState === 'complete';
62
+ // @ts-expect-error - Accessing window property
63
+ const funnelInitialized = window.Tagada?.isInitialized === true;
64
+ if (pageLoaded && funnelInitialized) {
45
65
  callback();
66
+ return true;
46
67
  }
47
- },
48
- // Wait for window to be fully loaded AND funnel to be initialized
49
- loaded: (callback) => {
50
- const checkBothConditions = () => {
51
- const pageLoaded = document.readyState === 'complete';
52
- // @ts-expect-error - Accessing window property
53
- const funnelInitialized = window.Tagada?.isInitialized === true;
54
- if (pageLoaded && funnelInitialized) {
55
- callback();
56
- return true;
57
- }
58
- return false;
59
- };
60
- // Check immediately
61
- if (checkBothConditions()) {
62
- return;
68
+ return false;
69
+ };
70
+ if (checkBothConditions()) {
71
+ return;
72
+ }
73
+ let loadListener = null;
74
+ let initCheckInterval = null;
75
+ let hasCalled = false;
76
+ const cleanup = () => {
77
+ if (loadListener) {
78
+ window.removeEventListener('load', loadListener);
79
+ loadListener = null;
63
80
  }
64
- // Set up listeners for both conditions
65
- let loadListener = null;
66
- let initCheckInterval = null;
67
- let hasCalled = false;
68
- const cleanup = () => {
69
- if (loadListener) {
70
- window.removeEventListener('load', loadListener);
71
- loadListener = null;
72
- }
73
- if (initCheckInterval) {
74
- clearInterval(initCheckInterval);
75
- initCheckInterval = null;
76
- }
77
- };
78
- // Listen for page load
79
- loadListener = () => {
80
- if (checkBothConditions() && !hasCalled) {
81
- hasCalled = true;
82
- cleanup();
83
- }
84
- };
85
- window.addEventListener('load', loadListener);
86
- // Poll for initialization status (in case page loads before initialization)
87
- initCheckInterval = setInterval(() => {
88
- if (checkBothConditions() && !hasCalled) {
89
- hasCalled = true;
90
- cleanup();
91
- }
92
- }, 100);
93
- // Timeout fallback (10 seconds max wait)
94
- setTimeout(() => {
95
- if (!hasCalled) {
96
- hasCalled = true;
97
- cleanup();
98
- // Call anyway if page is loaded (graceful degradation)
99
- if (document.readyState === 'complete') {
100
- callback();
101
- }
102
- }
103
- }, 10000);
104
- },
105
- // Execute with delay
106
- delay: (callback, ms = 1000) => {
107
- setTimeout(callback, ms);
108
- },
109
- // Retry until condition is met
110
- retry: (condition, callback, maxAttempts = 10, interval = 500) => {
111
- let attempts = 0;
112
- const check = () => {
113
- attempts++;
114
- if (condition() || attempts >= maxAttempts) {
81
+ if (initCheckInterval) {
82
+ clearInterval(initCheckInterval);
83
+ initCheckInterval = null;
84
+ }
85
+ };
86
+ loadListener = () => {
87
+ if (checkBothConditions() && !hasCalled) {
88
+ hasCalled = true;
89
+ cleanup();
90
+ }
91
+ };
92
+ window.addEventListener('load', loadListener);
93
+ initCheckInterval = setInterval(() => {
94
+ if (checkBothConditions() && !hasCalled) {
95
+ hasCalled = true;
96
+ cleanup();
97
+ }
98
+ }, 100);
99
+ setTimeout(() => {
100
+ if (!hasCalled) {
101
+ hasCalled = true;
102
+ cleanup();
103
+ if (document.readyState === 'complete') {
115
104
  callback();
116
105
  }
117
- else {
118
- setTimeout(check, interval);
119
- }
120
- };
121
- check();
122
- },
123
- // Wait for element to exist
124
- waitForElement: (selector, callback, timeout = 10000) => {
106
+ }
107
+ }, 10000);
108
+ },
109
+ delay: (callback, ms = 1000) => {
110
+ setTimeout(callback, ms);
111
+ },
112
+ retry: (condition, callback, maxAttempts = 10, interval = 500) => {
113
+ let attempts = 0;
114
+ const check = () => {
115
+ attempts++;
116
+ if (condition() || attempts >= maxAttempts) {
117
+ callback();
118
+ }
119
+ else {
120
+ setTimeout(check, interval);
121
+ }
122
+ };
123
+ check();
124
+ },
125
+ waitForElement: (selector, callback, timeout = 10000) => {
126
+ const element = document.querySelector(selector);
127
+ if (element) {
128
+ callback(element);
129
+ return;
130
+ }
131
+ const observer = new MutationObserver(() => {
125
132
  const element = document.querySelector(selector);
126
133
  if (element) {
127
- callback(element);
128
- return;
129
- }
130
- const observer = new MutationObserver(() => {
131
- const element = document.querySelector(selector);
132
- if (element) {
133
- observer.disconnect();
134
- callback(element);
135
- }
136
- });
137
- observer.observe(document.body, {
138
- childList: true,
139
- subtree: true,
140
- });
141
- // Timeout fallback
142
- setTimeout(() => {
143
134
  observer.disconnect();
144
- }, timeout);
145
- },
146
- // Page type helper (current step ID)
147
- pageType: context?.currentStepId || null,
148
- // Funnel initialization status
149
- isInitialized: isInitialized,
150
- // Expose resources directly (convenience access)
151
- ressources: context?.resources || null,
152
- // Expose funnel context data
153
- funnel: context
154
- ? {
155
- sessionId: context.sessionId,
156
- funnelId: context.funnelId,
157
- currentStepId: context.currentStepId,
158
- previousStepId: context.previousStepId,
159
- ressources: context.resources,
135
+ callback(element);
160
136
  }
161
- : null,
162
- };
137
+ });
138
+ observer.observe(document.body, {
139
+ childList: true,
140
+ subtree: true,
141
+ });
142
+ setTimeout(() => {
143
+ observer.disconnect();
144
+ }, timeout);
145
+ },
146
+ pageType: context?.currentStepId || null,
147
+ isInitialized: isInitialized,
148
+ ressources: context?.resources || null,
149
+ funnel: context
150
+ ? {
151
+ sessionId: context.sessionId,
152
+ funnelId: context.funnelId,
153
+ currentStepId: context.currentStepId,
154
+ previousStepId: context.previousStepId,
155
+ ressources: context.resources,
156
+ }
157
+ : null,
158
+ stepConfig: getAssignedStepConfig() || null,
163
159
  };
164
- // Set up utilities before injecting script
160
+ }, [context, isInitialized]);
161
+ useEffect(() => {
162
+ // Only run in browser environment
163
+ if (typeof document === 'undefined') {
164
+ return;
165
+ }
166
+ // Set up Tagada utilities before injecting legacy script
165
167
  setupTagada();
166
168
  const scriptContent = context?.script;
167
169
  const scriptId = 'tagada-funnel-script';
@@ -197,27 +199,27 @@ export function FunnelScriptInjector({ context, isInitialized }) {
197
199
  existingScript.remove();
198
200
  }
199
201
  // Wrap script content with error handling and context checks
200
- const wrappedScript = `
201
- (function() {
202
- try {
203
- // Check if we have basic DOM access
204
- if (typeof document === 'undefined') {
205
- console.error('[TagadaPay] Document not available');
206
- return;
207
- }
208
-
209
- // Check if we have Tagada
210
- if (!window.Tagada) {
211
- console.error('[TagadaPay] Tagada not available');
212
- return;
213
- }
214
-
215
- // Execute the original script
216
- ${scriptBody}
217
- } catch (error) {
218
- console.error('[TagadaPay] Script execution error:', error);
219
- }
220
- })();
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
+ })();
221
223
  `;
222
224
  // Create and inject new script element
223
225
  const scriptElement = document.createElement('script');
@@ -236,7 +238,98 @@ export function FunnelScriptInjector({ context, isInitialized }) {
236
238
  // This prevents React StrictMode from re-injecting the same script on the second run
237
239
  // The ref will be cleared when script content actually changes (next effect run)
238
240
  };
239
- }, [context?.script, context?.currentStepId, isInitialized]);
241
+ }, [context?.script, context?.currentStepId, isInitialized, setupTagada]);
242
+ // Effect for NEW stepConfig.scripts (from HTML injection, supports A/B variants)
243
+ useEffect(() => {
244
+ if (typeof document === 'undefined')
245
+ return;
246
+ // IMPORTANT: Set up Tagada BEFORE injecting any scripts
247
+ // This ensures Tagada.ready() is always available when scripts run
248
+ setupTagada();
249
+ if (stepConfigScripts.length === 0)
250
+ return;
251
+ // Create a hash of current scripts to detect changes
252
+ const scriptsHash = JSON.stringify(stepConfigScripts.map(s => ({ name: s.name, content: s.content, position: s.position })));
253
+ // Check if scripts are actually in the DOM (handles React StrictMode cleanup)
254
+ const existingScripts = document.querySelectorAll('[data-tagada-stepconfig-script]');
255
+ const scriptsExistInDom = existingScripts.length > 0;
256
+ // Skip ONLY if: same hash AND scripts actually exist in DOM
257
+ // This handles React StrictMode where cleanup removes scripts but ref persists
258
+ if (lastInjectedStepConfigScriptsRef.current === scriptsHash && scriptsExistInDom) {
259
+ return;
260
+ }
261
+ // Remove any existing stepConfig scripts before re-injecting
262
+ existingScripts.forEach(el => el.remove());
263
+ // Inject each enabled script at its correct position
264
+ stepConfigScripts.forEach((script, index) => {
265
+ 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)
274
+ 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
+ }
322
+ });
323
+ // Track injected scripts to prevent re-injection
324
+ lastInjectedStepConfigScriptsRef.current = scriptsHash;
325
+ // Cleanup: remove scripts on unmount
326
+ // Note: We keep the ref intact but check DOM existence on re-mount
327
+ // This handles both StrictMode (re-inject if cleanup removed them) and
328
+ // real unmounts (scripts properly cleaned up)
329
+ return () => {
330
+ document.querySelectorAll('[data-tagada-stepconfig-script]').forEach(el => el.remove());
331
+ };
332
+ }, [stepConfigScripts, setupTagada]);
240
333
  // This component doesn't render anything
241
334
  return null;
242
335
  }
@@ -14,6 +14,8 @@ export interface GooglePayButtonProps {
14
14
  size?: 'sm' | 'md' | 'lg';
15
15
  buttonColor?: 'default' | 'black' | 'white';
16
16
  buttonType?: 'buy' | 'plain' | 'donate' | 'pay';
17
+ /** Override shipping requirement. If undefined, auto-detects based on shippingMethods. */
18
+ requiresShipping?: boolean;
17
19
  }
18
20
  export declare const GooglePayButton: React.FC<GooglePayButtonProps>;
19
21
  export default GooglePayButton;