@shoppexio/storefront 1.0.77 → 1.0.79
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/CHANGELOG.md +18 -0
- package/dist/index.cjs +86 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -2
- package/dist/index.d.ts +80 -2
- package/dist/index.js +86 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -591,6 +591,33 @@ interface AffiliateValidation {
|
|
|
591
591
|
discount_active: boolean;
|
|
592
592
|
discount_percent: number;
|
|
593
593
|
message?: string;
|
|
594
|
+
/**
|
|
595
|
+
* The attribution policy that governs this code (ADR-0067).
|
|
596
|
+
*
|
|
597
|
+
* Absent when the code did not resolve, or when an older API answered. A
|
|
598
|
+
* caller storing the code itself must apply these rather than the 30-day
|
|
599
|
+
* last-click default, or a first-click tier and any non-30-day window are
|
|
600
|
+
* silently lost.
|
|
601
|
+
*/
|
|
602
|
+
attribution_mode?: 'LAST_CLICK' | 'FIRST_CLICK';
|
|
603
|
+
attribution_window_days?: number;
|
|
604
|
+
/**
|
|
605
|
+
* The policy of the attribution the browser already holds, when one was sent.
|
|
606
|
+
* Null when that code no longer resolves — the stored mode is then the only
|
|
607
|
+
* information left.
|
|
608
|
+
*/
|
|
609
|
+
held_attribution_mode?: 'LAST_CLICK' | 'FIRST_CLICK' | null;
|
|
610
|
+
/**
|
|
611
|
+
* `applyAffiliateCode` only: whether the code you passed is the one now
|
|
612
|
+
* attributed.
|
|
613
|
+
*
|
|
614
|
+
* False when an unexpired first-click attribution kept the sale — the code
|
|
615
|
+
* you passed is still valid, it just did not win. `affiliate_code` then
|
|
616
|
+
* carries the code that DID, because the documented integration feeds that
|
|
617
|
+
* field straight into `checkout({ affiliateCode })` and an explicit checkout
|
|
618
|
+
* code overrides storage.
|
|
619
|
+
*/
|
|
620
|
+
applied?: boolean;
|
|
594
621
|
}
|
|
595
622
|
interface ProductGroup {
|
|
596
623
|
id?: string;
|
|
@@ -1576,7 +1603,26 @@ interface SearchOptions {
|
|
|
1576
1603
|
declare function searchCatalogItems(query: string, options?: SearchOptions): Promise<SDKResponse<StorefrontCatalogSearchItem[]>>;
|
|
1577
1604
|
declare function searchProducts(query: string, options?: SearchOptions): Promise<SDKResponse<Product[]>>;
|
|
1578
1605
|
|
|
1579
|
-
|
|
1606
|
+
type AffiliateAttributionMode = 'LAST_CLICK' | 'FIRST_CLICK';
|
|
1607
|
+
declare function setAffiliateCode(code: string | null | undefined, ttlDays?: number, mode?: AffiliateAttributionMode): string | null;
|
|
1608
|
+
/**
|
|
1609
|
+
* The stored attribution, or null when nothing valid is held.
|
|
1610
|
+
*
|
|
1611
|
+
* Unlike `getAffiliateCode()` this also reports the mode the entry was written
|
|
1612
|
+
* under, which is what `captureAffiliateFromUrl` needs BEFORE it decides
|
|
1613
|
+
* whether it may overwrite.
|
|
1614
|
+
*/
|
|
1615
|
+
declare function getStoredAffiliate(): {
|
|
1616
|
+
code: string;
|
|
1617
|
+
mode: AffiliateAttributionMode;
|
|
1618
|
+
windowDays: number;
|
|
1619
|
+
/**
|
|
1620
|
+
* When this entry expires, as written. Reported so a hold that has to be put
|
|
1621
|
+
* back after an early overwrite keeps its original expiry — the window runs
|
|
1622
|
+
* from the click that earned it, not from the landing that failed to take it.
|
|
1623
|
+
*/
|
|
1624
|
+
expiresAt: number;
|
|
1625
|
+
} | null;
|
|
1580
1626
|
declare function clearAffiliateCode(): void;
|
|
1581
1627
|
declare function getAffiliateCode(): string | null;
|
|
1582
1628
|
declare function trackAffiliateEvent(eventType: 'add_to_cart' | 'checkout_started', options?: {
|
|
@@ -1597,13 +1643,44 @@ declare function trackAffiliateEvent(eventType: 'add_to_cart' | 'checkout_starte
|
|
|
1597
1643
|
dedupeKey?: string;
|
|
1598
1644
|
}): Promise<void>;
|
|
1599
1645
|
declare function validateAffiliateCode(code: string): Promise<SDKResponse<AffiliateValidation>>;
|
|
1646
|
+
/**
|
|
1647
|
+
* Validate a manually entered code and store it (ADR-0067).
|
|
1648
|
+
*
|
|
1649
|
+
* Attribution is governed by the same rules as a `?ref=` landing, deliberately:
|
|
1650
|
+
* a themed "apply code" box must not be a way around first-click, or the rule
|
|
1651
|
+
* is bypassed by any storefront that offers one. So a still-protected hold
|
|
1652
|
+
* keeps the sale, and the stored entry always carries the mode and window the
|
|
1653
|
+
* server just reported — the old unconditional `setAffiliateCode(code)` wrote
|
|
1654
|
+
* a 30-day last-click entry whatever the shop or tier had configured.
|
|
1655
|
+
*
|
|
1656
|
+
* The result reports what is now ATTRIBUTED, not merely what was validated:
|
|
1657
|
+
* `affiliate_code` carries the winning code and `applied` says whether that is
|
|
1658
|
+
* the one you passed. The documented integration hands `affiliate_code`
|
|
1659
|
+
* straight to `checkout({ affiliateCode })`, and an explicit checkout code
|
|
1660
|
+
* overrides storage — so returning the losing code here would credit it and
|
|
1661
|
+
* walk right around the hold this function just honoured.
|
|
1662
|
+
*/
|
|
1600
1663
|
declare function applyAffiliateCode(code: string): Promise<SDKResponse<AffiliateValidation>>;
|
|
1601
1664
|
/**
|
|
1602
|
-
* Capture an affiliate code from the current URL and store it
|
|
1665
|
+
* Capture an affiliate code from the current URL and store it (ADR-0067).
|
|
1603
1666
|
*
|
|
1604
1667
|
* Example:
|
|
1605
1668
|
* - URL: https://mystore.com/product/abc?ref=deadbeef
|
|
1606
1669
|
* - captureAffiliateFromUrl() stores "deadbeef" and returns it.
|
|
1670
|
+
*
|
|
1671
|
+
* Under LAST_CLICK (the default, and the only behaviour before ADR-0067) a new
|
|
1672
|
+
* `?ref=` replaces whatever was stored. Under FIRST_CLICK an existing,
|
|
1673
|
+
* unexpired attribution wins and the new code is NOT stored — the landing is
|
|
1674
|
+
* still reported to the server so the affiliate's click statistics stay
|
|
1675
|
+
* honest, but the credit stays with whoever was there first.
|
|
1676
|
+
*
|
|
1677
|
+
* The mode comes from the server, which means the FIRST landing in a browser
|
|
1678
|
+
* can never be blocked by it — correct, because there is nothing to protect
|
|
1679
|
+
* yet. From then on the mode travels with the stored entry.
|
|
1680
|
+
*
|
|
1681
|
+
* Enforcement is client-side. A buyer who edits localStorage can re-attribute
|
|
1682
|
+
* their own visit; this is the same trust level as the window itself and must
|
|
1683
|
+
* not be presented to merchants as fraud protection.
|
|
1607
1684
|
*/
|
|
1608
1685
|
declare function captureAffiliateFromUrl(param?: string): Promise<string | null>;
|
|
1609
1686
|
|
|
@@ -1926,6 +2003,7 @@ declare const shoppex: {
|
|
|
1926
2003
|
validateAffiliateCode: typeof validateAffiliateCode;
|
|
1927
2004
|
applyAffiliateCode: typeof applyAffiliateCode;
|
|
1928
2005
|
getAffiliateCode: typeof getAffiliateCode;
|
|
2006
|
+
getStoredAffiliate: typeof getStoredAffiliate;
|
|
1929
2007
|
setAffiliateCode: typeof setAffiliateCode;
|
|
1930
2008
|
clearAffiliateCode: typeof clearAffiliateCode;
|
|
1931
2009
|
trackAffiliateEvent: typeof trackAffiliateEvent;
|
package/dist/index.d.ts
CHANGED
|
@@ -591,6 +591,33 @@ interface AffiliateValidation {
|
|
|
591
591
|
discount_active: boolean;
|
|
592
592
|
discount_percent: number;
|
|
593
593
|
message?: string;
|
|
594
|
+
/**
|
|
595
|
+
* The attribution policy that governs this code (ADR-0067).
|
|
596
|
+
*
|
|
597
|
+
* Absent when the code did not resolve, or when an older API answered. A
|
|
598
|
+
* caller storing the code itself must apply these rather than the 30-day
|
|
599
|
+
* last-click default, or a first-click tier and any non-30-day window are
|
|
600
|
+
* silently lost.
|
|
601
|
+
*/
|
|
602
|
+
attribution_mode?: 'LAST_CLICK' | 'FIRST_CLICK';
|
|
603
|
+
attribution_window_days?: number;
|
|
604
|
+
/**
|
|
605
|
+
* The policy of the attribution the browser already holds, when one was sent.
|
|
606
|
+
* Null when that code no longer resolves — the stored mode is then the only
|
|
607
|
+
* information left.
|
|
608
|
+
*/
|
|
609
|
+
held_attribution_mode?: 'LAST_CLICK' | 'FIRST_CLICK' | null;
|
|
610
|
+
/**
|
|
611
|
+
* `applyAffiliateCode` only: whether the code you passed is the one now
|
|
612
|
+
* attributed.
|
|
613
|
+
*
|
|
614
|
+
* False when an unexpired first-click attribution kept the sale — the code
|
|
615
|
+
* you passed is still valid, it just did not win. `affiliate_code` then
|
|
616
|
+
* carries the code that DID, because the documented integration feeds that
|
|
617
|
+
* field straight into `checkout({ affiliateCode })` and an explicit checkout
|
|
618
|
+
* code overrides storage.
|
|
619
|
+
*/
|
|
620
|
+
applied?: boolean;
|
|
594
621
|
}
|
|
595
622
|
interface ProductGroup {
|
|
596
623
|
id?: string;
|
|
@@ -1576,7 +1603,26 @@ interface SearchOptions {
|
|
|
1576
1603
|
declare function searchCatalogItems(query: string, options?: SearchOptions): Promise<SDKResponse<StorefrontCatalogSearchItem[]>>;
|
|
1577
1604
|
declare function searchProducts(query: string, options?: SearchOptions): Promise<SDKResponse<Product[]>>;
|
|
1578
1605
|
|
|
1579
|
-
|
|
1606
|
+
type AffiliateAttributionMode = 'LAST_CLICK' | 'FIRST_CLICK';
|
|
1607
|
+
declare function setAffiliateCode(code: string | null | undefined, ttlDays?: number, mode?: AffiliateAttributionMode): string | null;
|
|
1608
|
+
/**
|
|
1609
|
+
* The stored attribution, or null when nothing valid is held.
|
|
1610
|
+
*
|
|
1611
|
+
* Unlike `getAffiliateCode()` this also reports the mode the entry was written
|
|
1612
|
+
* under, which is what `captureAffiliateFromUrl` needs BEFORE it decides
|
|
1613
|
+
* whether it may overwrite.
|
|
1614
|
+
*/
|
|
1615
|
+
declare function getStoredAffiliate(): {
|
|
1616
|
+
code: string;
|
|
1617
|
+
mode: AffiliateAttributionMode;
|
|
1618
|
+
windowDays: number;
|
|
1619
|
+
/**
|
|
1620
|
+
* When this entry expires, as written. Reported so a hold that has to be put
|
|
1621
|
+
* back after an early overwrite keeps its original expiry — the window runs
|
|
1622
|
+
* from the click that earned it, not from the landing that failed to take it.
|
|
1623
|
+
*/
|
|
1624
|
+
expiresAt: number;
|
|
1625
|
+
} | null;
|
|
1580
1626
|
declare function clearAffiliateCode(): void;
|
|
1581
1627
|
declare function getAffiliateCode(): string | null;
|
|
1582
1628
|
declare function trackAffiliateEvent(eventType: 'add_to_cart' | 'checkout_started', options?: {
|
|
@@ -1597,13 +1643,44 @@ declare function trackAffiliateEvent(eventType: 'add_to_cart' | 'checkout_starte
|
|
|
1597
1643
|
dedupeKey?: string;
|
|
1598
1644
|
}): Promise<void>;
|
|
1599
1645
|
declare function validateAffiliateCode(code: string): Promise<SDKResponse<AffiliateValidation>>;
|
|
1646
|
+
/**
|
|
1647
|
+
* Validate a manually entered code and store it (ADR-0067).
|
|
1648
|
+
*
|
|
1649
|
+
* Attribution is governed by the same rules as a `?ref=` landing, deliberately:
|
|
1650
|
+
* a themed "apply code" box must not be a way around first-click, or the rule
|
|
1651
|
+
* is bypassed by any storefront that offers one. So a still-protected hold
|
|
1652
|
+
* keeps the sale, and the stored entry always carries the mode and window the
|
|
1653
|
+
* server just reported — the old unconditional `setAffiliateCode(code)` wrote
|
|
1654
|
+
* a 30-day last-click entry whatever the shop or tier had configured.
|
|
1655
|
+
*
|
|
1656
|
+
* The result reports what is now ATTRIBUTED, not merely what was validated:
|
|
1657
|
+
* `affiliate_code` carries the winning code and `applied` says whether that is
|
|
1658
|
+
* the one you passed. The documented integration hands `affiliate_code`
|
|
1659
|
+
* straight to `checkout({ affiliateCode })`, and an explicit checkout code
|
|
1660
|
+
* overrides storage — so returning the losing code here would credit it and
|
|
1661
|
+
* walk right around the hold this function just honoured.
|
|
1662
|
+
*/
|
|
1600
1663
|
declare function applyAffiliateCode(code: string): Promise<SDKResponse<AffiliateValidation>>;
|
|
1601
1664
|
/**
|
|
1602
|
-
* Capture an affiliate code from the current URL and store it
|
|
1665
|
+
* Capture an affiliate code from the current URL and store it (ADR-0067).
|
|
1603
1666
|
*
|
|
1604
1667
|
* Example:
|
|
1605
1668
|
* - URL: https://mystore.com/product/abc?ref=deadbeef
|
|
1606
1669
|
* - captureAffiliateFromUrl() stores "deadbeef" and returns it.
|
|
1670
|
+
*
|
|
1671
|
+
* Under LAST_CLICK (the default, and the only behaviour before ADR-0067) a new
|
|
1672
|
+
* `?ref=` replaces whatever was stored. Under FIRST_CLICK an existing,
|
|
1673
|
+
* unexpired attribution wins and the new code is NOT stored — the landing is
|
|
1674
|
+
* still reported to the server so the affiliate's click statistics stay
|
|
1675
|
+
* honest, but the credit stays with whoever was there first.
|
|
1676
|
+
*
|
|
1677
|
+
* The mode comes from the server, which means the FIRST landing in a browser
|
|
1678
|
+
* can never be blocked by it — correct, because there is nothing to protect
|
|
1679
|
+
* yet. From then on the mode travels with the stored entry.
|
|
1680
|
+
*
|
|
1681
|
+
* Enforcement is client-side. A buyer who edits localStorage can re-attribute
|
|
1682
|
+
* their own visit; this is the same trust level as the window itself and must
|
|
1683
|
+
* not be presented to merchants as fraud protection.
|
|
1607
1684
|
*/
|
|
1608
1685
|
declare function captureAffiliateFromUrl(param?: string): Promise<string | null>;
|
|
1609
1686
|
|
|
@@ -1926,6 +2003,7 @@ declare const shoppex: {
|
|
|
1926
2003
|
validateAffiliateCode: typeof validateAffiliateCode;
|
|
1927
2004
|
applyAffiliateCode: typeof applyAffiliateCode;
|
|
1928
2005
|
getAffiliateCode: typeof getAffiliateCode;
|
|
2006
|
+
getStoredAffiliate: typeof getStoredAffiliate;
|
|
1929
2007
|
setAffiliateCode: typeof setAffiliateCode;
|
|
1930
2008
|
clearAffiliateCode: typeof clearAffiliateCode;
|
|
1931
2009
|
trackAffiliateEvent: typeof trackAffiliateEvent;
|
package/dist/index.js
CHANGED
|
@@ -3890,6 +3890,9 @@ var SESSION_STORAGE_KEY = "shoppex:affiliate_session:v1";
|
|
|
3890
3890
|
var DEFAULT_TTL_DAYS = 30;
|
|
3891
3891
|
var FALLBACK_SESSION_KEY_LENGTH = 24;
|
|
3892
3892
|
var SESSION_KEY_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
3893
|
+
function normalizeMode(value) {
|
|
3894
|
+
return value === "FIRST_CLICK" ? "FIRST_CLICK" : "LAST_CLICK";
|
|
3895
|
+
}
|
|
3893
3896
|
function nowMs() {
|
|
3894
3897
|
return Date.now();
|
|
3895
3898
|
}
|
|
@@ -3907,7 +3910,7 @@ function safeRead() {
|
|
|
3907
3910
|
if (!raw) return null;
|
|
3908
3911
|
const parsed = JSON.parse(raw);
|
|
3909
3912
|
if (!parsed || typeof parsed.code !== "string" || typeof parsed.expiresAt !== "number") return null;
|
|
3910
|
-
return parsed;
|
|
3913
|
+
return parsed.mode === void 0 ? parsed : { ...parsed, mode: normalizeMode(parsed.mode) };
|
|
3911
3914
|
} catch {
|
|
3912
3915
|
return null;
|
|
3913
3916
|
}
|
|
@@ -3919,15 +3922,35 @@ function safeWrite(value) {
|
|
|
3919
3922
|
} catch {
|
|
3920
3923
|
}
|
|
3921
3924
|
}
|
|
3922
|
-
function
|
|
3925
|
+
function restoreStoredAffiliate(value) {
|
|
3926
|
+
safeWrite(value);
|
|
3927
|
+
}
|
|
3928
|
+
function setAffiliateCode(code, ttlDays = DEFAULT_TTL_DAYS, mode) {
|
|
3923
3929
|
const normalized = normalizeAffiliateCode(code);
|
|
3924
3930
|
if (!normalized) {
|
|
3925
3931
|
clearAffiliateCode();
|
|
3926
3932
|
return null;
|
|
3927
3933
|
}
|
|
3928
|
-
safeWrite({
|
|
3934
|
+
safeWrite({
|
|
3935
|
+
code: normalized,
|
|
3936
|
+
expiresAt: nowMs() + ttlMs(ttlDays),
|
|
3937
|
+
windowDays: ttlDays,
|
|
3938
|
+
...mode ? { mode } : {}
|
|
3939
|
+
});
|
|
3929
3940
|
return normalized;
|
|
3930
3941
|
}
|
|
3942
|
+
function getStoredAffiliate() {
|
|
3943
|
+
const stored = safeRead();
|
|
3944
|
+
if (!stored) return null;
|
|
3945
|
+
if (stored.expiresAt <= nowMs()) {
|
|
3946
|
+
clearAffiliateCode();
|
|
3947
|
+
return null;
|
|
3948
|
+
}
|
|
3949
|
+
const code = normalizeAffiliateCode(stored.code);
|
|
3950
|
+
if (!code) return null;
|
|
3951
|
+
const windowDays = typeof stored.windowDays === "number" && stored.windowDays > 0 ? stored.windowDays : DEFAULT_TTL_DAYS;
|
|
3952
|
+
return { code, mode: normalizeMode(stored.mode), windowDays, expiresAt: stored.expiresAt };
|
|
3953
|
+
}
|
|
3931
3954
|
function clearAffiliateCode() {
|
|
3932
3955
|
if (typeof window === "undefined") return;
|
|
3933
3956
|
try {
|
|
@@ -4017,11 +4040,13 @@ async function validateAffiliateCode(code) {
|
|
|
4017
4040
|
};
|
|
4018
4041
|
}
|
|
4019
4042
|
const config = getConfig();
|
|
4043
|
+
const held = getStoredAffiliate();
|
|
4020
4044
|
const response = await post(
|
|
4021
4045
|
"/v1/storefront/affiliates/resolve",
|
|
4022
4046
|
{
|
|
4023
4047
|
shop_slug: config.storeSlug,
|
|
4024
|
-
code: normalizedCode
|
|
4048
|
+
code: normalizedCode,
|
|
4049
|
+
...held && held.code !== normalizedCode ? { held_code: held.code } : {}
|
|
4025
4050
|
},
|
|
4026
4051
|
{ retries: 0 }
|
|
4027
4052
|
);
|
|
@@ -4049,15 +4074,36 @@ async function validateAffiliateCode(code) {
|
|
|
4049
4074
|
...response.data.program_enabled !== void 0 ? { program_enabled: response.data.program_enabled } : {},
|
|
4050
4075
|
affiliate_code: normalizeAffiliateCode(response.data.affiliate_code),
|
|
4051
4076
|
discount_active: Boolean(response.data.discount_active),
|
|
4052
|
-
discount_percent: Number(response.data.discount_percent ?? 0)
|
|
4077
|
+
discount_percent: Number(response.data.discount_percent ?? 0),
|
|
4078
|
+
// Passed through rather than dropped: a caller that stores the code has
|
|
4079
|
+
// no other way to learn the policy, and defaulting to 30-day last-click
|
|
4080
|
+
// would quietly discard a first-click tier and any custom window.
|
|
4081
|
+
attribution_mode: normalizeMode(response.data.attribution_mode),
|
|
4082
|
+
attribution_window_days: typeof response.data.attribution_window_days === "number" && response.data.attribution_window_days >= 1 ? Math.trunc(response.data.attribution_window_days) : DEFAULT_TTL_DAYS,
|
|
4083
|
+
held_attribution_mode: response.data.held_attribution_mode === "FIRST_CLICK" || response.data.held_attribution_mode === "LAST_CLICK" ? response.data.held_attribution_mode : null
|
|
4053
4084
|
},
|
|
4054
4085
|
...response.message ? { message: response.message } : {}
|
|
4055
4086
|
};
|
|
4056
4087
|
}
|
|
4057
4088
|
async function applyAffiliateCode(code) {
|
|
4089
|
+
const held = getStoredAffiliate();
|
|
4058
4090
|
const result = await validateAffiliateCode(code);
|
|
4059
4091
|
if (result.success && result.data?.affiliate_code) {
|
|
4060
|
-
|
|
4092
|
+
const applied = result.data.affiliate_code;
|
|
4093
|
+
const holdsOtherCode = held !== null && held.code !== applied;
|
|
4094
|
+
const heldMode = holdsOtherCode ? result.data.held_attribution_mode ?? held.mode : null;
|
|
4095
|
+
if (heldMode === "FIRST_CLICK" && held) {
|
|
4096
|
+
return {
|
|
4097
|
+
...result,
|
|
4098
|
+
data: { ...result.data, affiliate_code: held.code, applied: false }
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
setAffiliateCode(
|
|
4102
|
+
applied,
|
|
4103
|
+
result.data.attribution_window_days ?? DEFAULT_TTL_DAYS,
|
|
4104
|
+
result.data.attribution_mode
|
|
4105
|
+
);
|
|
4106
|
+
return { ...result, data: { ...result.data, applied: true } };
|
|
4061
4107
|
}
|
|
4062
4108
|
return result;
|
|
4063
4109
|
}
|
|
@@ -4073,29 +4119,56 @@ async function captureAffiliateFromUrl(param = "ref") {
|
|
|
4073
4119
|
}
|
|
4074
4120
|
code = normalizeAffiliateCode(code);
|
|
4075
4121
|
if (!code) return null;
|
|
4076
|
-
|
|
4122
|
+
const held = getStoredAffiliate();
|
|
4123
|
+
const holdsOtherCode = held !== null && held.code !== code;
|
|
4124
|
+
const mayHoldFirstClick = holdsOtherCode && held.mode === "FIRST_CLICK";
|
|
4125
|
+
if (!mayHoldFirstClick) {
|
|
4126
|
+
setAffiliateCode(code, held?.windowDays ?? DEFAULT_TTL_DAYS, held?.mode);
|
|
4127
|
+
}
|
|
4077
4128
|
if (isInitialized()) {
|
|
4078
4129
|
try {
|
|
4079
4130
|
const config = getConfig();
|
|
4080
4131
|
const res = await post(
|
|
4081
4132
|
"/v1/storefront/affiliates/attribution",
|
|
4082
|
-
{
|
|
4133
|
+
{
|
|
4134
|
+
shop_slug: config.storeSlug,
|
|
4135
|
+
code,
|
|
4136
|
+
// Sent so the server can report the policy of the attribution we
|
|
4137
|
+
// already hold. Its mode — not the arriving code's — decides whether
|
|
4138
|
+
// that hold may be released.
|
|
4139
|
+
...held ? { held_code: held.code } : {}
|
|
4140
|
+
},
|
|
4083
4141
|
{ retries: 0 }
|
|
4084
4142
|
);
|
|
4143
|
+
const serverMode = res.success && res.data?.attribution_mode ? normalizeMode(res.data.attribution_mode) : null;
|
|
4144
|
+
const serverWindow = res.success && typeof res.data?.attribution_window_days === "number" ? res.data.attribution_window_days : DEFAULT_TTL_DAYS;
|
|
4145
|
+
const heldMode = res.success && res.data?.held_attribution_mode ? normalizeMode(res.data.held_attribution_mode) : null;
|
|
4146
|
+
const holdsFirstClick = holdsOtherCode && (heldMode ?? held?.mode) === "FIRST_CLICK";
|
|
4147
|
+
if (holdsFirstClick && held) {
|
|
4148
|
+
if (!mayHoldFirstClick) {
|
|
4149
|
+
restoreStoredAffiliate({
|
|
4150
|
+
code: held.code,
|
|
4151
|
+
expiresAt: held.expiresAt,
|
|
4152
|
+
windowDays: held.windowDays,
|
|
4153
|
+
mode: heldMode ?? "FIRST_CLICK"
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
return held.code;
|
|
4157
|
+
}
|
|
4085
4158
|
if (res.success && res.data?.accepted && res.data.affiliate_code) {
|
|
4086
|
-
setAffiliateCode(res.data.affiliate_code);
|
|
4159
|
+
setAffiliateCode(res.data.affiliate_code, serverWindow, serverMode ?? void 0);
|
|
4087
4160
|
return res.data.affiliate_code;
|
|
4088
4161
|
}
|
|
4089
4162
|
if (res.success && res.data && res.data.accepted === false) {
|
|
4090
4163
|
clearAffiliateCode();
|
|
4091
4164
|
return null;
|
|
4092
4165
|
}
|
|
4093
|
-
return code;
|
|
4166
|
+
return mayHoldFirstClick ? held.code : code;
|
|
4094
4167
|
} catch {
|
|
4095
|
-
return code;
|
|
4168
|
+
return mayHoldFirstClick ? held.code : code;
|
|
4096
4169
|
}
|
|
4097
4170
|
}
|
|
4098
|
-
return code;
|
|
4171
|
+
return mayHoldFirstClick ? held.code : code;
|
|
4099
4172
|
}
|
|
4100
4173
|
|
|
4101
4174
|
// ../sdk/src/utils/cart-line-id.ts
|
|
@@ -6180,6 +6253,7 @@ var shoppex = {
|
|
|
6180
6253
|
validateAffiliateCode,
|
|
6181
6254
|
applyAffiliateCode,
|
|
6182
6255
|
getAffiliateCode,
|
|
6256
|
+
getStoredAffiliate,
|
|
6183
6257
|
setAffiliateCode,
|
|
6184
6258
|
clearAffiliateCode,
|
|
6185
6259
|
trackAffiliateEvent,
|