@tagadapay/plugin-sdk 4.0.0 → 4.0.2

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 (63) 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 +696 -245
  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 +2908 -94
  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 +1 -1
  22. package/dist/v2/core/resources/offers.js +3 -1
  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/usePixelTracking.d.ts +10 -5
  48. package/dist/v2/react/hooks/usePixelTracking.js +32 -374
  49. package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
  50. package/dist/v2/react/hooks/usePreviewOffer.js +8 -2
  51. package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
  52. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
  53. package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
  54. package/dist/v2/react/hooks/useStepConfig.js +5 -1
  55. package/dist/v2/react/index.d.ts +4 -0
  56. package/dist/v2/react/index.js +8 -0
  57. package/dist/v2/react/providers/TagadaProvider.js +18 -5
  58. package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
  59. package/dist/v2/standalone/index.d.ts +3 -0
  60. package/dist/v2/standalone/index.js +23 -0
  61. package/dist/v2/standalone/payment-service.d.ts +54 -1
  62. package/dist/v2/standalone/payment-service.js +228 -61
  63. package/package.json +115 -115
@@ -2,9 +2,11 @@
2
2
  * Checkout Resource Client
3
3
  * Axios-based API client for checkout endpoints
4
4
  */
5
+ import { PROMOTION_APPLIED, PROMOTION_REMOVED } from './promotionEvents';
5
6
  export class CheckoutResource {
6
- constructor(apiClient) {
7
+ constructor(apiClient, bus) {
7
8
  this.apiClient = apiClient;
9
+ this.bus = bus;
8
10
  }
9
11
  /**
10
12
  * Initialize a new checkout session (sync mode)
@@ -131,15 +133,23 @@ export class CheckoutResource {
131
133
  * Apply promotion code
132
134
  */
133
135
  async applyPromotionCode(checkoutSessionId, code) {
134
- return this.apiClient.post(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/apply`, {
136
+ const result = await this.apiClient.post(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/apply`, {
135
137
  code,
136
138
  });
139
+ if (result.success) {
140
+ void this.bus?.emit(PROMOTION_APPLIED, { checkoutSessionId });
141
+ }
142
+ return result;
137
143
  }
138
144
  /**
139
145
  * Remove promotion
140
146
  */
141
147
  async removePromotion(checkoutSessionId, promotionId) {
142
- return this.apiClient.delete(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/${promotionId}`);
148
+ const result = await this.apiClient.delete(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/${promotionId}`);
149
+ if (result.success) {
150
+ void this.bus?.emit(PROMOTION_REMOVED, { checkoutSessionId });
151
+ }
152
+ return result;
143
153
  }
144
154
  /**
145
155
  * Get applied promotions
@@ -240,7 +240,7 @@ export declare class OffersResource {
240
240
  productId?: string;
241
241
  variantId: string;
242
242
  quantity: number;
243
- }>, returnUrl?: string, mainOrderId?: string): Promise<{
243
+ }>, returnUrl?: string, mainOrderId?: string, initiatedBy?: 'merchant' | 'customer'): Promise<{
244
244
  preview: any;
245
245
  checkout: {
246
246
  checkoutUrl: string;
@@ -68,13 +68,14 @@ export class OffersResource {
68
68
  * @param returnUrl - Optional return URL for checkout
69
69
  * @param mainOrderId - Optional main order ID (for upsells)
70
70
  */
71
- async payPreviewedOffer(offerId, currency = '', lineItems, returnUrl, mainOrderId) {
71
+ async payPreviewedOffer(offerId, currency = '', lineItems, returnUrl, mainOrderId, initiatedBy) {
72
72
  console.log('💳 [OffersResource] Calling pay-preview API:', {
73
73
  offerId,
74
74
  currency,
75
75
  lineItems,
76
76
  returnUrl,
77
77
  mainOrderId,
78
+ initiatedBy,
78
79
  endpoint: `/api/v1/offers/${offerId}/pay-preview`,
79
80
  });
80
81
  const response = await this.apiClient.post(`/api/v1/offers/${offerId}/pay-preview`, {
@@ -83,6 +84,7 @@ export class OffersResource {
83
84
  lineItems,
84
85
  returnUrl: returnUrl || (typeof window !== 'undefined' ? window.location.href : ''),
85
86
  mainOrderId,
87
+ ...(initiatedBy ? { initiatedBy } : {}),
86
88
  });
87
89
  console.log('📥 [OffersResource] Pay-preview API response:', response);
88
90
  return response;
@@ -0,0 +1,5 @@
1
+ export declare const PROMOTION_APPLIED = "PROMOTION_APPLIED";
2
+ export declare const PROMOTION_REMOVED = "PROMOTION_REMOVED";
3
+ export interface PromotionEventPayload {
4
+ checkoutSessionId: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ export const PROMOTION_APPLIED = 'PROMOTION_APPLIED';
2
+ export const PROMOTION_REMOVED = 'PROMOTION_REMOVED';
@@ -4,6 +4,10 @@
4
4
  */
5
5
  import { ApiClient } from './apiClient';
6
6
  import { Promotion } from './checkout';
7
+ import { EventBus } from '../utils/eventBus';
8
+ import { PROMOTION_APPLIED, PROMOTION_REMOVED, PromotionEventPayload } from './promotionEvents';
9
+ export { PROMOTION_APPLIED, PROMOTION_REMOVED };
10
+ export type { PromotionEventPayload };
7
11
  export interface PromotionCodeValidation {
8
12
  isValid: boolean;
9
13
  code: string;
@@ -18,7 +22,8 @@ export interface PromotionCodeValidation {
18
22
  }
19
23
  export declare class PromotionsResource {
20
24
  private apiClient;
21
- constructor(apiClient: ApiClient);
25
+ private bus?;
26
+ constructor(apiClient: ApiClient, bus?: EventBus | undefined);
22
27
  /**
23
28
  * Get applied promotions for a checkout session
24
29
  */
@@ -2,9 +2,12 @@
2
2
  * Promotions Resource Client
3
3
  * Axios-based API client for promotion endpoints
4
4
  */
5
+ import { PROMOTION_APPLIED, PROMOTION_REMOVED } from './promotionEvents';
6
+ export { PROMOTION_APPLIED, PROMOTION_REMOVED };
5
7
  export class PromotionsResource {
6
- constructor(apiClient) {
8
+ constructor(apiClient, bus) {
7
9
  this.apiClient = apiClient;
10
+ this.bus = bus;
8
11
  }
9
12
  /**
10
13
  * Get applied promotions for a checkout session
@@ -19,6 +22,7 @@ export class PromotionsResource {
19
22
  try {
20
23
  const response = await this.apiClient.post(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/apply`, { code: code.trim() });
21
24
  if (response.success) {
25
+ void this.bus?.emit(PROMOTION_APPLIED, { checkoutSessionId });
22
26
  return { success: true, promotion: response.promotion };
23
27
  }
24
28
  else {
@@ -47,6 +51,7 @@ export class PromotionsResource {
47
51
  try {
48
52
  const response = await this.apiClient.delete(`/api/v1/checkout-sessions/${checkoutSessionId}/promotions/${promotionId}`);
49
53
  if (response.success) {
54
+ void this.bus?.emit(PROMOTION_REMOVED, { checkoutSessionId });
50
55
  return { success: true };
51
56
  }
52
57
  else {
@@ -29,6 +29,11 @@ export interface ShippingRatesPreviewResponse {
29
29
  apiKey: string | null;
30
30
  };
31
31
  }
32
+ export interface SelectPreviewShippingRateResponse {
33
+ success: boolean;
34
+ selectedRateId: string | null;
35
+ rates: ShippingRate[];
36
+ }
32
37
  export declare class ShippingRatesResource {
33
38
  private apiClient;
34
39
  constructor(apiClient: ApiClient);
@@ -48,4 +53,17 @@ export declare class ShippingRatesResource {
48
53
  * Useful for showing rates before user enters email
49
54
  */
50
55
  previewShippingRates(sessionId: string, params: ShippingRatesPreviewParams): Promise<ShippingRatesPreviewResponse>;
56
+ /**
57
+ * Preview shipping rates for a country AND persist the auto-selected rate on the session.
58
+ *
59
+ * Difference with `previewShippingRates` (which is read-only):
60
+ * - the server picks the highlighted (or cheapest) rate for `countryCode`
61
+ * - it writes `checkoutSession.shippingRateId` to the DB
62
+ * - the next `getCheckout()` will return a summary that includes shipping cost
63
+ *
64
+ * Use this from the studio when geolocation (or any client-side hint) determines a country
65
+ * BEFORE the user submits the full address. Otherwise the wallet sheet (Apple Pay, etc.) will
66
+ * be opened with a stale total that doesn't include shipping fees.
67
+ */
68
+ selectPreviewShippingRate(sessionId: string, params: ShippingRatesPreviewParams): Promise<SelectPreviewShippingRateResponse>;
51
69
  }
@@ -31,4 +31,22 @@ export class ShippingRatesResource {
31
31
  }
32
32
  return this.apiClient.get(`/api/v1/checkout-sessions/${sessionId}/shipping-rates/preview?${queryParams.toString()}`);
33
33
  }
34
+ /**
35
+ * Preview shipping rates for a country AND persist the auto-selected rate on the session.
36
+ *
37
+ * Difference with `previewShippingRates` (which is read-only):
38
+ * - the server picks the highlighted (or cheapest) rate for `countryCode`
39
+ * - it writes `checkoutSession.shippingRateId` to the DB
40
+ * - the next `getCheckout()` will return a summary that includes shipping cost
41
+ *
42
+ * Use this from the studio when geolocation (or any client-side hint) determines a country
43
+ * BEFORE the user submits the full address. Otherwise the wallet sheet (Apple Pay, etc.) will
44
+ * be opened with a stale total that doesn't include shipping fees.
45
+ */
46
+ async selectPreviewShippingRate(sessionId, params) {
47
+ return this.apiClient.post(`/api/v1/checkout-sessions/${sessionId}/shipping-rates/select-preview`, {
48
+ countryCode: params.countryCode,
49
+ ...(params.stateCode ? { stateCode: params.stateCode } : {}),
50
+ });
51
+ }
34
52
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Click-ID resolver — pure, runtime-agnostic utility.
3
+ *
4
+ * Many ad-trackers (ClickFlare, Voluum, Binom, RedTrack, ClickMagick, …) all
5
+ * follow the same pattern: assign a `click_id` on the visitor's first ad-click
6
+ * on the lander, then expect that id to flow end-to-end (lander → checkout →
7
+ * thank-you) so they can match a server postback to the original visit.
8
+ *
9
+ * This module:
10
+ * - Resolves the click id from URL query params (preferred — survives the
11
+ * redirect chain) and first-party cookies (fallback — set by the tracker's
12
+ * lander script).
13
+ * - Publishes the resolved value to `window.TagadaPay.tracking` so merchant-
14
+ * authored postback snippets in `stepConfig.scripts` can read it without
15
+ * re-implementing URL/cookie scraping.
16
+ *
17
+ * No React, no SDK class dependencies — works from React provider, standalone
18
+ * SDK, the studio builder, the external-tracker bundle, or a raw <script>.
19
+ *
20
+ * The lists are intentionally explicit (not regexes) so we never accidentally
21
+ * promote a random param to a "click id" — they are the exact identifiers
22
+ * documented by each tracker / ad platform.
23
+ */
24
+ /** URL query-param names ad trackers / ad platforms use for click ids.
25
+ *
26
+ * Order matters — the resolver returns the FIRST matching entry, so put
27
+ * tracker-specific canonical names BEFORE generic fallbacks like
28
+ * `click_id` or `clickid`. This is why ClickFlare's `cf_click_id` comes
29
+ * before `click_id`, and why the per-tracker canonical token (`cid` for
30
+ * Voluum, `rtkclickid` for RedTrack, `cmc_tid` for ClickMagick) comes
31
+ * first within its own block.
32
+ */
33
+ export declare const CLICK_ID_URL_PARAMS: readonly ["cf_click_id", "cid", "rtkclickid", "cmc_tid", "cmc_id", "cmcid", "clickid", "click_id", "gclid", "gbraid", "wbraid", "fbclid", "msclkid", "ttclid", "twclid", "li_fat_id", "epik", "dclid", "yclid", "irclickid"];
34
+ /** Cookie names ad trackers store their click ids under.
35
+ *
36
+ * Same ordering rule as URL params: canonical first-party cookies first
37
+ * (e.g. `rtkclickid-store` for RedTrack), legacy fallbacks last.
38
+ */
39
+ export declare const CLICK_ID_COOKIES: readonly ["cf_click_id", "cfclid", "rtkclickid-store", "_rtkclickid", "rtkclickid", "_voluum", "_voluumclickid", "_binom", "_binomclickid", "_mck", "cmcid", "skro-click-id", "click_id"];
40
+ export type ClickIdSource = 'url' | 'cookie';
41
+ export interface ResolvedClickId {
42
+ /** The first matching click id, or null. */
43
+ clickId: string | null;
44
+ /** Where it was sourced from. */
45
+ source: ClickIdSource | null;
46
+ /** Which key (param name or cookie name) matched. */
47
+ key: string | null;
48
+ /** Every click-id-shaped value found, namespaced as `url:foo` / `cookie:bar`. */
49
+ all: Record<string, string>;
50
+ }
51
+ export interface TagadaTrackingGlobal extends ResolvedClickId {
52
+ /** ms since epoch when the resolver last ran. */
53
+ resolvedAt: number;
54
+ }
55
+ declare global {
56
+ interface Window {
57
+ TagadaPay?: {
58
+ tracking?: TagadaTrackingGlobal;
59
+ [key: string]: unknown;
60
+ };
61
+ }
62
+ }
63
+ /**
64
+ * Resolve the visitor's click id. Pure — no side effects, safe in SSR.
65
+ *
66
+ * Resolution order:
67
+ * 1. URL params (in CLICK_ID_URL_PARAMS order) — most authoritative.
68
+ * 2. Cookies (in CLICK_ID_COOKIES order) — fallback.
69
+ */
70
+ export declare function resolveClickId(): ResolvedClickId;
71
+ /**
72
+ * Resolve and merge tracking metadata into `window.TagadaPay.tracking`.
73
+ *
74
+ * Idempotent and namespace-safe: we only ever touch the `tracking` key, never
75
+ * other namespaces (e.g. `order` set by the thank-you page).
76
+ *
77
+ * Returns the published value, or `null` when called outside a browser.
78
+ */
79
+ export declare function publishTrackingGlobal(): TagadaTrackingGlobal | null;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Click-ID resolver — pure, runtime-agnostic utility.
3
+ *
4
+ * Many ad-trackers (ClickFlare, Voluum, Binom, RedTrack, ClickMagick, …) all
5
+ * follow the same pattern: assign a `click_id` on the visitor's first ad-click
6
+ * on the lander, then expect that id to flow end-to-end (lander → checkout →
7
+ * thank-you) so they can match a server postback to the original visit.
8
+ *
9
+ * This module:
10
+ * - Resolves the click id from URL query params (preferred — survives the
11
+ * redirect chain) and first-party cookies (fallback — set by the tracker's
12
+ * lander script).
13
+ * - Publishes the resolved value to `window.TagadaPay.tracking` so merchant-
14
+ * authored postback snippets in `stepConfig.scripts` can read it without
15
+ * re-implementing URL/cookie scraping.
16
+ *
17
+ * No React, no SDK class dependencies — works from React provider, standalone
18
+ * SDK, the studio builder, the external-tracker bundle, or a raw <script>.
19
+ *
20
+ * The lists are intentionally explicit (not regexes) so we never accidentally
21
+ * promote a random param to a "click id" — they are the exact identifiers
22
+ * documented by each tracker / ad platform.
23
+ */
24
+ // -----------------------------------------------------------------------------
25
+ // Configuration — explicit allow-lists
26
+ // -----------------------------------------------------------------------------
27
+ /** URL query-param names ad trackers / ad platforms use for click ids.
28
+ *
29
+ * Order matters — the resolver returns the FIRST matching entry, so put
30
+ * tracker-specific canonical names BEFORE generic fallbacks like
31
+ * `click_id` or `clickid`. This is why ClickFlare's `cf_click_id` comes
32
+ * before `click_id`, and why the per-tracker canonical token (`cid` for
33
+ * Voluum, `rtkclickid` for RedTrack, `cmc_tid` for ClickMagick) comes
34
+ * first within its own block.
35
+ */
36
+ export const CLICK_ID_URL_PARAMS = Object.freeze([
37
+ // Postback ad-trackers (most specific first)
38
+ 'cf_click_id', // ClickFlare canonical
39
+ 'cid', // Voluum canonical (also: ClickFlare receiving-side)
40
+ 'rtkclickid', // RedTrack canonical
41
+ 'cmc_tid', // ClickMagick canonical (replaces legacy #S2#)
42
+ 'cmc_id', // ClickMagick legacy
43
+ 'cmcid', // ClickMagick alt
44
+ 'clickid', // Generic affiliate-network token (Binom, RedTrack, Voluum, …)
45
+ 'click_id', // Generic snake_case (ClickFlare, ClickMagick, …)
46
+ // Ad-platform native click ids
47
+ 'gclid', // Google Ads
48
+ 'gbraid', // Google Ads (iOS app)
49
+ 'wbraid', // Google Ads (web→app)
50
+ 'fbclid', // Meta
51
+ 'msclkid', // Microsoft Ads
52
+ 'ttclid', // TikTok
53
+ 'twclid', // X / Twitter
54
+ 'li_fat_id', // LinkedIn
55
+ 'epik', // Pinterest
56
+ 'dclid', // Display & Video 360
57
+ 'yclid', // Yandex
58
+ 'irclickid', // Impact
59
+ ]);
60
+ /** Cookie names ad trackers store their click ids under.
61
+ *
62
+ * Same ordering rule as URL params: canonical first-party cookies first
63
+ * (e.g. `rtkclickid-store` for RedTrack), legacy fallbacks last.
64
+ */
65
+ export const CLICK_ID_COOKIES = Object.freeze([
66
+ // ClickFlare
67
+ 'cf_click_id',
68
+ 'cfclid',
69
+ // RedTrack — `rtkclickid-store` is the canonical first-party cookie
70
+ // set by RedTrack's Universal Tracking Script.
71
+ 'rtkclickid-store',
72
+ '_rtkclickid',
73
+ 'rtkclickid',
74
+ // Voluum
75
+ '_voluum',
76
+ '_voluumclickid',
77
+ // Binom
78
+ '_binom',
79
+ '_binomclickid',
80
+ // ClickMagick
81
+ '_mck',
82
+ 'cmcid',
83
+ // Other
84
+ 'skro-click-id', // Skro
85
+ 'click_id', // generic catch-all (last)
86
+ ]);
87
+ // -----------------------------------------------------------------------------
88
+ // Pure resolution
89
+ // -----------------------------------------------------------------------------
90
+ function readCookie(name) {
91
+ if (typeof document === 'undefined' || !document.cookie)
92
+ return null;
93
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
94
+ const m = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));
95
+ if (!m)
96
+ return null;
97
+ try {
98
+ return decodeURIComponent(m[1]);
99
+ }
100
+ catch {
101
+ return m[1];
102
+ }
103
+ }
104
+ function readUrlParam(search, name) {
105
+ if (!search)
106
+ return null;
107
+ try {
108
+ return new URLSearchParams(search).get(name);
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Resolve the visitor's click id. Pure — no side effects, safe in SSR.
116
+ *
117
+ * Resolution order:
118
+ * 1. URL params (in CLICK_ID_URL_PARAMS order) — most authoritative.
119
+ * 2. Cookies (in CLICK_ID_COOKIES order) — fallback.
120
+ */
121
+ export function resolveClickId() {
122
+ const all = {};
123
+ let clickId = null;
124
+ let source = null;
125
+ let key = null;
126
+ const search = typeof window !== 'undefined' ? window.location?.search ?? '' : '';
127
+ for (const name of CLICK_ID_URL_PARAMS) {
128
+ const v = readUrlParam(search, name);
129
+ if (v) {
130
+ all[`url:${name}`] = v;
131
+ if (clickId === null) {
132
+ clickId = v;
133
+ source = 'url';
134
+ key = name;
135
+ }
136
+ }
137
+ }
138
+ for (const name of CLICK_ID_COOKIES) {
139
+ const v = readCookie(name);
140
+ if (v) {
141
+ all[`cookie:${name}`] = v;
142
+ if (clickId === null) {
143
+ clickId = v;
144
+ source = 'cookie';
145
+ key = name;
146
+ }
147
+ }
148
+ }
149
+ return { clickId, source, key, all };
150
+ }
151
+ // -----------------------------------------------------------------------------
152
+ // Browser-side publication
153
+ // -----------------------------------------------------------------------------
154
+ /**
155
+ * Resolve and merge tracking metadata into `window.TagadaPay.tracking`.
156
+ *
157
+ * Idempotent and namespace-safe: we only ever touch the `tracking` key, never
158
+ * other namespaces (e.g. `order` set by the thank-you page).
159
+ *
160
+ * Returns the published value, or `null` when called outside a browser.
161
+ */
162
+ export function publishTrackingGlobal() {
163
+ if (typeof window === 'undefined')
164
+ return null;
165
+ const resolved = resolveClickId();
166
+ const tracking = { ...resolved, resolvedAt: Date.now() };
167
+ window.TagadaPay = { ...(window.TagadaPay ?? {}), tracking };
168
+ return tracking;
169
+ }
@@ -13,3 +13,5 @@ export * from './orderBump';
13
13
  export * from './sessionStorage';
14
14
  export * from './funnelQueryKeys';
15
15
  export * from './configHotReload';
16
+ export * from './metaEventId';
17
+ export * from './clickIdResolver';
@@ -14,3 +14,7 @@ export * from './sessionStorage';
14
14
  export * from './funnelQueryKeys';
15
15
  // Config hot reload for live preview editing
16
16
  export * from './configHotReload';
17
+ // Meta CAPI / browser pixel deduplication helper.
18
+ export * from './metaEventId';
19
+ // Click-id resolver for ad-tracker integrations (ClickFlare, Voluum, Binom, …)
20
+ export * from './clickIdResolver';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Stable event_id helper for Meta CAPI / browser pixel deduplication.
3
+ *
4
+ * The browser side (`fbq('track', name, params, { eventID })`) and the server
5
+ * side (`ServerEvent.setEventId(...)`) must publish the same value to let Meta
6
+ * dedup. This helper is the single source of truth for the formula, mirrored
7
+ * on the server in `src/app/services/meta/meta-event-id.ts`. A parity test in
8
+ * the server package keeps them byte-identical.
9
+ *
10
+ * Convention: `${eventName}_${entityId}`. Different event names get different
11
+ * IDs even when fired for the same entity (e.g. a Purchase + Subscribe pair on
12
+ * the same order), so each dedups independently.
13
+ */
14
+ export declare function makeMetaEventId(eventName: string, entityId: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Stable event_id helper for Meta CAPI / browser pixel deduplication.
3
+ *
4
+ * The browser side (`fbq('track', name, params, { eventID })`) and the server
5
+ * side (`ServerEvent.setEventId(...)`) must publish the same value to let Meta
6
+ * dedup. This helper is the single source of truth for the formula, mirrored
7
+ * on the server in `src/app/services/meta/meta-event-id.ts`. A parity test in
8
+ * the server package keeps them byte-identical.
9
+ *
10
+ * Convention: `${eventName}_${entityId}`. Different event names get different
11
+ * IDs even when fired for the same entity (e.g. a Purchase + Subscribe pair on
12
+ * the same order), so each dedups independently.
13
+ */
14
+ export function makeMetaEventId(eventName, entityId) {
15
+ return `${eventName}_${entityId}`;
16
+ }