@tagadapay/plugin-sdk 4.0.0 → 4.0.4

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 (65) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +499 -499
  3. package/dist/external-tracker.js +156 -2
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/providers/TagadaProvider.js +5 -5
  7. package/dist/tagada-react-sdk-minimal.min.js +2 -2
  8. package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
  9. package/dist/tagada-react-sdk.js +707 -253
  10. package/dist/tagada-react-sdk.min.js +2 -2
  11. package/dist/tagada-react-sdk.min.js.map +4 -4
  12. package/dist/tagada-sdk.js +2922 -102
  13. package/dist/tagada-sdk.min.js +2 -2
  14. package/dist/tagada-sdk.min.js.map +4 -4
  15. package/dist/v2/core/funnelClient.d.ts +40 -0
  16. package/dist/v2/core/funnelClient.js +30 -0
  17. package/dist/v2/core/pixelTracker.d.ts +51 -0
  18. package/dist/v2/core/pixelTracker.js +425 -0
  19. package/dist/v2/core/resources/checkout.d.ts +45 -1
  20. package/dist/v2/core/resources/checkout.js +13 -3
  21. package/dist/v2/core/resources/offers.d.ts +3 -3
  22. package/dist/v2/core/resources/offers.js +11 -3
  23. package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
  24. package/dist/v2/core/resources/promotionEvents.js +2 -0
  25. package/dist/v2/core/resources/promotions.d.ts +6 -1
  26. package/dist/v2/core/resources/promotions.js +6 -1
  27. package/dist/v2/core/resources/shippingRates.d.ts +18 -0
  28. package/dist/v2/core/resources/shippingRates.js +18 -0
  29. package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
  30. package/dist/v2/core/utils/clickIdResolver.js +169 -0
  31. package/dist/v2/core/utils/index.d.ts +2 -0
  32. package/dist/v2/core/utils/index.js +4 -0
  33. package/dist/v2/core/utils/metaEventId.d.ts +14 -0
  34. package/dist/v2/core/utils/metaEventId.js +16 -0
  35. package/dist/v2/core/utils/previewModeIndicator.js +101 -101
  36. package/dist/v2/index.d.ts +7 -0
  37. package/dist/v2/index.js +10 -0
  38. package/dist/v2/react/components/ApplePayButton.js +50 -0
  39. package/dist/v2/react/components/FunnelScriptInjector.js +9 -9
  40. package/dist/v2/react/components/GooglePayButton.js +39 -1
  41. package/dist/v2/react/components/StripeExpressButton.js +54 -2
  42. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +11 -11
  43. package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
  44. package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
  45. package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
  46. package/dist/v2/react/hooks/useFunnel.js +2 -1
  47. package/dist/v2/react/hooks/useOfferQuery.d.ts +11 -0
  48. package/dist/v2/react/hooks/useOfferQuery.js +11 -0
  49. package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
  50. package/dist/v2/react/hooks/usePixelTracking.js +32 -374
  51. package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
  52. package/dist/v2/react/hooks/usePreviewOffer.js +4 -2
  53. package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
  54. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
  55. package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
  56. package/dist/v2/react/hooks/useStepConfig.js +5 -1
  57. package/dist/v2/react/index.d.ts +5 -0
  58. package/dist/v2/react/index.js +9 -0
  59. package/dist/v2/react/providers/TagadaProvider.js +18 -5
  60. package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
  61. package/dist/v2/standalone/index.d.ts +3 -0
  62. package/dist/v2/standalone/index.js +23 -0
  63. package/dist/v2/standalone/payment-service.d.ts +54 -1
  64. package/dist/v2/standalone/payment-service.js +228 -61
  65. package/package.json +115 -115
@@ -21,7 +21,7 @@ export function useCheckoutQuery(options = {}) {
21
21
  const { storeId } = usePluginConfig();
22
22
  const currency = useCurrency();
23
23
  const queryClient = useQueryClient();
24
- const { isSessionInitialized, session } = useTagadaContext();
24
+ const { isSessionInitialized, session, client } = useTagadaContext();
25
25
  // Track pending session promises to avoid creating multiple promises
26
26
  const pendingSessionPromise = useRef(null);
27
27
  const sessionResolvers = useRef(new Set());
@@ -64,12 +64,12 @@ export function useCheckoutQuery(options = {}) {
64
64
  // Create checkout resource client
65
65
  const checkoutResource = useMemo(() => {
66
66
  try {
67
- return new CheckoutResource(getGlobalApiClient());
67
+ return new CheckoutResource(getGlobalApiClient(), client.bus);
68
68
  }
69
69
  catch (error) {
70
70
  throw new Error(messages.initFailed + ': ' + (error instanceof Error ? error.message : 'Unknown error'));
71
71
  }
72
- }, []);
72
+ }, [client.bus]);
73
73
  // Internal token state that can be updated after init
74
74
  const [internalToken, setInternalToken] = useState(providedToken);
75
75
  // Update internal token when provided token changes
@@ -304,6 +304,32 @@ export function useCheckoutQuery(options = {}) {
304
304
  promotionIds,
305
305
  }),
306
306
  });
307
+ // Stable mutator wrappers. Returning inline arrows each render produced new
308
+ // refs on every checkout state change (mutation success → query invalidate
309
+ // → refetch → re-render), which propagated instability into consumer
310
+ // useCallback/useEffect deps and broke debounced auto-update (the timer in
311
+ // the shared RHF watch listener kept getting cleared mid-debounce, so the
312
+ // customer-and-session-info call never fired for email-only typing).
313
+ const init = useCallback(async (params) => {
314
+ await waitForSession();
315
+ const result = await initMutation.mutateAsync(params);
316
+ setInternalToken(result.checkoutToken);
317
+ return {
318
+ checkoutSession: checkout?.checkoutSession ?? {},
319
+ checkoutToken: result.checkoutToken,
320
+ };
321
+ }, [waitForSession, initMutation.mutateAsync, checkout?.checkoutSession]);
322
+ const replaceSessionLineItems = useCallback((lineItems) => replaceSessionLineItemsMutation.mutateAsync({ lineItems }), [replaceSessionLineItemsMutation.mutateAsync]);
323
+ const updateLineItems = useCallback((lineItems) => lineItemsMutation.mutateAsync({ lineItems }), [lineItemsMutation.mutateAsync]);
324
+ const updateLineItemsOptimistic = useCallback((lineItems) => lineItemsMutation.mutate({ lineItems }), [lineItemsMutation.mutate]);
325
+ const addLineItems = useCallback((lineItems) => addLineItemsMutation.mutateAsync({ lineItems }), [addLineItemsMutation.mutateAsync]);
326
+ const removeLineItems = useCallback((lineItems) => removeLineItemsMutation.mutateAsync({ lineItems }), [removeLineItemsMutation.mutateAsync]);
327
+ const setItemQuantity = useCallback((variantId, quantity, priceId) => quantityMutation.mutateAsync({ variantId, quantity, priceId }), [quantityMutation.mutateAsync]);
328
+ const updateCustomer = useCallback((data) => customerMutation.mutateAsync(data), [customerMutation.mutateAsync]);
329
+ const updateCustomerAndSessionInfo = useCallback((data) => customerAndSessionMutation.mutateAsync(data), [customerAndSessionMutation.mutateAsync]);
330
+ const applyPromotionCode = useCallback((code) => promotionMutation.mutateAsync({ code }), [promotionMutation.mutateAsync]);
331
+ const removePromotion = useCallback((promotionId) => removePromotionMutation.mutateAsync({ promotionId }), [removePromotionMutation.mutateAsync]);
332
+ const previewCheckoutSession = useCallback((lineItems, promotionIds) => previewCheckoutSessionMutation.mutateAsync({ lineItems, promotionIds }), [previewCheckoutSessionMutation.mutateAsync]);
307
333
  return {
308
334
  // Query data
309
335
  checkout,
@@ -311,33 +337,19 @@ export function useCheckoutQuery(options = {}) {
311
337
  error,
312
338
  isSuccess,
313
339
  // Actions
314
- init: async (params) => {
315
- // Wait for session to be initialized to ensure we have customerId
316
- await waitForSession();
317
- const result = await initMutation.mutateAsync(params);
318
- // Update internal token state so the query can fetch the checkout data
319
- // The query will automatically refetch when token changes, and getCheckout()
320
- // will automatically wait for async completion (via SDK skipAsyncWait=false)
321
- setInternalToken(result.checkoutToken);
322
- // Return immediately with token
323
- // checkoutSession will be populated by the query once background processing completes
324
- return {
325
- checkoutSession: checkout?.checkoutSession ?? {},
326
- checkoutToken: result.checkoutToken,
327
- };
328
- },
340
+ init,
329
341
  refresh,
330
342
  // Checkout operations
331
- replaceSessionLineItems: (lineItems) => replaceSessionLineItemsMutation.mutateAsync({ lineItems }),
332
- updateLineItems: (lineItems) => lineItemsMutation.mutateAsync({ lineItems }),
333
- updateLineItemsOptimistic: (lineItems) => lineItemsMutation.mutate({ lineItems }),
334
- addLineItems: (lineItems) => addLineItemsMutation.mutateAsync({ lineItems }),
335
- removeLineItems: (lineItems) => removeLineItemsMutation.mutateAsync({ lineItems }),
336
- setItemQuantity: (variantId, quantity, priceId) => quantityMutation.mutateAsync({ variantId, quantity, priceId }),
337
- updateCustomer: (data) => customerMutation.mutateAsync(data),
338
- updateCustomerAndSessionInfo: (data) => customerAndSessionMutation.mutateAsync(data),
339
- applyPromotionCode: (code) => promotionMutation.mutateAsync({ code }),
340
- removePromotion: (promotionId) => removePromotionMutation.mutateAsync({ promotionId }),
341
- previewCheckoutSession: (lineItems, promotionIds) => previewCheckoutSessionMutation.mutateAsync({ lineItems, promotionIds }),
343
+ replaceSessionLineItems,
344
+ updateLineItems,
345
+ updateLineItemsOptimistic,
346
+ addLineItems,
347
+ removeLineItems,
348
+ setItemQuantity,
349
+ updateCustomer,
350
+ updateCustomerAndSessionInfo,
351
+ applyPromotionCode,
352
+ removePromotion,
353
+ previewCheckoutSession,
342
354
  };
343
355
  }
@@ -45,6 +45,8 @@ export function useDiscountsQuery(options = {}) {
45
45
  await Promise.all([
46
46
  queryClient.invalidateQueries({ queryKey: ['discounts', sessionId] }),
47
47
  queryClient.invalidateQueries({ queryKey: ['checkout'] }),
48
+ queryClient.invalidateQueries({ queryKey: ['shipping-rates', sessionId] }),
49
+ queryClient.invalidateQueries({ queryKey: ['shipping-rates-preview', sessionId] }),
48
50
  ]);
49
51
  }
50
52
  // Call onSuccess callback if provided
@@ -72,6 +74,8 @@ export function useDiscountsQuery(options = {}) {
72
74
  await Promise.all([
73
75
  queryClient.invalidateQueries({ queryKey: ['discounts', sessionId] }),
74
76
  queryClient.invalidateQueries({ queryKey: ['checkout'] }),
77
+ queryClient.invalidateQueries({ queryKey: ['shipping-rates', sessionId] }),
78
+ queryClient.invalidateQueries({ queryKey: ['shipping-rates-preview', sessionId] }),
75
79
  ]);
76
80
  }
77
81
  // Call onSuccess callback if provided
@@ -65,6 +65,13 @@ export interface StepConfigValue {
65
65
  * undefined = inherit all store upsells, string[] = only these IDs.
66
66
  */
67
67
  upsellOfferIds: string[] | undefined;
68
+ /**
69
+ * Payment initiator override for this step's offer/upsell charge.
70
+ * 'merchant' = MIT (off-session, no 3DS). 'customer' = CIT (may trigger 3DS).
71
+ * undefined = use SDK default (merchant).
72
+ * usePreviewOffer auto-applies this value; consumers usually don't need to read it.
73
+ */
74
+ paymentInitiator: 'merchant' | 'customer' | undefined;
68
75
  }
69
76
  export interface FunnelContextValue extends FunnelState {
70
77
  currentStep: {
@@ -20,7 +20,7 @@
20
20
  * ```
21
21
  */
22
22
  import { useMemo } from 'react';
23
- import { TrackingProvider, getAssignedOrderBumpOfferIds, getAssignedPaymentFlowId, getAssignedPixels, getAssignedResources, getAssignedScripts, getAssignedStepConfig, getAssignedUpsellOfferIds, } from '../../core/funnelClient';
23
+ import { TrackingProvider, getAssignedOrderBumpOfferIds, getAssignedPaymentFlowId, getAssignedPaymentInitiator, getAssignedPixels, getAssignedResources, getAssignedScripts, getAssignedStepConfig, getAssignedUpsellOfferIds, } from '../../core/funnelClient';
24
24
  import { useTagadaContext } from '../providers/TagadaProvider';
25
25
  /**
26
26
  * Hook to access funnel state and methods
@@ -45,6 +45,7 @@ export function useFunnel() {
45
45
  getScripts: (position) => getAssignedScripts(position),
46
46
  orderBumpOfferIds: getAssignedOrderBumpOfferIds(),
47
47
  upsellOfferIds: getAssignedUpsellOfferIds(),
48
+ paymentInitiator: getAssignedPaymentInitiator(),
48
49
  };
49
50
  }, []);
50
51
  return {
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * useOffer Hook - Single offer workflow with checkout session management
3
3
  *
4
+ * @deprecated Use `usePreviewOffer` instead. `usePreviewOffer` is the
5
+ * supported hook for post-checkout upsell pages: it skips the upfront
6
+ * checkout-session round-trip, recomputes pricing client-side, and applies
7
+ * the CRM-configured MIT/CIT payment initiator automatically via the
8
+ * shared OffersResource auto-pickup. `useOfferQuery` (aliased as `useOffer`)
9
+ * remains exported for backwards compatibility but will be removed in a
10
+ * future major version.
11
+ *
4
12
  * Behavior copied from useOffersQuery but simplified for a single offer:
5
13
  * 1. Fetches offer data
6
14
  * 2. Auto-initializes checkout session when mainOrderId is provided
@@ -106,4 +114,7 @@ export interface UseOfferQueryResult {
106
114
  /** Whether variant is loading for a product */
107
115
  isLoadingVariant: (productId: string) => boolean;
108
116
  }
117
+ /**
118
+ * @deprecated Use `usePreviewOffer` instead. See module-level JSDoc.
119
+ */
109
120
  export declare function useOfferQuery(options: UseOfferQueryOptions): UseOfferQueryResult;
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * useOffer Hook - Single offer workflow with checkout session management
3
3
  *
4
+ * @deprecated Use `usePreviewOffer` instead. `usePreviewOffer` is the
5
+ * supported hook for post-checkout upsell pages: it skips the upfront
6
+ * checkout-session round-trip, recomputes pricing client-side, and applies
7
+ * the CRM-configured MIT/CIT payment initiator automatically via the
8
+ * shared OffersResource auto-pickup. `useOfferQuery` (aliased as `useOffer`)
9
+ * remains exported for backwards compatibility but will be removed in a
10
+ * future major version.
11
+ *
4
12
  * Behavior copied from useOffersQuery but simplified for a single offer:
5
13
  * 1. Fetches offer data
6
14
  * 2. Auto-initializes checkout session when mainOrderId is provided
@@ -14,6 +22,9 @@ import { OffersResource } from '../../core/resources/offers';
14
22
  import { useTagadaContext } from '../providers/TagadaProvider';
15
23
  import { getGlobalApiClient } from './useApiQuery';
16
24
  import { usePluginConfig } from './usePluginConfig';
25
+ /**
26
+ * @deprecated Use `usePreviewOffer` instead. See module-level JSDoc.
27
+ */
17
28
  export function useOfferQuery(options) {
18
29
  const { offerId, enabled = true, mainOrderId: rawMainOrderId } = options;
19
30
  const { storeId } = usePluginConfig();
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * usePixelTracking Hook & Provider
3
3
  *
4
- * SDK-level pixel tracking based on runtime stepConfig.pixels injected
5
- * by the CRM. Uses core/pixelMapping for event mapping and gating,
6
- * keeping browser-specific init/fire logic here.
4
+ * Thin React adapter over `core/pixelTracker`. The provider creates a tracker
5
+ * for the current `stepConfig.pixels`, exposes `{ track, pixelsInitialized }`
6
+ * via context, and disposes/recreates the tracker when pixels change.
7
+ *
8
+ * Init + fire logic lives in `core/pixelTracker` so non-React entries (Studio
9
+ * islands, full-SPA runtime) can fire pixels without mounting a provider.
7
10
  */
8
11
  import React from 'react';
9
- import { type StandardPixelEvent } from '../../core/pixelMapping';
12
+ import { type TrackOptions } from '../../core/pixelTracker';
13
+ import type { StandardPixelEvent } from '../../core/pixelMapping';
10
14
  export type { StandardPixelEvent } from '../../core/pixelMapping';
15
+ export type { TrackOptions } from '../../core/pixelTracker';
11
16
  export interface PixelTrackingContextValue {
12
- track: (eventName: StandardPixelEvent, parameters?: Record<string, unknown>) => void;
17
+ track: (eventName: StandardPixelEvent, parameters?: Record<string, unknown>, options?: TrackOptions) => void;
13
18
  pixelsInitialized: boolean;
14
19
  }
15
20
  export declare function PixelTrackingProvider({ children }: {
@@ -3,114 +3,46 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
4
  * usePixelTracking Hook & Provider
5
5
  *
6
- * SDK-level pixel tracking based on runtime stepConfig.pixels injected
7
- * by the CRM. Uses core/pixelMapping for event mapping and gating,
8
- * keeping browser-specific init/fire logic here.
6
+ * Thin React adapter over `core/pixelTracker`. The provider creates a tracker
7
+ * for the current `stepConfig.pixels`, exposes `{ track, pixelsInitialized }`
8
+ * via context, and disposes/recreates the tracker when pixels change.
9
+ *
10
+ * Init + fire logic lives in `core/pixelTracker` so non-React entries (Studio
11
+ * islands, full-SPA runtime) can fire pixels without mounting a provider.
9
12
  */
10
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
11
- import { resolvePixelEvents, } from '../../core/pixelMapping';
13
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, } from 'react';
14
+ import { createPixelTracker, } from '../../core/pixelTracker';
12
15
  import { useStepConfig } from './useStepConfig';
13
16
  const PixelTrackingContext = createContext(null);
14
- // ---------------------------------------------------------------------------
15
- // Duplicate guard (time-window based)
16
- // ---------------------------------------------------------------------------
17
- function createDuplicateGuard(windowMs) {
18
- const lastEvents = new Map();
19
- return (eventName, parameters) => {
20
- try {
21
- const key = JSON.stringify({ eventName, parameters });
22
- const now = Date.now();
23
- const last = lastEvents.get(key);
24
- if (last && now - last < windowMs)
25
- return false;
26
- lastEvents.set(key, now);
27
- return true;
28
- }
29
- catch {
30
- return true;
31
- }
32
- };
33
- }
34
- const shouldTrackEvent = createDuplicateGuard(5000);
35
- // ---------------------------------------------------------------------------
36
- // Provider
37
- // ---------------------------------------------------------------------------
38
17
  export function PixelTrackingProvider({ children }) {
39
18
  const { pixels } = useStepConfig();
19
+ const trackerRef = useRef(null);
40
20
  const [pixelsInitialized, setPixelsInitialized] = useState(false);
41
- const isMountedRef = useRef(true);
42
- useEffect(() => {
43
- isMountedRef.current = true;
44
- return () => { isMountedRef.current = false; };
45
- }, []);
46
- // ---- Initialize pixel scripts once ----
47
- // Wait for all external scripts to actually load before marking initialized,
48
- // so that pixel helpers (TikTok, etc.) can intercept events properly.
21
+ // Recreate the tracker when `pixels` changes. The underlying init helpers
22
+ // are idempotent — base scripts and per-pixel `init` calls de-duplicate
23
+ // themselves so a rebuild won't double-load scripts.
49
24
  useEffect(() => {
50
- if (!pixels || pixelsInitialized || !isMountedRef.current)
25
+ if (!pixels)
51
26
  return;
52
- const loadPromises = [];
53
- try {
54
- pixels.facebook?.forEach((px) => { if (px.enabled && px.pixelId)
55
- loadPromises.push(initMetaPixel(px.pixelId)); });
56
- pixels.tiktok?.forEach((px) => { if (px.enabled && px.pixelId)
57
- loadPromises.push(initTikTokPixel(px.pixelId)); });
58
- pixels.snapchat?.forEach((px) => { if (px.enabled && px.pixelId)
59
- loadPromises.push(initSnapchatPixel(px.pixelId)); });
60
- pixels.pinterest?.forEach((px) => { if (px.enabled && px.pixelId)
61
- loadPromises.push(initPinterestPixel(px.pixelId)); });
62
- pixels.gtm?.forEach((px) => { if (px.enabled && px.containerId)
63
- loadPromises.push(initGTM(px.containerId)); });
64
- Promise.all(loadPromises).then(() => {
65
- if (isMountedRef.current)
66
- setPixelsInitialized(true);
67
- });
68
- }
69
- catch (error) {
70
- console.error('[SDK Pixels] Initialization error:', error);
71
- }
72
- }, [pixels, pixelsInitialized]);
73
- // ---- Track function ----
74
- const track = useCallback((eventName, parameters = {}) => {
75
- if (!pixels || !pixelsInitialized || !isMountedRef.current)
76
- return;
77
- if (!shouldTrackEvent(eventName, parameters))
78
- return;
79
- try {
80
- const events = resolvePixelEvents(pixels, eventName, parameters);
81
- // Deduplicate by provider: Meta/Snapchat/Pinterest SDKs broadcast
82
- // to all registered pixel IDs internally, so we only need to fire once per
83
- // provider. GTM and TikTok need per-pixel firing (GTM for Google Ads
84
- // send_to targeting, TikTok because ttq.instance() targets a specific pixel).
85
- const firedProviders = new Set();
86
- for (const { provider, mapped, pixel } of events) {
87
- if (provider === 'gtm' || provider === 'tiktok') {
88
- fire(provider, mapped.name, mapped.params, pixel);
89
- }
90
- else {
91
- if (!firedProviders.has(provider)) {
92
- fire(provider, mapped.name, mapped.params);
93
- firedProviders.add(provider);
94
- }
95
- }
96
- }
97
- }
98
- catch (error) {
99
- console.error('[SDK Pixels] Tracking error:', error);
100
- }
101
- }, [pixels, pixelsInitialized]);
102
- // ---- Auto page-view ----
103
- useEffect(() => {
104
- if (!pixelsInitialized || !isMountedRef.current)
105
- return;
106
- const id = setTimeout(() => {
107
- if (isMountedRef.current) {
108
- track('PageView', { path: typeof window !== 'undefined' ? window.location.pathname : '' });
109
- }
110
- }, 0);
111
- return () => clearTimeout(id);
112
- }, [pixelsInitialized, track]);
113
- const value = useMemo(() => ({ track, pixelsInitialized }), [track, pixelsInitialized]);
27
+ const tracker = createPixelTracker(pixels);
28
+ trackerRef.current = tracker;
29
+ setPixelsInitialized(false);
30
+ let cancelled = false;
31
+ tracker.initPromise.then(() => {
32
+ if (!cancelled)
33
+ setPixelsInitialized(true);
34
+ });
35
+ return () => {
36
+ cancelled = true;
37
+ trackerRef.current = null;
38
+ };
39
+ }, [pixels]);
40
+ const value = useMemo(() => ({
41
+ track: (eventName, parameters, options) => {
42
+ trackerRef.current?.track(eventName, parameters, options);
43
+ },
44
+ pixelsInitialized,
45
+ }), [pixelsInitialized]);
114
46
  return _jsx(PixelTrackingContext.Provider, { value: value, children: children });
115
47
  }
116
48
  export function usePixelTracking() {
@@ -119,277 +51,3 @@ export function usePixelTracking() {
119
51
  throw new Error('usePixelTracking must be used within a PixelTrackingProvider');
120
52
  return context;
121
53
  }
122
- // ---------------------------------------------------------------------------
123
- // Browser-specific: fire an event to the correct global pixel function
124
- // ---------------------------------------------------------------------------
125
- /* eslint-disable @typescript-eslint/no-explicit-any */
126
- function fire(provider, name, params, pixel) {
127
- if (typeof window === 'undefined')
128
- return;
129
- const w = window;
130
- switch (provider) {
131
- case 'facebook':
132
- w.fbq?.('track', name, params);
133
- break;
134
- case 'tiktok': {
135
- // Use ttq.instance(pixelId) to target each pixel individually,
136
- // since the global ttq.track() only fires for the sdkid pixel.
137
- const pixelId = pixel && 'pixelId' in pixel ? pixel.pixelId : null;
138
- const target = pixelId && w.ttq?.instance ? w.ttq.instance(pixelId) : w.ttq;
139
- if (name === 'Pageview') {
140
- target?.page?.();
141
- }
142
- else {
143
- target?.track?.(name, params);
144
- }
145
- break;
146
- }
147
- case 'snapchat':
148
- w.snaptr?.('track', name, params);
149
- break;
150
- case 'pinterest':
151
- // Pinterest handles page views via pintrk('page'), not pintrk('track', 'pagevisit')
152
- if (name === 'pagevisit') {
153
- w.pintrk?.('page');
154
- }
155
- else {
156
- w.pintrk?.('track', name, params);
157
- }
158
- break;
159
- case 'gtm':
160
- fireGTM(name, params);
161
- break;
162
- }
163
- }
164
- function fireGTM(event, params) {
165
- try {
166
- const w = window;
167
- if (w.gtag) {
168
- w.gtag('event', event, params);
169
- }
170
- else if (w.dataLayer) {
171
- w.dataLayer.push({ event, ...params });
172
- }
173
- }
174
- catch (error) {
175
- console.error('[SDK GTM] Error:', error);
176
- }
177
- }
178
- /* eslint-enable @typescript-eslint/no-explicit-any */
179
- // ===========================================================================
180
- // Pixel initialization (browser-only)
181
- // All pixel globals are accessed via `win` typed as `any` to avoid
182
- // `declare global` augmentation issues across tsconfig scopes.
183
- // ===========================================================================
184
- /* eslint-disable @typescript-eslint/no-explicit-any */
185
- const win = (typeof window !== 'undefined' ? window : undefined);
186
- const SCRIPT_LOAD_TIMEOUT_MS = 5000;
187
- function waitForScriptLoad(script) {
188
- // If the script has already loaded, resolve immediately
189
- if (script.dataset.loaded)
190
- return Promise.resolve();
191
- return new Promise((resolve) => {
192
- const done = () => { script.dataset.loaded = '1'; clearTimeout(timer); resolve(); };
193
- const timer = setTimeout(done, SCRIPT_LOAD_TIMEOUT_MS);
194
- script.addEventListener('load', done, { once: true });
195
- script.addEventListener('error', done, { once: true });
196
- });
197
- }
198
- let _metaScriptEl = null;
199
- function initMetaPixel(pixelId) {
200
- if (!win)
201
- return Promise.resolve();
202
- // Initialize Meta base code once
203
- if (!win.fbq) {
204
- const n = function (...args) {
205
- if (n.callMethod)
206
- n.callMethod(...args);
207
- else
208
- n.queue.push(args);
209
- };
210
- n.queue = [];
211
- n.loaded = true;
212
- n.version = '2.0';
213
- win.fbq = n;
214
- if (!win._fbq)
215
- win._fbq = n;
216
- const t = document.createElement('script');
217
- t.async = true;
218
- t.src = 'https://connect.facebook.net/en_US/fbevents.js';
219
- const s = document.getElementsByTagName('script')[0];
220
- s?.parentNode?.insertBefore(t, s);
221
- _metaScriptEl = t;
222
- }
223
- // Register each pixel ID (fbq supports multiple pixels via multiple init calls)
224
- win.fbq('init', pixelId);
225
- return _metaScriptEl ? waitForScriptLoad(_metaScriptEl) : Promise.resolve();
226
- }
227
- let _tiktokBaseInitialized = false;
228
- function initTikTokPixel(pixelId) {
229
- if (!win)
230
- return Promise.resolve();
231
- // Initialize TikTok base code once
232
- if (!_tiktokBaseInitialized) {
233
- _tiktokBaseInitialized = true;
234
- win.TiktokAnalyticsObject = 'ttq';
235
- const ttq = (win.ttq = win.ttq || []);
236
- ttq.methods = [
237
- 'page', 'track', 'identify', 'instances', 'debug', 'on', 'off',
238
- 'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie',
239
- 'holdConsent', 'revokeConsent', 'grantConsent',
240
- ];
241
- ttq.setAndDefer = function (t, e) {
242
- t[e] = function (...args) { t.push([e, ...args]); };
243
- };
244
- for (const method of ttq.methods) {
245
- ttq.setAndDefer(ttq, method);
246
- }
247
- ttq.instance = function (t) {
248
- const e = ttq._i[t] || [];
249
- for (const method of ttq.methods) {
250
- ttq.setAndDefer(e, method);
251
- }
252
- return e;
253
- };
254
- ttq.load = function (e, n) {
255
- const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
256
- ttq._i = ttq._i || {};
257
- ttq._i[e] = [];
258
- ttq._i[e]._u = r;
259
- ttq._t = ttq._t || {};
260
- ttq._t[e] = +new Date();
261
- ttq._o = ttq._o || {};
262
- ttq._o[e] = n || {};
263
- const s = document.createElement('script');
264
- s.type = 'text/javascript';
265
- s.async = true;
266
- s.src = r + '?sdkid=' + e + '&lib=ttq';
267
- const p = document.getElementsByTagName('script')[0];
268
- p?.parentNode?.insertBefore(s, p);
269
- };
270
- }
271
- // Skip if this specific pixel ID is already registered
272
- if (win.ttq._i?.[pixelId])
273
- return Promise.resolve();
274
- // Register pixel and load its script
275
- win.ttq.load(pixelId);
276
- // Find the script we just created and wait for it
277
- const scripts = document.querySelectorAll('script[src*="analytics.tiktok.com/i18n/pixel/events.js"]');
278
- const lastScript = scripts[scripts.length - 1];
279
- return lastScript ? waitForScriptLoad(lastScript) : Promise.resolve();
280
- }
281
- let _snapchatScriptEl = null;
282
- function initSnapchatPixel(pixelId) {
283
- if (!win)
284
- return Promise.resolve();
285
- // Initialize Snapchat base code once
286
- if (!win.snaptr) {
287
- const a = function (...args) {
288
- if (a.handleRequest)
289
- a.handleRequest(...args);
290
- else
291
- a.queue.push(args);
292
- };
293
- a.queue = [];
294
- win.snaptr = a;
295
- const r = document.createElement('script');
296
- r.async = true;
297
- r.src = 'https://sc-static.net/scevent.min.js';
298
- const u = document.getElementsByTagName('script')[0];
299
- u?.parentNode?.insertBefore(r, u);
300
- _snapchatScriptEl = r;
301
- }
302
- // Register each pixel ID (snaptr supports multiple pixels via multiple init calls)
303
- win.snaptr('init', pixelId);
304
- return _snapchatScriptEl ? waitForScriptLoad(_snapchatScriptEl) : Promise.resolve();
305
- }
306
- let _pinterestScriptEl = null;
307
- function initPinterestPixel(pixelId) {
308
- if (!win)
309
- return Promise.resolve();
310
- // Initialize Pinterest base code once
311
- if (!win.pintrk) {
312
- const a = function (...args) { a.queue.push(args); };
313
- a.queue = [];
314
- win.pintrk = a;
315
- const s = document.createElement('script');
316
- s.async = true;
317
- s.src = 'https://s.pinimg.com/ct/core.js';
318
- const u = document.getElementsByTagName('script')[0];
319
- u?.parentNode?.insertBefore(s, u);
320
- _pinterestScriptEl = s;
321
- }
322
- // Register each pixel ID (pintrk supports multiple pixels via multiple load calls)
323
- win.pintrk('load', pixelId);
324
- // Note: pintrk('page') is NOT called here — the Provider's auto page-view
325
- // effect handles it via fire(). Calling both would double-count page views.
326
- return _pinterestScriptEl ? waitForScriptLoad(_pinterestScriptEl) : Promise.resolve();
327
- }
328
- function initGTM(containerId) {
329
- if (!win || !containerId)
330
- return Promise.resolve();
331
- const isGtmContainer = containerId.startsWith('GTM-');
332
- const scriptUrlPart = isGtmContainer
333
- ? `googletagmanager.com/gtm.js?id=${containerId}`
334
- : `googletagmanager.com/gtag/js?id=${containerId}`;
335
- if (document.querySelector(`script[src*="${scriptUrlPart}"]`))
336
- return Promise.resolve();
337
- win.dataLayer = win.dataLayer || [];
338
- // Push referrer domain context into dataLayer before GTM loads, so tags
339
- // inside the container can use it for cross-domain tracking configuration.
340
- try {
341
- const ref = document.referrer && new URL(document.referrer).hostname;
342
- if (ref && ref !== window.location.hostname) {
343
- win.dataLayer.push({ tagada_referrer_domain: ref });
344
- }
345
- }
346
- catch { /* ignore invalid referrer */ }
347
- if (isGtmContainer) {
348
- win.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
349
- const f = document.getElementsByTagName('script')[0];
350
- const j = document.createElement('script');
351
- j.async = true;
352
- j.src = 'https://www.googletagmanager.com/gtm.js?id=' + containerId;
353
- if (f?.parentNode)
354
- f.parentNode.insertBefore(j, f);
355
- else
356
- (document.head || document.getElementsByTagName('head')[0])?.appendChild(j);
357
- const noscript = document.createElement('noscript');
358
- const iframe = document.createElement('iframe');
359
- iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
360
- iframe.height = '0';
361
- iframe.width = '0';
362
- iframe.style.display = 'none';
363
- iframe.style.visibility = 'hidden';
364
- noscript.appendChild(iframe);
365
- (document.body || document.getElementsByTagName('body')[0])?.insertBefore(noscript, document.body?.firstChild ?? null);
366
- return waitForScriptLoad(j);
367
- }
368
- else {
369
- if (!win.gtag) {
370
- win.gtag = function (..._args) { win.dataLayer.push(arguments); };
371
- }
372
- win.gtag('js', new Date());
373
- // Enable cross-domain tracking: accept incoming _gl parameter from referring
374
- // domains (e.g. Shopify store → TagadaPay checkout) so Google preserves the
375
- // client-id/session across the redirect.
376
- const linkerConfig = { accept_incoming: true };
377
- try {
378
- const ref = document.referrer && new URL(document.referrer).hostname;
379
- if (ref && ref !== window.location.hostname) {
380
- linkerConfig.domains = [ref, window.location.hostname];
381
- }
382
- }
383
- catch { /* ignore invalid referrer */ }
384
- win.gtag('config', containerId, { linker: linkerConfig });
385
- const script = document.createElement('script');
386
- script.async = true;
387
- script.src = 'https://www.googletagmanager.com/gtag/js?id=' + containerId;
388
- const firstScript = document.getElementsByTagName('script')[0];
389
- if (firstScript?.parentNode)
390
- firstScript.parentNode.insertBefore(script, firstScript);
391
- else
392
- (document.head || document.getElementsByTagName('head')[0])?.appendChild(script);
393
- return waitForScriptLoad(script);
394
- }
395
- }
@@ -72,7 +72,9 @@ export interface UsePreviewOfferResult {
72
72
  unitAmount: number;
73
73
  currency: string;
74
74
  }>;
75
- pay: (mainOrderId?: string) => Promise<{
75
+ pay: (mainOrderId?: string, options?: {
76
+ initiatedBy?: 'merchant' | 'customer';
77
+ }) => Promise<{
76
78
  checkoutUrl: string;
77
79
  }>;
78
80
  toCheckout: (mainOrderId?: string) => Promise<{