@tagadapay/plugin-sdk 4.0.7 → 4.1.0
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/dist/external-tracker.js +103 -36
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +3 -3
- package/dist/react/types.d.ts +2 -2
- package/dist/tagada-react-sdk-minimal.min.js +2 -2
- package/dist/tagada-react-sdk-minimal.min.js.map +3 -3
- package/dist/tagada-react-sdk.js +148 -28
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +125 -45
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/funnelClient.js +14 -9
- package/dist/v2/core/pixelMapping.d.ts +84 -0
- package/dist/v2/core/pixelMapping.js +102 -0
- package/dist/v2/core/pixelTracker.d.ts +1 -6
- package/dist/v2/core/pixelTracker.js +36 -2
- package/dist/v2/core/resources/credits.d.ts +13 -0
- package/dist/v2/core/resources/credits.js +7 -0
- package/dist/v2/core/resources/offers.d.ts +5 -1
- package/dist/v2/core/resources/offers.js +3 -2
- package/dist/v2/core/resources/payments.d.ts +1 -0
- package/dist/v2/core/resources/payments.js +1 -0
- package/dist/v2/core/types.d.ts +17 -2
- package/dist/v2/core/utils/authHandoff.d.ts +2 -1
- package/dist/v2/index.d.ts +3 -1
- package/dist/v2/index.js +4 -1
- package/dist/v2/react/components/FunnelScriptInjector.js +42 -7
- package/dist/v2/react/hooks/useAuth.d.ts +1 -0
- package/dist/v2/react/hooks/useAuth.js +1 -0
- package/dist/v2/react/hooks/useClubOffers.d.ts +16 -0
- package/dist/v2/react/hooks/useClubOffers.js +29 -3
- package/dist/v2/react/hooks/useCustomer.d.ts +1 -0
- package/dist/v2/react/hooks/useCustomer.js +1 -0
- package/dist/v2/react/hooks/useStore.d.ts +5 -0
- package/dist/v2/react/hooks/useStore.js +16 -0
- package/dist/v2/react/index.d.ts +1 -0
- package/dist/v2/react/index.js +1 -0
- package/dist/v2/standalone/index.js +134 -46
- package/dist/v2/standalone/payment-service.d.ts +2 -1
- package/dist/v2/standalone/payment-service.js +6 -4
- package/package.json +2 -3
|
@@ -352,16 +352,21 @@ export function getAssignedScripts(position) {
|
|
|
352
352
|
* Split a pixel config with semicolon/comma-separated IDs into individual configs.
|
|
353
353
|
* Handles cases where the CRM stores "ID1; ID2; ID3" as a single pixelId or containerId.
|
|
354
354
|
*/
|
|
355
|
+
// Split "ID1; ID2, ID3" into trimmed, non-empty IDs. Always trims, so even a
|
|
356
|
+
// single ID with leading/trailing whitespace is cleaned.
|
|
357
|
+
function splitIds(rawId) {
|
|
358
|
+
if (!rawId)
|
|
359
|
+
return [];
|
|
360
|
+
return rawId.split(/[;,]/).map((id) => id.trim()).filter((id) => id.length > 0);
|
|
361
|
+
}
|
|
355
362
|
function splitPixelConfig(px) {
|
|
356
|
-
// GTM configs
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const ids =
|
|
362
|
-
|
|
363
|
-
return [px];
|
|
364
|
-
return ids.map((id) => ({ ...px, [idField]: id }));
|
|
363
|
+
// GTM configs key off containerId; every other provider uses pixelId.
|
|
364
|
+
if ('containerId' in px) {
|
|
365
|
+
const ids = splitIds(px.containerId);
|
|
366
|
+
return ids.length === 0 ? [px] : ids.map((id) => ({ ...px, containerId: id }));
|
|
367
|
+
}
|
|
368
|
+
const ids = splitIds(px.pixelId);
|
|
369
|
+
return ids.length === 0 ? [px] : ids.map((id) => ({ ...px, pixelId: id }));
|
|
365
370
|
}
|
|
366
371
|
export function getAssignedPixels() {
|
|
367
372
|
const stepConfig = getAssignedStepConfig();
|
|
@@ -18,6 +18,89 @@ export interface MappedEvent {
|
|
|
18
18
|
name: string;
|
|
19
19
|
params: Record<string, unknown>;
|
|
20
20
|
}
|
|
21
|
+
export declare const GA4_PARAM_KEY: "_ga4";
|
|
22
|
+
export interface Ga4UserData {
|
|
23
|
+
email_address?: string;
|
|
24
|
+
phone_number?: string;
|
|
25
|
+
address?: {
|
|
26
|
+
first_name?: string;
|
|
27
|
+
last_name?: string;
|
|
28
|
+
street?: string;
|
|
29
|
+
city?: string;
|
|
30
|
+
region?: string;
|
|
31
|
+
postal_code?: string;
|
|
32
|
+
country?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface Ga4Item {
|
|
36
|
+
item_id?: string;
|
|
37
|
+
item_name?: string;
|
|
38
|
+
item_variant?: string;
|
|
39
|
+
/** Minor units (cents). Converted to major by mapGTMEvent. */
|
|
40
|
+
price?: number;
|
|
41
|
+
quantity?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface Ga4PurchasePayload {
|
|
44
|
+
user_id?: string;
|
|
45
|
+
user_data?: Ga4UserData;
|
|
46
|
+
/** Minor units (cents). */
|
|
47
|
+
value?: number;
|
|
48
|
+
/** Minor units (cents). */
|
|
49
|
+
tax?: number;
|
|
50
|
+
/** Minor units (cents). */
|
|
51
|
+
shipping?: number;
|
|
52
|
+
items: Ga4Item[];
|
|
53
|
+
}
|
|
54
|
+
/** Loose order shape accepted by buildGa4PurchasePayload (subset of SDK Order). */
|
|
55
|
+
export interface Ga4OrderInput {
|
|
56
|
+
id?: string;
|
|
57
|
+
currency?: string;
|
|
58
|
+
paidAmount?: number;
|
|
59
|
+
items?: Array<{
|
|
60
|
+
productId?: string;
|
|
61
|
+
variantId?: string | null;
|
|
62
|
+
quantity?: number;
|
|
63
|
+
adjustedAmount?: number;
|
|
64
|
+
orderLineItemProduct?: {
|
|
65
|
+
name?: string;
|
|
66
|
+
};
|
|
67
|
+
orderLineItemVariant?: {
|
|
68
|
+
name?: string;
|
|
69
|
+
};
|
|
70
|
+
}>;
|
|
71
|
+
summaries?: Array<{
|
|
72
|
+
currency?: string;
|
|
73
|
+
totalTaxAmount?: number;
|
|
74
|
+
shippingCost?: number;
|
|
75
|
+
totalAdjustedAmount?: number;
|
|
76
|
+
}>;
|
|
77
|
+
customer?: {
|
|
78
|
+
id?: string;
|
|
79
|
+
email?: string;
|
|
80
|
+
phone?: string;
|
|
81
|
+
firstName?: string;
|
|
82
|
+
lastName?: string;
|
|
83
|
+
};
|
|
84
|
+
billingAddress?: Ga4Address;
|
|
85
|
+
shippingAddress?: Ga4Address;
|
|
86
|
+
}
|
|
87
|
+
interface Ga4Address {
|
|
88
|
+
firstName?: string;
|
|
89
|
+
lastName?: string;
|
|
90
|
+
address1?: string;
|
|
91
|
+
city?: string;
|
|
92
|
+
state?: string;
|
|
93
|
+
postal?: string;
|
|
94
|
+
country?: string;
|
|
95
|
+
phone?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the GA4-only enrichment payload (`_ga4`) from an order. Pure, defensive,
|
|
99
|
+
* framework-agnostic — shared by the native ThankYouPage and the Studio
|
|
100
|
+
* OrderConfirmation island so both emit an identical canonical GA4 purchase.
|
|
101
|
+
* Returns minor-unit amounts; mapGTMEvent converts them to major units.
|
|
102
|
+
*/
|
|
103
|
+
export declare function buildGa4PurchasePayload(order: Ga4OrderInput): Ga4PurchasePayload;
|
|
21
104
|
/**
|
|
22
105
|
* Returns `true` if the given event is enabled on the pixel config.
|
|
23
106
|
* If the pixel has no `events` map (e.g. MetaConversionTrackingConfig) we allow all.
|
|
@@ -47,3 +130,4 @@ export interface ProviderEvent {
|
|
|
47
130
|
* for every eligible (enabled + event-toggled-on) pixel.
|
|
48
131
|
*/
|
|
49
132
|
export declare function resolvePixelEvents(pixels: PixelsConfig, eventName: StandardPixelEvent, parameters: Record<string, unknown>): ProviderEvent[];
|
|
133
|
+
export {};
|
|
@@ -12,6 +12,71 @@
|
|
|
12
12
|
* GTM/GA4: gtag('event', 'purchase', { value, currency, items })
|
|
13
13
|
*/
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
+
// GA4-only enrichment
|
|
16
|
+
//
|
|
17
|
+
// Canonical GA4 ecommerce `purchase` data carries user identity + a nested
|
|
18
|
+
// `ecommerce` object that the flat Meta/TikTok payload does not. We attach it
|
|
19
|
+
// to the shared track() params under the reserved `_ga4` key. ONLY mapGTMEvent
|
|
20
|
+
// reads it; `baseTransform` strips it so it can never leak into the
|
|
21
|
+
// Meta/TikTok/Snapchat/Pinterest payloads (those would otherwise spread it,
|
|
22
|
+
// shipping unhashed PII to those pixels). All monetary fields are in MINOR
|
|
23
|
+
// units here — mapGTMEvent converts to major via the same currency helpers.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const GA4_PARAM_KEY = '_ga4';
|
|
26
|
+
/**
|
|
27
|
+
* Build the GA4-only enrichment payload (`_ga4`) from an order. Pure, defensive,
|
|
28
|
+
* framework-agnostic — shared by the native ThankYouPage and the Studio
|
|
29
|
+
* OrderConfirmation island so both emit an identical canonical GA4 purchase.
|
|
30
|
+
* Returns minor-unit amounts; mapGTMEvent converts them to major units.
|
|
31
|
+
*/
|
|
32
|
+
export function buildGa4PurchasePayload(order) {
|
|
33
|
+
const items = (order.items ?? []).map((item) => ({
|
|
34
|
+
item_id: item.productId || undefined,
|
|
35
|
+
// GA4 convention: item_name = product, item_variant = variant.
|
|
36
|
+
item_name: item.orderLineItemProduct?.name || item.orderLineItemVariant?.name || undefined,
|
|
37
|
+
item_variant: item.orderLineItemVariant?.name || undefined,
|
|
38
|
+
price: item.adjustedAmount,
|
|
39
|
+
quantity: item.quantity,
|
|
40
|
+
}));
|
|
41
|
+
const summary = order.summaries?.find((s) => s.currency === order.currency) ?? order.summaries?.[0];
|
|
42
|
+
// Prefer the captured paid amount; fall back to the summary total, then to
|
|
43
|
+
// the sum of line items. Guards against the value:0 seen in the field.
|
|
44
|
+
const itemsSum = (order.items ?? []).reduce((sum, i) => sum + (i.adjustedAmount ?? 0) * (i.quantity ?? 0), 0);
|
|
45
|
+
const value = order.paidAmount && order.paidAmount > 0
|
|
46
|
+
? order.paidAmount
|
|
47
|
+
: (summary?.totalAdjustedAmount ?? itemsSum);
|
|
48
|
+
const payload = { items, value };
|
|
49
|
+
if (summary?.totalTaxAmount != null)
|
|
50
|
+
payload.tax = summary.totalTaxAmount;
|
|
51
|
+
if (summary?.shippingCost != null)
|
|
52
|
+
payload.shipping = summary.shippingCost;
|
|
53
|
+
if (order.customer?.id)
|
|
54
|
+
payload.user_id = order.customer.id;
|
|
55
|
+
const addr = order.billingAddress ?? order.shippingAddress;
|
|
56
|
+
const userData = {};
|
|
57
|
+
if (order.customer?.email)
|
|
58
|
+
userData.email_address = order.customer.email;
|
|
59
|
+
const phone = order.customer?.phone || addr?.phone;
|
|
60
|
+
if (phone)
|
|
61
|
+
userData.phone_number = phone;
|
|
62
|
+
if (addr) {
|
|
63
|
+
const address = {
|
|
64
|
+
first_name: addr.firstName || order.customer?.firstName || undefined,
|
|
65
|
+
last_name: addr.lastName || order.customer?.lastName || undefined,
|
|
66
|
+
street: addr.address1 || undefined,
|
|
67
|
+
city: addr.city || undefined,
|
|
68
|
+
region: addr.state || undefined,
|
|
69
|
+
postal_code: addr.postal || undefined,
|
|
70
|
+
country: addr.country || undefined,
|
|
71
|
+
};
|
|
72
|
+
if (Object.values(address).some((v) => v != null))
|
|
73
|
+
userData.address = address;
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(userData).length > 0)
|
|
76
|
+
payload.user_data = userData;
|
|
77
|
+
return payload;
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
15
80
|
// Currency conversion helper (inline, zero-dep)
|
|
16
81
|
// ---------------------------------------------------------------------------
|
|
17
82
|
/**
|
|
@@ -109,6 +174,12 @@ function baseTransform(params) {
|
|
|
109
174
|
p = convertValueToMajor(p);
|
|
110
175
|
p = convertMonetaryFieldsToMajor(p);
|
|
111
176
|
p = convertContentsPricesToMajor(p);
|
|
177
|
+
// Strip GA4-only enrichment so it never reaches Meta/TikTok/Snapchat/Pinterest
|
|
178
|
+
// (those mappers spread all params). mapGTMEvent reads it from the raw input.
|
|
179
|
+
if (GA4_PARAM_KEY in p) {
|
|
180
|
+
p = { ...p };
|
|
181
|
+
delete p[GA4_PARAM_KEY];
|
|
182
|
+
}
|
|
112
183
|
return p;
|
|
113
184
|
}
|
|
114
185
|
// ---------------------------------------------------------------------------
|
|
@@ -291,6 +362,37 @@ const GTM_EVENT_MAP = {
|
|
|
291
362
|
};
|
|
292
363
|
export function mapGTMEvent(eventName, parameters) {
|
|
293
364
|
const name = GTM_EVENT_MAP[eventName] ?? eventName.toLowerCase();
|
|
365
|
+
// Canonical GA4 ecommerce shape: when the caller attached `_ga4` enrichment
|
|
366
|
+
// (purchase from the thank-you page) emit a nested `ecommerce` object plus
|
|
367
|
+
// user identity instead of the legacy flat payload. baseTransform strips
|
|
368
|
+
// `_ga4`, so read it from the raw input first.
|
|
369
|
+
const ga4 = parameters[GA4_PARAM_KEY];
|
|
370
|
+
if (ga4) {
|
|
371
|
+
const currency = typeof parameters.currency === 'string' ? parameters.currency.toUpperCase() : undefined;
|
|
372
|
+
const toMajor = (v) => v == null || !currency ? v : minorToMajor(Number(v), currency);
|
|
373
|
+
const ecommerce = {
|
|
374
|
+
transaction_id: parameters.transaction_id,
|
|
375
|
+
currency,
|
|
376
|
+
value: toMajor(ga4.value),
|
|
377
|
+
};
|
|
378
|
+
if (ga4.tax != null)
|
|
379
|
+
ecommerce.tax = toMajor(ga4.tax);
|
|
380
|
+
if (ga4.shipping != null)
|
|
381
|
+
ecommerce.shipping = toMajor(ga4.shipping);
|
|
382
|
+
ecommerce.items = (ga4.items ?? []).map((item) => ({
|
|
383
|
+
item_id: item.item_id,
|
|
384
|
+
item_name: item.item_name,
|
|
385
|
+
item_variant: item.item_variant,
|
|
386
|
+
price: toMajor(item.price),
|
|
387
|
+
quantity: item.quantity != null ? Number(item.quantity) : undefined,
|
|
388
|
+
}));
|
|
389
|
+
const out = { ecommerce };
|
|
390
|
+
if (ga4.user_id)
|
|
391
|
+
out.user_id = ga4.user_id;
|
|
392
|
+
if (ga4.user_data)
|
|
393
|
+
out.user_data = ga4.user_data;
|
|
394
|
+
return { name, params: out };
|
|
395
|
+
}
|
|
294
396
|
const params = baseTransform(parameters);
|
|
295
397
|
if (params.num_items !== undefined) {
|
|
296
398
|
params.num_items = Number(params.num_items);
|
|
@@ -28,12 +28,7 @@ export interface PixelTracker {
|
|
|
28
28
|
/** Track a standard pixel event. No-ops until init resolves. */
|
|
29
29
|
track(eventName: StandardPixelEvent, parameters?: Record<string, unknown>, options?: TrackOptions): void;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
* Create a pixel tracker for a given pixels config.
|
|
33
|
-
* Kicks off pixel script init immediately, fires PageView once init resolves,
|
|
34
|
-
* and returns a `track()` function that no-ops until init completes.
|
|
35
|
-
*/
|
|
36
|
-
export declare function createPixelTracker(pixels: PixelsConfig | undefined): PixelTracker;
|
|
31
|
+
export declare function createPixelTracker(rawPixels: PixelsConfig | undefined): PixelTracker;
|
|
37
32
|
declare global {
|
|
38
33
|
interface Window {
|
|
39
34
|
__TGD_PIXEL_TRACKER__?: PixelTracker;
|
|
@@ -86,10 +86,23 @@ function fire(provider, name, params, pixel, options = {}) {
|
|
|
86
86
|
function fireGTM(event, params) {
|
|
87
87
|
try {
|
|
88
88
|
const w = window;
|
|
89
|
+
const hasEcommerce = params && typeof params === 'object' && 'ecommerce' in params;
|
|
89
90
|
if (w.gtag) {
|
|
90
|
-
|
|
91
|
+
// gtag.js (GA4 config tag) reads ecommerce fields at the top level of the
|
|
92
|
+
// event, not under a nested `ecommerce` key — flatten it back out.
|
|
93
|
+
if (hasEcommerce) {
|
|
94
|
+
const { ecommerce, ...rest } = params;
|
|
95
|
+
w.gtag('event', event, { ...rest, ...ecommerce });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
w.gtag('event', event, params);
|
|
99
|
+
}
|
|
91
100
|
}
|
|
92
101
|
else if (w.dataLayer) {
|
|
102
|
+
// GA4 best practice: clear the previous ecommerce object before pushing a
|
|
103
|
+
// new one so item arrays from earlier events don't bleed through.
|
|
104
|
+
if (hasEcommerce)
|
|
105
|
+
w.dataLayer.push({ ecommerce: null });
|
|
93
106
|
w.dataLayer.push({ event, ...params });
|
|
94
107
|
}
|
|
95
108
|
}
|
|
@@ -323,7 +336,28 @@ function initGTM(containerId) {
|
|
|
323
336
|
* Kicks off pixel script init immediately, fires PageView once init resolves,
|
|
324
337
|
* and returns a `track()` function that no-ops until init completes.
|
|
325
338
|
*/
|
|
326
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Trim leading/trailing whitespace from every pixel/container ID. Mutating a
|
|
341
|
+
* copy here means both init and per-pixel event targeting (TikTok ttq.instance)
|
|
342
|
+
* use the clean ID, even when the CRM stored an ID with stray spaces.
|
|
343
|
+
*/
|
|
344
|
+
function trimPixelIds(pixels) {
|
|
345
|
+
const out = {};
|
|
346
|
+
for (const [provider, list] of Object.entries(pixels)) {
|
|
347
|
+
if (!Array.isArray(list))
|
|
348
|
+
continue;
|
|
349
|
+
out[provider] = list.map((px) => {
|
|
350
|
+
// GTM configs key off containerId; every other provider uses pixelId.
|
|
351
|
+
if ('containerId' in px) {
|
|
352
|
+
return typeof px.containerId === 'string' ? { ...px, containerId: px.containerId.trim() } : px;
|
|
353
|
+
}
|
|
354
|
+
return typeof px.pixelId === 'string' ? { ...px, pixelId: px.pixelId.trim() } : px;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
export function createPixelTracker(rawPixels) {
|
|
360
|
+
const pixels = rawPixels ? trimPixelIds(rawPixels) : rawPixels;
|
|
327
361
|
const shouldTrackEvent = createDuplicateGuard(5000);
|
|
328
362
|
let pixelsInitialized = false;
|
|
329
363
|
const initPromise = (async () => {
|
|
@@ -24,6 +24,12 @@ export interface RedeemProductResponse {
|
|
|
24
24
|
creditsSpent: number;
|
|
25
25
|
remainingCredits: number;
|
|
26
26
|
}
|
|
27
|
+
export interface RedeemOfferResponse {
|
|
28
|
+
success: boolean;
|
|
29
|
+
orderId: string;
|
|
30
|
+
creditsSpent: number;
|
|
31
|
+
remainingCredits: number;
|
|
32
|
+
}
|
|
27
33
|
export declare class CreditsResource {
|
|
28
34
|
private apiClient;
|
|
29
35
|
constructor(apiClient: ApiClient);
|
|
@@ -39,4 +45,11 @@ export declare class CreditsResource {
|
|
|
39
45
|
variantId: string;
|
|
40
46
|
quantity?: number;
|
|
41
47
|
}): Promise<RedeemProductResponse>;
|
|
48
|
+
/**
|
|
49
|
+
* Redeem an offer (its full bundle of line items) using credits.
|
|
50
|
+
* customerId/storeId/accountId are derived server-side from the verified session.
|
|
51
|
+
*/
|
|
52
|
+
redeemOffer(data: {
|
|
53
|
+
offerId: string;
|
|
54
|
+
}): Promise<RedeemOfferResponse>;
|
|
42
55
|
}
|
|
@@ -18,4 +18,11 @@ export class CreditsResource {
|
|
|
18
18
|
async redeemProduct(data) {
|
|
19
19
|
return this.apiClient.post('/api/v1/credits/redeem', data);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Redeem an offer (its full bundle of line items) using credits.
|
|
23
|
+
* customerId/storeId/accountId are derived server-side from the verified session.
|
|
24
|
+
*/
|
|
25
|
+
async redeemOffer(data) {
|
|
26
|
+
return this.apiClient.post('/api/v1/credits/redeem-offer', data);
|
|
27
|
+
}
|
|
21
28
|
}
|
|
@@ -225,6 +225,7 @@ export declare class OffersResource {
|
|
|
225
225
|
productId?: string;
|
|
226
226
|
variantId: string;
|
|
227
227
|
quantity: number;
|
|
228
|
+
priceId?: string;
|
|
228
229
|
}>): Promise<any>;
|
|
229
230
|
/**
|
|
230
231
|
* Preview and pay for an offer with variant selections
|
|
@@ -240,6 +241,7 @@ export declare class OffersResource {
|
|
|
240
241
|
productId?: string;
|
|
241
242
|
variantId: string;
|
|
242
243
|
quantity: number;
|
|
244
|
+
priceId?: string;
|
|
243
245
|
}>, returnUrl?: string, mainOrderId?: string, initiatedBy?: 'merchant' | 'customer'): Promise<{
|
|
244
246
|
preview: any;
|
|
245
247
|
checkout: {
|
|
@@ -262,6 +264,7 @@ export declare class OffersResource {
|
|
|
262
264
|
productId?: string;
|
|
263
265
|
variantId: string;
|
|
264
266
|
quantity: number;
|
|
267
|
+
priceId?: string;
|
|
265
268
|
}>, returnUrl?: string, mainOrderId?: string): Promise<{
|
|
266
269
|
checkoutSessionId?: string;
|
|
267
270
|
checkoutToken?: string;
|
|
@@ -290,7 +293,7 @@ export declare class OffersResource {
|
|
|
290
293
|
/**
|
|
291
294
|
* Pay for an offer directly
|
|
292
295
|
*/
|
|
293
|
-
payOffer(offerId: string, orderId?: string, initiatedBy?: 'merchant' | 'customer'): Promise<any>;
|
|
296
|
+
payOffer(offerId: string, orderId?: string, initiatedBy?: 'merchant' | 'customer', customerId?: string): Promise<any>;
|
|
294
297
|
/**
|
|
295
298
|
* Transform offer to checkout session with dynamic variant selection
|
|
296
299
|
* Uses lineItems from the offer to create a new checkout session
|
|
@@ -324,6 +327,7 @@ export declare class OffersResource {
|
|
|
324
327
|
productId?: string;
|
|
325
328
|
variantId: string;
|
|
326
329
|
quantity: number;
|
|
330
|
+
priceId?: string;
|
|
327
331
|
}>, returnUrl?: string, mainOrderId?: string): Promise<{
|
|
328
332
|
checkoutToken: string;
|
|
329
333
|
customerId: string;
|
|
@@ -173,14 +173,15 @@ export class OffersResource {
|
|
|
173
173
|
/**
|
|
174
174
|
* Pay for an offer directly
|
|
175
175
|
*/
|
|
176
|
-
async payOffer(offerId, orderId, initiatedBy) {
|
|
176
|
+
async payOffer(offerId, orderId, initiatedBy, customerId) {
|
|
177
177
|
// 🎯 Check draft mode from URL, localStorage, or cookie
|
|
178
178
|
const draft = isDraftMode();
|
|
179
179
|
const effectiveInitiatedBy = initiatedBy ?? getAssignedPaymentInitiator();
|
|
180
180
|
return this.apiClient.post(`/api/v1/offers/${offerId}/pay`, {
|
|
181
181
|
offerId,
|
|
182
|
-
draft,
|
|
182
|
+
draft,
|
|
183
183
|
returnUrl: typeof window !== 'undefined' ? window.location.href : '',
|
|
184
|
+
...(customerId ? { customerId } : {}),
|
|
184
185
|
...(effectiveInitiatedBy ? { initiatedBy: effectiveInitiatedBy } : {}),
|
|
185
186
|
metadata: orderId ? {
|
|
186
187
|
comingFromPostPurchase: true,
|
|
@@ -147,6 +147,7 @@ export class PaymentsResource {
|
|
|
147
147
|
...(options.paymentFlowId && { paymentFlowId: options.paymentFlowId }),
|
|
148
148
|
...(options.processorId && { processorId: options.processorId }),
|
|
149
149
|
...(options.paymentMethod && { paymentMethod: options.paymentMethod }),
|
|
150
|
+
...(options.blikCode && { blikCode: options.blikCode }),
|
|
150
151
|
...(options.isExpress && { isExpress: options.isExpress }),
|
|
151
152
|
...(options.shippingRateId && { shippingRateId: options.shippingRateId }),
|
|
152
153
|
};
|
package/dist/v2/core/types.d.ts
CHANGED
|
@@ -34,9 +34,9 @@ export interface Customer {
|
|
|
34
34
|
lastName?: string;
|
|
35
35
|
phone?: string;
|
|
36
36
|
isAuthenticated: boolean;
|
|
37
|
-
role:
|
|
37
|
+
role: SessionRole;
|
|
38
38
|
}
|
|
39
|
-
export type SessionRole = 'authenticated' | 'anonymous';
|
|
39
|
+
export type SessionRole = 'authenticated' | 'anonymous' | 'verified';
|
|
40
40
|
export interface Session {
|
|
41
41
|
sessionId: string;
|
|
42
42
|
storeId: string;
|
|
@@ -63,6 +63,19 @@ export interface Currency {
|
|
|
63
63
|
symbol: string;
|
|
64
64
|
name: string;
|
|
65
65
|
}
|
|
66
|
+
export interface StoreCreditSettings {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
defaultCreditsPerRebill?: number;
|
|
69
|
+
creditRatio?: {
|
|
70
|
+
credits: number;
|
|
71
|
+
baseAmount: number;
|
|
72
|
+
};
|
|
73
|
+
creditRewardRatio?: {
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
credits: number;
|
|
76
|
+
baseAmount: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
66
79
|
export interface Store {
|
|
67
80
|
id: string;
|
|
68
81
|
accountId: string;
|
|
@@ -72,6 +85,8 @@ export interface Store {
|
|
|
72
85
|
locale: string;
|
|
73
86
|
presentmentCurrencies: string[];
|
|
74
87
|
chargeCurrencies: string[];
|
|
88
|
+
/** Store-level credit system configuration (from session init). */
|
|
89
|
+
creditSettings?: StoreCreditSettings | null;
|
|
75
90
|
}
|
|
76
91
|
export interface PickupPoint {
|
|
77
92
|
id: string;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 4. Clean the URL (remove authCode)
|
|
12
12
|
* 5. Return resolved customer and context
|
|
13
13
|
*/
|
|
14
|
+
import { SessionRole } from '../types';
|
|
14
15
|
export interface AuthHandoffResolveResponse {
|
|
15
16
|
sessionId: string;
|
|
16
17
|
token: string;
|
|
@@ -19,7 +20,7 @@ export interface AuthHandoffResolveResponse {
|
|
|
19
20
|
email?: string;
|
|
20
21
|
firstName?: string;
|
|
21
22
|
lastName?: string;
|
|
22
|
-
role:
|
|
23
|
+
role: SessionRole;
|
|
23
24
|
};
|
|
24
25
|
context: Record<string, unknown>;
|
|
25
26
|
}
|
package/dist/v2/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export * from './core/pathRemapping';
|
|
|
20
20
|
export { bootstrapPixelTrackerFromWindow, createPixelTracker, } from './core/pixelTracker';
|
|
21
21
|
export type { PixelTracker, TrackOptions } from './core/pixelTracker';
|
|
22
22
|
export type { PixelProviderKey } from './core/pixelMapping';
|
|
23
|
+
export { buildGa4PurchasePayload, GA4_PARAM_KEY } from './core/pixelMapping';
|
|
24
|
+
export type { Ga4PurchasePayload, Ga4Item, Ga4UserData, Ga4OrderInput } from './core/pixelMapping';
|
|
23
25
|
export { makeMetaEventId } from './core/utils/metaEventId';
|
|
24
26
|
export type { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutSessionPreview, Promotion } from './core/resources/checkout';
|
|
25
27
|
export type { Order, OrderLineItem } from './core/utils/order';
|
|
@@ -33,7 +35,7 @@ export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './co
|
|
|
33
35
|
export type { StoreConfig } from './core/resources/storeConfig';
|
|
34
36
|
export { FunnelActionType } from './core/resources/funnel';
|
|
35
37
|
export type { BackNavigationActionData, CartUpdatedActionData, DirectNavigationActionData, FormSubmitActionData, FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelAction as FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, NextAction, OfferAcceptedActionData, OfferDeclinedActionData, PaymentFailedActionData, PaymentSuccessActionData, SimpleFunnelContext } from './core/resources/funnel';
|
|
36
|
-
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
38
|
+
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStore, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
37
39
|
export type { DebugScript } from './react';
|
|
38
40
|
export type { PaymentMethodName } from './react';
|
|
39
41
|
export { resolveClickId, publishTrackingGlobal, CLICK_ID_URL_PARAMS, CLICK_ID_COOKIES, } from './core/utils/clickIdResolver';
|
package/dist/v2/index.js
CHANGED
|
@@ -25,13 +25,16 @@ loadLocalFunnelConfig } from './core/funnelClient';
|
|
|
25
25
|
export * from './core/pathRemapping';
|
|
26
26
|
// Framework-agnostic pixel tracker (Studio entries bootstrap from this)
|
|
27
27
|
export { bootstrapPixelTrackerFromWindow, createPixelTracker, } from './core/pixelTracker';
|
|
28
|
+
// GA4 canonical purchase enrichment (used by thank-you pages to attach
|
|
29
|
+
// nested-ecommerce + user data the GTM mapper turns into a GA4 `purchase`).
|
|
30
|
+
export { buildGa4PurchasePayload, GA4_PARAM_KEY } from './core/pixelMapping';
|
|
28
31
|
// Stable event_id helper for Meta CAPI / browser pixel deduplication.
|
|
29
32
|
// Re-exported here (already exposed via /v2/react) so framework-agnostic
|
|
30
33
|
// Studio islands can import it from the same entry as bootstrapPixelTracker.
|
|
31
34
|
export { makeMetaEventId } from './core/utils/metaEventId';
|
|
32
35
|
export { FunnelActionType } from './core/resources/funnel';
|
|
33
36
|
// React exports (hooks and components only, types are exported above)
|
|
34
|
-
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
37
|
+
export { ApplePayButton, StripeExpressButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useGooglePayCheckout, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePixelTracking, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStore, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers, useSetPaymentMethod, WhopCheckout, useWhopPaymentPolling } from './react';
|
|
35
38
|
// Click-id resolver (ad-tracker integrations: ClickFlare, Voluum, Binom, …)
|
|
36
39
|
// Re-exported from core so it's reachable from any entry point.
|
|
37
40
|
export { resolveClickId, publishTrackingGlobal, CLICK_ID_URL_PARAMS, CLICK_ID_COOKIES, } from './core/utils/clickIdResolver';
|
|
@@ -3,10 +3,27 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
|
3
3
|
import { getAssignedStepConfig } from '../../core';
|
|
4
4
|
import { useCheckoutQuery } from '../hooks/useCheckoutQuery';
|
|
5
5
|
import { useOrderQuery } from '../hooks/useOrderQuery';
|
|
6
|
+
/**
|
|
7
|
+
* Classify a chunk of text found OUTSIDE <script>/<noscript> tags.
|
|
8
|
+
*
|
|
9
|
+
* Such chunks are HTML markup (e.g. <link rel="preconnect">, <meta>, <img>,
|
|
10
|
+
* <iframe> pixels) far more often than bare JavaScript. Injecting markup as
|
|
11
|
+
* inline JS produces an uncatchable parse-time `SyntaxError: Unexpected token '<'`
|
|
12
|
+
* that aborts the whole <script> — which silently kills any sibling tracking
|
|
13
|
+
* script bundled in the same snippet (e.g. Hyros + TripleWhale). So we only
|
|
14
|
+
* treat a chunk as inline JS when it does NOT look like an HTML tag.
|
|
15
|
+
*/
|
|
16
|
+
function classifyLooseChunk(chunk) {
|
|
17
|
+
if (!chunk || /^<!--[\s\S]*?-->$/.test(chunk))
|
|
18
|
+
return null;
|
|
19
|
+
// Opening tag at the start, or any real element/closing tag anywhere.
|
|
20
|
+
const looksLikeMarkup = /^</.test(chunk) || /<\/?[a-zA-Z][\w-]*[\s/>]/.test(chunk);
|
|
21
|
+
return looksLikeMarkup ? { type: 'html', html: chunk } : { type: 'inline', code: chunk };
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Parse script content that may contain multiple <script> and <noscript> tags.
|
|
8
25
|
* Handles: external scripts (<script src="...">), inline scripts, noscript blocks,
|
|
9
|
-
* and bare JS without any HTML tags.
|
|
26
|
+
* non-script HTML markup (links/meta/img/iframe), and bare JS without any HTML tags.
|
|
10
27
|
*/
|
|
11
28
|
function parseScriptContent(content) {
|
|
12
29
|
const trimmed = content.trim();
|
|
@@ -19,11 +36,11 @@ function parseScriptContent(content) {
|
|
|
19
36
|
let lastIndex = 0;
|
|
20
37
|
let match;
|
|
21
38
|
while ((match = tagRegex.exec(trimmed)) !== null) {
|
|
22
|
-
// Capture any
|
|
39
|
+
// Capture any text between tags — markup (link/meta/img) or bare JS.
|
|
23
40
|
const between = trimmed.slice(lastIndex, match.index).trim();
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
elements.push(
|
|
41
|
+
const betweenElement = classifyLooseChunk(between);
|
|
42
|
+
if (betweenElement) {
|
|
43
|
+
elements.push(betweenElement);
|
|
27
44
|
}
|
|
28
45
|
const [, tagName, attrs, body] = match;
|
|
29
46
|
if (tagName.toLowerCase() === 'noscript') {
|
|
@@ -51,8 +68,9 @@ function parseScriptContent(content) {
|
|
|
51
68
|
}
|
|
52
69
|
// Trailing content after last tag
|
|
53
70
|
const trailing = trimmed.slice(lastIndex).trim();
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
const trailingElement = classifyLooseChunk(trailing);
|
|
72
|
+
if (trailingElement) {
|
|
73
|
+
elements.push(trailingElement);
|
|
56
74
|
}
|
|
57
75
|
return elements;
|
|
58
76
|
}
|
|
@@ -555,6 +573,23 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
555
573
|
el.innerHTML = element.html;
|
|
556
574
|
injectAtPosition(el, position);
|
|
557
575
|
}
|
|
576
|
+
else if (element.type === 'html') {
|
|
577
|
+
// Non-script markup (link/meta/img/iframe pixels, etc.) — inject as real
|
|
578
|
+
// DOM nodes. Injecting this as JS would throw "Unexpected token '<'" and
|
|
579
|
+
// abort sibling scripts in the same snippet. <script> tags created via
|
|
580
|
+
// innerHTML don't execute, but real <script> blocks are parsed separately
|
|
581
|
+
// above, so none reach here.
|
|
582
|
+
const template = document.createElement('template');
|
|
583
|
+
template.innerHTML = element.html;
|
|
584
|
+
Array.from(template.content.childNodes).forEach(node => {
|
|
585
|
+
if (node.nodeType !== 1 /* ELEMENT_NODE */)
|
|
586
|
+
return;
|
|
587
|
+
const el = node;
|
|
588
|
+
el.setAttribute('data-tagada-stepconfig-script', 'true');
|
|
589
|
+
el.setAttribute('data-script-name', script.name);
|
|
590
|
+
injectAtPosition(el, position);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
558
593
|
});
|
|
559
594
|
});
|
|
560
595
|
// Track injected scripts to prevent re-injection
|