# Changelog

## 1.0.81

- `ProductCategory` carries `sort_order`, the merchant's category position from the dashboard. Product-level category references arrive sorted by it, so a theme can order its filter row and grouped sections the way the merchant arranged them instead of alphabetically.

## 1.0.80

- Keep an existing affiliate code while the server resolves its attribution policy. Checkout can no longer read a replacement before a first-click decision arrives.
- Keep the existing code and its expiry when policy resolution fails or rejects the arriving code.

## 1.0.79

- 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.
- `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.
- `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.
- `validateAffiliateCode()` passes the resolved `attribution_mode`, `attribution_window_days` and `held_attribution_mode` through instead of dropping them.
- `getStoredAffiliate()` also returns `expiresAt`, so a hold can be restored without extending the window it earned.
- 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.

## 1.0.78

- `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.
- 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.
- New export `getStoredAffiliate()`, returning `{ code, mode }` for the held attribution, or `null`.
- `setAffiliateCode(code, ttlDays, mode?)` accepts the mode to persist. Existing two-argument calls are unaffected.
- 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.
- Note: first-click is enforced in the browser, like the window itself. It is an attribution rule, not fraud protection.

## 1.0.77

- 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.

## 1.0.75

- `getStore`, `getStorefront`, `resolveStoreByDomain`, `getProducts`, `getStorefrontProductsPage`, `getProduct` and `getCategories` now forward the full failure contract (`code`, `errorParams`, `status`, `responseReceived`, `responseDefinitive`) instead of rebuilding `{ success: false, message }`, so `code === 'errors.storefront.currency_unavailable'` is branchable through every catalog read. A slug `getProduct()` whose store lookup failed returns that failure instead of a misleading not-found.

## 1.0.74

- Catalog reads now resolve the buyer currency with the same rule as cart quotes and checkout: a `?currency=` on the page URL wins, then `init({ currency })`. Before, a merchant-hosted page with `?currency=` priced the catalog in the init currency and the cart in the URL currency.
- `shoppex.invalidateCache(prefixOrKey)` matches the bare key again across every locale/currency namespace (1.0.73 keys are `<locale>|<currency>|<key>`, which made prefix invalidation a no-op).
- A rejected `init()` (invalid currency) leaves the previous configuration, including the cached shop id, untouched.

## 1.0.73

- `init({ currency })` is now enforced on every priced read (ADR-0066). `getStorefront`, `getStore`, `resolveStoreByDomain`, `getProducts`, `getStorefrontProductsPage` and `getProduct` send it as `?currency=`; before, only cart quotes and checkout used it, so a catalog could show one currency and the checkout price in another.
- A currency the shop has not enabled fails those reads with `code: 'errors.storefront.currency_unavailable'` (`errorParams.requested`, `errorParams.available`) instead of silently serving the shop default. New export `CURRENCY_UNAVAILABLE_ERROR_CODE`.
- `init` normalizes the currency to an upper-case ISO 4217 code and throws `ValidationError` for anything else.
- Response cache keys carry locale and buyer currency; a re-init with another audience no longer reads the previous audience's payloads.
- `Shop` gains `default_currency` and `available_currencies`; `Shop.currency` is documented as the currency the response is priced in.

## 1.0.72

- REMOVED: the checkout Turnstile verification lane. `mountCheckoutChallenge`, `CheckoutChallengeCallbacks`, `CheckoutChallengeFrame`, `ApiChallenge`, `SDKResponse.challenge`, `retryWithProof`, and the `autoResolveChallenge`/`turnstileToken` checkout options are gone; invoice creation is a single request again and rate-limit refusals are plain 429s. Bot protection moved to the platform edge. The `X-Idempotency-Key` machinery is unchanged.

## 1.0.70

- One replay engine: a turnstile-challenged refusal now carries `retryWithProof` — the frozen hand-off (cart bytes, codes, currency, quote proof, and the ORIGINAL idempotency key) replays with only the fresh proof attached, and the server's create-request uniqueness is the double-invoice guarantee. Client coalescing is UX-only. New `quoteToken` option; shared Turnstile frame protocol.

## 1.0.69

- Replace the timed replay-share window with real waiter tracking (a settled replay lives exactly as long as an initial duplicate is still in flight), pin the quote proof and currency through the storefront widget replay via the new `quoteToken` option and currency tri-state, and spend the embed replay budget down exactly under widget flicker.

## 1.0.68

- Harden the automatic challenge flow end to end: the interactive overlay is dismissible and always settles (timeout message, overall ceiling), failed replays are never cached, the success-share window shrinks to real latency skew, the replay pin carries currency verbatim, the coalescing key carries autoRedirect/locale, and `autoResolveChallenge: false` lets integrations keep their own challenge UX.

## 1.0.67

- Harden replay coalescing: keys carry the entry point and every effective create-intent dimension, and settled replays stay shareable for a grace window so a slow initial refusal joins the finished replay instead of minting a second invoice.

## 1.0.66

- Coalesce concurrent automatic challenge replays: overlapping identical checkout calls share one broker and one replay, so a double-fired handler can no longer mint a second invoice for one buyer action.

## 1.0.65

- Replay a challenge-refused checkout with the exact refused hand-off (cart bytes, codes, currency, email, quote proof) instead of re-reading shared storage; a cart another tab changed refuses instead of billing.

## 1.0.64

- Resolve checkout verification challenges automatically: `checkout()` and `buildCheckoutUrl()` mount the hosted broker themselves on a challenge refusal and replay the create once with the delivered proof — no merchant wiring, no second click. `mountCheckoutChallenge()` gains an `onVisibilityChange` callback.

## 1.0.63

- Keep managed checkout verification collapsed until Cloudflare requires buyer interaction.

## 1.0.62

- Fail open with an unavailable callback when the hosted checkout verification broker never completes its ready handshake.

## 1.0.61

- Add `mountCheckoutChallenge()` so custom storefront domains can complete the hosted checkout verification broker and retry invoice creation.

## 1.0.60

- Reject post-purchase `redirectUrl` values that still contain unresolved template placeholders.

## 1.0.59

- Add `redirectUrl` to `checkout()` and `buildCheckoutUrl()` for channel-specific post-purchase returns.
- Keep `currency` and the post-purchase redirect in the same idempotent invoice-create intent.
- Publish npm from the public Storefront wrapper and keep the internal CDN implementation private.
- Correct the cart custom-field and `line_id` examples in the npm and Mintlify documentation.

## 1.0.58

- Add buyer-selected `currency` to `checkout()` and `buildCheckoutUrl()` so quote and invoice creation use the same currency.

## 1.0.57

- Preserve checkout-origin Turnstile challenge details on `buildCheckoutUrl()` errors so headless callers can render the proof and retry.

## 1.0.56

- Retain checkout idempotency keys until invoice-create responses are both parseable and definitive, preventing duplicate orders after truncated response bodies.
- Expose server-requested Turnstile challenges so official storefront checkout surfaces can collect and resubmit human-verification proofs.

## 1.0.55

- Retain independent checkout idempotency keys for overlapping buyer intents, so an ambiguous response cannot duplicate an earlier invoice when another checkout starts concurrently.

## 1.0.54

- Reuse checkout idempotency keys after ambiguous transport failures, while rotating them after definitive responses or changed buyer intent.
- Forward optional `storefront_checkout` Turnstile proofs without changing the idempotent create command.

## 1.0.51

- Send a fresh idempotency key for every storefront checkout action so network retries replay safely without reviving an older abandoned cart.

## 1.0.50

- Expose authoritative paid and due amounts in customer order details.
- Expose volume discounts as a separate buyer-facing price adjustment.

## 1.0.49

- Expose the complete customer-order pricing breakdown, including buyer-paid processing fees.

## 1.0.48

- Preserve Headless customer review scores by exposing the stored `1`, `3`, and `5` sentiment levels.
- Count the 256-character review limit by visible Unicode characters, including joined emoji.

## 1.0.47

- Add authenticated `orderReview()` and `submitOrderReview()` methods to the Headless Customer SDK.
- Scope review reads and writes to the signed-in customer's own shop and order.

## 1.0.46

- Headless customer dashboards now expose Shoppex file deliverables as authenticated `downloadIndex` values instead of hosted-portal-only URLs.

## 1.0.45

- Expand `@shoppexio/storefront/customer` from order history to buyer-safe customer portal parity: account settings, support, licenses, wallet checkout handoffs, subscriptions, replacements, loyalty, warranties, favorites, affiliate links, and reseller flows.
- Validate every new customer response against the shared Shoppex wire contracts.
- Publish the matching customer portal wire contracts with the SDK release.
- Keep direct money movement, provider redirects, password/session controls, reseller balance spending, and durable API-key creation behind Shoppex-controlled portal handoffs.

## 1.0.44

- Actually ship the `@shoppexio/storefront/customer` subpath in the published tarball: 1.0.42/1.0.43 documented the entry, but the release pipeline packs `packages/sdk`, whose exports map did not carry it (`ERR_PACKAGE_PATH_NOT_EXPORTED`).

## 1.0.43

- Require an explicit file index for customer downloads so the SDK never guesses which delivered file to return.
- Expose safe `downloadIndex` descriptors for headless order details.
- Keep the local customer session when remote logout fails, so revocation can be retried.

## 1.0.42

- Add `@shoppexio/storefront/customer` for custom OTP login, customer details, order history, order details, and downloads.
- Add opt-in `rememberMe` sessions for up to 30 days.
- Return safe `downloadIndex` descriptors for delivered files and retain the local session when logout revocation can be retried.

## 0.3.1

- Add `program_enabled` to affiliate validation responses.
- Clarify disabled affiliate program handling for headless storefronts.

## 0.3.0

- Publish the active Storefront SDK runtime under `@shoppexio/storefront`.
- Include cart coupon validation types for `{ productId, variantId }`.

## 0.1.1

- Package metadata and release polish.

## 0.1.0

- first public storefront package release
- typed package entrypoint for the existing Shoppex storefront runtime
