@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.
- package/README.md +1129 -1129
- package/build-cdn.js +499 -499
- package/dist/external-tracker.js +156 -2
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/tagada-react-sdk-minimal.min.js +2 -2
- package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
- package/dist/tagada-react-sdk.js +707 -253
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +2922 -102
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/funnelClient.d.ts +40 -0
- package/dist/v2/core/funnelClient.js +30 -0
- package/dist/v2/core/pixelTracker.d.ts +51 -0
- package/dist/v2/core/pixelTracker.js +425 -0
- package/dist/v2/core/resources/checkout.d.ts +45 -1
- package/dist/v2/core/resources/checkout.js +13 -3
- package/dist/v2/core/resources/offers.d.ts +3 -3
- package/dist/v2/core/resources/offers.js +11 -3
- package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
- package/dist/v2/core/resources/promotionEvents.js +2 -0
- package/dist/v2/core/resources/promotions.d.ts +6 -1
- package/dist/v2/core/resources/promotions.js +6 -1
- package/dist/v2/core/resources/shippingRates.d.ts +18 -0
- package/dist/v2/core/resources/shippingRates.js +18 -0
- package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
- package/dist/v2/core/utils/clickIdResolver.js +169 -0
- package/dist/v2/core/utils/index.d.ts +2 -0
- package/dist/v2/core/utils/index.js +4 -0
- package/dist/v2/core/utils/metaEventId.d.ts +14 -0
- package/dist/v2/core/utils/metaEventId.js +16 -0
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +7 -0
- package/dist/v2/index.js +10 -0
- package/dist/v2/react/components/ApplePayButton.js +50 -0
- package/dist/v2/react/components/FunnelScriptInjector.js +9 -9
- package/dist/v2/react/components/GooglePayButton.js +39 -1
- package/dist/v2/react/components/StripeExpressButton.js +54 -2
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +11 -11
- package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
- package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
- package/dist/v2/react/hooks/useFunnel.js +2 -1
- package/dist/v2/react/hooks/useOfferQuery.d.ts +11 -0
- package/dist/v2/react/hooks/useOfferQuery.js +11 -0
- package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
- package/dist/v2/react/hooks/usePixelTracking.js +32 -374
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
- package/dist/v2/react/hooks/usePreviewOffer.js +4 -2
- package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
- package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
- package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
- package/dist/v2/react/hooks/useStepConfig.js +5 -1
- package/dist/v2/react/index.d.ts +5 -0
- package/dist/v2/react/index.js +9 -0
- package/dist/v2/react/providers/TagadaProvider.js +18 -5
- package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
- package/dist/v2/standalone/index.d.ts +3 -0
- package/dist/v2/standalone/index.js +23 -0
- package/dist/v2/standalone/payment-service.d.ts +54 -1
- package/dist/v2/standalone/payment-service.js +228 -61
- 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
|
|
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
|
|
332
|
-
updateLineItems
|
|
333
|
-
updateLineItemsOptimistic
|
|
334
|
-
addLineItems
|
|
335
|
-
removeLineItems
|
|
336
|
-
setItemQuantity
|
|
337
|
-
updateCustomer
|
|
338
|
-
updateCustomerAndSessionInfo
|
|
339
|
-
applyPromotionCode
|
|
340
|
-
removePromotion
|
|
341
|
-
previewCheckoutSession
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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,
|
|
11
|
-
import {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
25
|
+
if (!pixels)
|
|
51
26
|
return;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
75
|
+
pay: (mainOrderId?: string, options?: {
|
|
76
|
+
initiatedBy?: 'merchant' | 'customer';
|
|
77
|
+
}) => Promise<{
|
|
76
78
|
checkoutUrl: string;
|
|
77
79
|
}>;
|
|
78
80
|
toCheckout: (mainOrderId?: string) => Promise<{
|