@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.79
|
|
4
|
+
|
|
5
|
+
- Attribution is now decided by the policy of the code that EARNED the hold, never by the one that just arrived. `captureAffiliateFromUrl()` sends the held code so the server can report its mode, and a first-click hold survives an affiliate on a last-click tier landing afterwards.
|
|
6
|
+
- `applyAffiliateCode()` applies the same rules as a `?ref=` landing. It stores the code with the shop's (or tier's) real mode and window instead of a fixed 30-day last-click entry, and it no longer overrides a standing first-click hold — a themed "apply code" field was previously a way around the rule.
|
|
7
|
+
- `applyAffiliateCode()` reports what is ATTRIBUTED: `data.affiliate_code` is the winning code and the new `data.applied` says whether that is the one you passed. Passing `data.affiliate_code` to `checkout()` therefore stays correct in both cases.
|
|
8
|
+
- `validateAffiliateCode()` passes the resolved `attribution_mode`, `attribution_window_days` and `held_attribution_mode` through instead of dropping them.
|
|
9
|
+
- `getStoredAffiliate()` also returns `expiresAt`, so a hold can be restored without extending the window it earned.
|
|
10
|
+
- A transport failure or an HTTP error such as a rate limit no longer returns the arriving code while storage still holds another: both paths answer with the code that keeps the attribution.
|
|
11
|
+
|
|
12
|
+
## 1.0.78
|
|
13
|
+
|
|
14
|
+
- `captureAffiliateFromUrl()` honours the shop's attribution mode (ADR-0067). Under `FIRST_CLICK` a held, unexpired attribution wins and a newer `?ref=` no longer replaces it; the landing is still reported so click statistics stay complete. `LAST_CLICK` is unchanged and remains the default.
|
|
15
|
+
- The attribution window is no longer fixed at 30 days: `/attribution` returns `attribution_mode` and `attribution_window_days`, and a newly stored referral uses the shop's (or its tier's) window.
|
|
16
|
+
- New export `getStoredAffiliate()`, returning `{ code, mode }` for the held attribution, or `null`.
|
|
17
|
+
- `setAffiliateCode(code, ttlDays, mode?)` accepts the mode to persist. Existing two-argument calls are unaffected.
|
|
18
|
+
- The `shoppex:affiliate_code:v1` storage key is unchanged and entries written by earlier versions keep working: an entry without a mode reads as `LAST_CLICK`, the behaviour it was written under. No attribution is lost on upgrade.
|
|
19
|
+
- Note: first-click is enforced in the browser, like the window itself. It is an attribution rule, not fraud protection.
|
|
20
|
+
|
|
3
21
|
## 1.0.77
|
|
4
22
|
|
|
5
23
|
- Headless customer replacement requests now require `lineItemId` in the SDK types, matching the live API. Pass the invoice position UUID; omitted or null IDs are not valid. The read-only `replacementEligibility` query still allows omitting its line ID.
|
package/dist/index.cjs
CHANGED
|
@@ -19285,6 +19285,9 @@ var SESSION_STORAGE_KEY = "shoppex:affiliate_session:v1";
|
|
|
19285
19285
|
var DEFAULT_TTL_DAYS = 30;
|
|
19286
19286
|
var FALLBACK_SESSION_KEY_LENGTH = 24;
|
|
19287
19287
|
var SESSION_KEY_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
19288
|
+
function normalizeMode(value) {
|
|
19289
|
+
return value === "FIRST_CLICK" ? "FIRST_CLICK" : "LAST_CLICK";
|
|
19290
|
+
}
|
|
19288
19291
|
function nowMs() {
|
|
19289
19292
|
return Date.now();
|
|
19290
19293
|
}
|
|
@@ -19302,7 +19305,7 @@ function safeRead() {
|
|
|
19302
19305
|
if (!raw) return null;
|
|
19303
19306
|
const parsed = JSON.parse(raw);
|
|
19304
19307
|
if (!parsed || typeof parsed.code !== "string" || typeof parsed.expiresAt !== "number") return null;
|
|
19305
|
-
return parsed;
|
|
19308
|
+
return parsed.mode === void 0 ? parsed : { ...parsed, mode: normalizeMode(parsed.mode) };
|
|
19306
19309
|
} catch {
|
|
19307
19310
|
return null;
|
|
19308
19311
|
}
|
|
@@ -19314,15 +19317,35 @@ function safeWrite(value) {
|
|
|
19314
19317
|
} catch {
|
|
19315
19318
|
}
|
|
19316
19319
|
}
|
|
19317
|
-
function
|
|
19320
|
+
function restoreStoredAffiliate(value) {
|
|
19321
|
+
safeWrite(value);
|
|
19322
|
+
}
|
|
19323
|
+
function setAffiliateCode(code, ttlDays = DEFAULT_TTL_DAYS, mode) {
|
|
19318
19324
|
const normalized = normalizeAffiliateCode(code);
|
|
19319
19325
|
if (!normalized) {
|
|
19320
19326
|
clearAffiliateCode();
|
|
19321
19327
|
return null;
|
|
19322
19328
|
}
|
|
19323
|
-
safeWrite({
|
|
19329
|
+
safeWrite({
|
|
19330
|
+
code: normalized,
|
|
19331
|
+
expiresAt: nowMs() + ttlMs(ttlDays),
|
|
19332
|
+
windowDays: ttlDays,
|
|
19333
|
+
...mode ? { mode } : {}
|
|
19334
|
+
});
|
|
19324
19335
|
return normalized;
|
|
19325
19336
|
}
|
|
19337
|
+
function getStoredAffiliate() {
|
|
19338
|
+
const stored = safeRead();
|
|
19339
|
+
if (!stored) return null;
|
|
19340
|
+
if (stored.expiresAt <= nowMs()) {
|
|
19341
|
+
clearAffiliateCode();
|
|
19342
|
+
return null;
|
|
19343
|
+
}
|
|
19344
|
+
const code = normalizeAffiliateCode(stored.code);
|
|
19345
|
+
if (!code) return null;
|
|
19346
|
+
const windowDays = typeof stored.windowDays === "number" && stored.windowDays > 0 ? stored.windowDays : DEFAULT_TTL_DAYS;
|
|
19347
|
+
return { code, mode: normalizeMode(stored.mode), windowDays, expiresAt: stored.expiresAt };
|
|
19348
|
+
}
|
|
19326
19349
|
function clearAffiliateCode() {
|
|
19327
19350
|
if (typeof window === "undefined") return;
|
|
19328
19351
|
try {
|
|
@@ -19412,11 +19435,13 @@ async function validateAffiliateCode(code) {
|
|
|
19412
19435
|
};
|
|
19413
19436
|
}
|
|
19414
19437
|
const config2 = getConfig();
|
|
19438
|
+
const held = getStoredAffiliate();
|
|
19415
19439
|
const response = await post(
|
|
19416
19440
|
"/v1/storefront/affiliates/resolve",
|
|
19417
19441
|
{
|
|
19418
19442
|
shop_slug: config2.storeSlug,
|
|
19419
|
-
code: normalizedCode
|
|
19443
|
+
code: normalizedCode,
|
|
19444
|
+
...held && held.code !== normalizedCode ? { held_code: held.code } : {}
|
|
19420
19445
|
},
|
|
19421
19446
|
{ retries: 0 }
|
|
19422
19447
|
);
|
|
@@ -19444,15 +19469,36 @@ async function validateAffiliateCode(code) {
|
|
|
19444
19469
|
...response.data.program_enabled !== void 0 ? { program_enabled: response.data.program_enabled } : {},
|
|
19445
19470
|
affiliate_code: normalizeAffiliateCode(response.data.affiliate_code),
|
|
19446
19471
|
discount_active: Boolean(response.data.discount_active),
|
|
19447
|
-
discount_percent: Number(response.data.discount_percent ?? 0)
|
|
19472
|
+
discount_percent: Number(response.data.discount_percent ?? 0),
|
|
19473
|
+
// Passed through rather than dropped: a caller that stores the code has
|
|
19474
|
+
// no other way to learn the policy, and defaulting to 30-day last-click
|
|
19475
|
+
// would quietly discard a first-click tier and any custom window.
|
|
19476
|
+
attribution_mode: normalizeMode(response.data.attribution_mode),
|
|
19477
|
+
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,
|
|
19478
|
+
held_attribution_mode: response.data.held_attribution_mode === "FIRST_CLICK" || response.data.held_attribution_mode === "LAST_CLICK" ? response.data.held_attribution_mode : null
|
|
19448
19479
|
},
|
|
19449
19480
|
...response.message ? { message: response.message } : {}
|
|
19450
19481
|
};
|
|
19451
19482
|
}
|
|
19452
19483
|
async function applyAffiliateCode(code) {
|
|
19484
|
+
const held = getStoredAffiliate();
|
|
19453
19485
|
const result = await validateAffiliateCode(code);
|
|
19454
19486
|
if (result.success && result.data?.affiliate_code) {
|
|
19455
|
-
|
|
19487
|
+
const applied = result.data.affiliate_code;
|
|
19488
|
+
const holdsOtherCode = held !== null && held.code !== applied;
|
|
19489
|
+
const heldMode = holdsOtherCode ? result.data.held_attribution_mode ?? held.mode : null;
|
|
19490
|
+
if (heldMode === "FIRST_CLICK" && held) {
|
|
19491
|
+
return {
|
|
19492
|
+
...result,
|
|
19493
|
+
data: { ...result.data, affiliate_code: held.code, applied: false }
|
|
19494
|
+
};
|
|
19495
|
+
}
|
|
19496
|
+
setAffiliateCode(
|
|
19497
|
+
applied,
|
|
19498
|
+
result.data.attribution_window_days ?? DEFAULT_TTL_DAYS,
|
|
19499
|
+
result.data.attribution_mode
|
|
19500
|
+
);
|
|
19501
|
+
return { ...result, data: { ...result.data, applied: true } };
|
|
19456
19502
|
}
|
|
19457
19503
|
return result;
|
|
19458
19504
|
}
|
|
@@ -19468,29 +19514,56 @@ async function captureAffiliateFromUrl(param = "ref") {
|
|
|
19468
19514
|
}
|
|
19469
19515
|
code = normalizeAffiliateCode(code);
|
|
19470
19516
|
if (!code) return null;
|
|
19471
|
-
|
|
19517
|
+
const held = getStoredAffiliate();
|
|
19518
|
+
const holdsOtherCode = held !== null && held.code !== code;
|
|
19519
|
+
const mayHoldFirstClick = holdsOtherCode && held.mode === "FIRST_CLICK";
|
|
19520
|
+
if (!mayHoldFirstClick) {
|
|
19521
|
+
setAffiliateCode(code, held?.windowDays ?? DEFAULT_TTL_DAYS, held?.mode);
|
|
19522
|
+
}
|
|
19472
19523
|
if (isInitialized()) {
|
|
19473
19524
|
try {
|
|
19474
19525
|
const config2 = getConfig();
|
|
19475
19526
|
const res = await post(
|
|
19476
19527
|
"/v1/storefront/affiliates/attribution",
|
|
19477
|
-
{
|
|
19528
|
+
{
|
|
19529
|
+
shop_slug: config2.storeSlug,
|
|
19530
|
+
code,
|
|
19531
|
+
// Sent so the server can report the policy of the attribution we
|
|
19532
|
+
// already hold. Its mode — not the arriving code's — decides whether
|
|
19533
|
+
// that hold may be released.
|
|
19534
|
+
...held ? { held_code: held.code } : {}
|
|
19535
|
+
},
|
|
19478
19536
|
{ retries: 0 }
|
|
19479
19537
|
);
|
|
19538
|
+
const serverMode = res.success && res.data?.attribution_mode ? normalizeMode(res.data.attribution_mode) : null;
|
|
19539
|
+
const serverWindow = res.success && typeof res.data?.attribution_window_days === "number" ? res.data.attribution_window_days : DEFAULT_TTL_DAYS;
|
|
19540
|
+
const heldMode = res.success && res.data?.held_attribution_mode ? normalizeMode(res.data.held_attribution_mode) : null;
|
|
19541
|
+
const holdsFirstClick = holdsOtherCode && (heldMode ?? held?.mode) === "FIRST_CLICK";
|
|
19542
|
+
if (holdsFirstClick && held) {
|
|
19543
|
+
if (!mayHoldFirstClick) {
|
|
19544
|
+
restoreStoredAffiliate({
|
|
19545
|
+
code: held.code,
|
|
19546
|
+
expiresAt: held.expiresAt,
|
|
19547
|
+
windowDays: held.windowDays,
|
|
19548
|
+
mode: heldMode ?? "FIRST_CLICK"
|
|
19549
|
+
});
|
|
19550
|
+
}
|
|
19551
|
+
return held.code;
|
|
19552
|
+
}
|
|
19480
19553
|
if (res.success && res.data?.accepted && res.data.affiliate_code) {
|
|
19481
|
-
setAffiliateCode(res.data.affiliate_code);
|
|
19554
|
+
setAffiliateCode(res.data.affiliate_code, serverWindow, serverMode ?? void 0);
|
|
19482
19555
|
return res.data.affiliate_code;
|
|
19483
19556
|
}
|
|
19484
19557
|
if (res.success && res.data && res.data.accepted === false) {
|
|
19485
19558
|
clearAffiliateCode();
|
|
19486
19559
|
return null;
|
|
19487
19560
|
}
|
|
19488
|
-
return code;
|
|
19561
|
+
return mayHoldFirstClick ? held.code : code;
|
|
19489
19562
|
} catch {
|
|
19490
|
-
return code;
|
|
19563
|
+
return mayHoldFirstClick ? held.code : code;
|
|
19491
19564
|
}
|
|
19492
19565
|
}
|
|
19493
|
-
return code;
|
|
19566
|
+
return mayHoldFirstClick ? held.code : code;
|
|
19494
19567
|
}
|
|
19495
19568
|
|
|
19496
19569
|
// ../sdk/src/utils/cart-line-id.ts
|
|
@@ -21575,6 +21648,7 @@ var shoppex = {
|
|
|
21575
21648
|
validateAffiliateCode,
|
|
21576
21649
|
applyAffiliateCode,
|
|
21577
21650
|
getAffiliateCode,
|
|
21651
|
+
getStoredAffiliate,
|
|
21578
21652
|
setAffiliateCode,
|
|
21579
21653
|
clearAffiliateCode,
|
|
21580
21654
|
trackAffiliateEvent,
|