@doswiftly/storefront-sdk 19.0.0 → 19.2.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/CHANGELOG.md +138 -0
- package/dist/core/client/create-client.d.ts.map +1 -1
- package/dist/core/client/create-client.js +8 -1
- package/dist/core/client/remote-debug-transport.d.ts +48 -0
- package/dist/core/client/remote-debug-transport.d.ts.map +1 -0
- package/dist/core/client/remote-debug-transport.js +198 -0
- package/dist/core/client/types.d.ts +51 -1
- package/dist/core/client/types.d.ts.map +1 -1
- package/dist/core/generated/operation-types.d.ts +31 -0
- package/dist/core/generated/operation-types.d.ts.map +1 -1
- package/dist/core/generated/operation-types.js +8 -0
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/operations/cart.d.ts.map +1 -1
- package/dist/core/operations/cart.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/cookies.d.ts +11 -1
- package/dist/react/cookies.d.ts.map +1 -1
- package/dist/react/cookies.js +11 -4
- package/dist/react/hooks/use-cart-manager.d.ts +7 -0
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
- package/dist/react/hooks/use-cart-manager.js +5 -2
- package/dist/react/providers/cart-manager-provider.d.ts +8 -1
- package/dist/react/providers/cart-manager-provider.d.ts.map +1 -1
- package/dist/react/providers/cart-manager-provider.js +2 -1
- package/dist/react/providers/storefront-provider.d.ts +12 -1
- package/dist/react/providers/storefront-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-provider.js +4 -3
- package/dist/react/stores/currency.store.d.ts +6 -1
- package/dist/react/stores/currency.store.d.ts.map +1 -1
- package/dist/react/stores/currency.store.js +49 -41
- package/dist/react/stores/language.store.d.ts +6 -1
- package/dist/react/stores/language.store.d.ts.map +1 -1
- package/dist/react/stores/language.store.js +46 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,143 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 19.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b60dcaf: Discount codes now report real per-code applicability and reasons on the cart and order.
|
|
8
|
+
|
|
9
|
+
**Why**: a recognised discount code previously always showed `isApplicable: true` even when it
|
|
10
|
+
reduced nothing — minimum-order not met, code expired, or no cart items within the code's product
|
|
11
|
+
scope — and `discountAllocations` only ever reflected the first code. The contract now tells the
|
|
12
|
+
truth per code, so a storefront can show the buyer exactly which codes apply and why the others do
|
|
13
|
+
not, and the order carries the same breakdown the buyer saw in the cart.
|
|
14
|
+
|
|
15
|
+
**Additive (backward-compatible)**:
|
|
16
|
+
1. `Cart.discountCodes[].isApplicable` is computed per code — `true` only when the code reduces the
|
|
17
|
+
price or grants free shipping — instead of being hard-coded to `true`.
|
|
18
|
+
2. `Cart.discountAllocations` contains one entry per applicable code, not just the first.
|
|
19
|
+
3. `cartDiscountCodesUpdate` returns a `warning` per non-applicable code
|
|
20
|
+
(`CartWarningCode.DISCOUNT_CODE_NOT_APPLICABLE`) whose localized message explains the reason. The
|
|
21
|
+
code stays on the cart, so adding a qualifying product can still activate it.
|
|
22
|
+
4. New `DiscountErrorCode.NOT_APPLICABLE_TO_CART` — the code is valid but no cart items fall within
|
|
23
|
+
its product/collection/category scope.
|
|
24
|
+
5. New `Order.discountAllocations` — the per-code breakdown frozen onto the order at checkout
|
|
25
|
+
(parity with `Cart.discountAllocations`), so a confirmation page renders the same rows.
|
|
26
|
+
6. `cartValidateDiscountCode` (the read-only preview) now mirrors the apply result: a code that is
|
|
27
|
+
recognised but out of scope for the cart returns `isValid: false` with
|
|
28
|
+
`error.code: NOT_APPLICABLE_TO_CART`. Inline "is this code valid?" feedback no longer reports a code
|
|
29
|
+
as valid when applying it would reduce nothing — preview and apply always agree.
|
|
30
|
+
|
|
31
|
+
**Usage example**:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const { cart, warnings } = await cartDiscountCodesUpdate({
|
|
35
|
+
id,
|
|
36
|
+
discountCodes: ["SUMMER20"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
for (const entry of cart.discountCodes) {
|
|
40
|
+
if (!entry.isApplicable) {
|
|
41
|
+
// The code is recognised but not reducing the price — explain why.
|
|
42
|
+
const reason = warnings.find((w) => w.target === entry.code)?.message;
|
|
43
|
+
showInlineHint(entry.code, reason);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Migration checklist for existing storefronts**:
|
|
49
|
+
- [ ] If you render a discount chip, branch on `discountCodes[].isApplicable` instead of assuming the code applied.
|
|
50
|
+
- [ ] Surface `cartDiscountCodesUpdate.warnings` (code `DISCOUNT_CODE_NOT_APPLICABLE`) next to the chip to tell the buyer why a code is inactive.
|
|
51
|
+
- [ ] Sum `discountAllocations[].amount` to display a per-code discount breakdown; it equals the cart/order discount total.
|
|
52
|
+
- [ ] Optionally read `Order.discountAllocations` on the confirmation page for the same breakdown after checkout.
|
|
53
|
+
- [ ] If you preview codes with `cartValidateDiscountCode`, handle `isValid: false` + `NOT_APPLICABLE_TO_CART` — don't show "valid" for a scope-mismatch code (the preview now matches what apply returns).
|
|
54
|
+
|
|
55
|
+
- d7b0185: Add `pickupConfig` to `AvailableShippingMethod` — render carrier pickup-point pickers at checkout
|
|
56
|
+
|
|
57
|
+
Shipping methods that deliver to a pickup point (`deliveryType` `LOCKER` or `PICKUP_POINT`) now expose a `pickupConfig` object with everything the storefront needs to let the buyer choose a collection point:
|
|
58
|
+
- `selectionMode: WIDGET` — render the carrier map widget. `widgetToken` (a public, domain-scoped browser token, e.g. the InPost Geowidget token — never a carrier API secret) and `scriptUrl` (the widget script to load) are provided.
|
|
59
|
+
- `selectionMode: SEARCH` — no browser widget; look points up server-side and render your own list. `widgetToken` and `scriptUrl` are `null` for this mode.
|
|
60
|
+
|
|
61
|
+
`pickupConfig` is `null` for `HOME` (street-address) delivery and when the merchant has not yet configured the carrier's public widget token — do not render a map in that case. The `AvailableShippingMethod` fragment now includes `pickupConfig`, so queries that spread it pick the new field up automatically once you regenerate your types.
|
|
62
|
+
|
|
63
|
+
Flow: render the picker from `pickupConfig`, then send the chosen point with `cartSetShippingAddress({ pickupPoint })` before `cartSelectShippingMethod` — the latter rejects a pickup method selected without a point (`PICKUP_POINT_REQUIRED`).
|
|
64
|
+
|
|
65
|
+
- 88cd617: Expose pickup-point configuration on shipping methods, so a storefront can render the carrier point picker directly from the cart's available shipping methods.
|
|
66
|
+
|
|
67
|
+
`availableShippingMethods` now returns a `pickupConfig` for methods whose `deliveryType` is `PICKUP_POINT` or `LOCKER`:
|
|
68
|
+
- `provider` — carrier code the config belongs to (e.g. `"inpost"`).
|
|
69
|
+
- `selectionMode` — `WIDGET` (render the carrier map) or `SEARCH` (query points server-side and render your own list).
|
|
70
|
+
- `widgetToken` — public, domain-scoped widget token (e.g. the InPost Geowidget token) used to initialise the map. **Never** a carrier API secret. Null in `SEARCH` mode and when the merchant has not configured the public token.
|
|
71
|
+
- `scriptUrl` — CDN URL of the carrier widget script to load before rendering the map. Null in `SEARCH` mode.
|
|
72
|
+
|
|
73
|
+
`pickupConfig` is `null` for `HOME` delivery methods.
|
|
74
|
+
|
|
75
|
+
**Additive (backward-compatible)** — existing code that does not read `pickupConfig` keeps working unchanged.
|
|
76
|
+
|
|
77
|
+
## 19.1.0
|
|
78
|
+
|
|
79
|
+
### Minor Changes
|
|
80
|
+
|
|
81
|
+
- 868894e: Add remote debug shipping for both GraphQL **and** cookie-lifecycle events, so you can trace a real shopper session end-to-end instead of reproducing it with DevTools open.
|
|
82
|
+
|
|
83
|
+
**Why**: the most useful checkout signal is often "when was the `cart-id` / currency / language cookie set or cleared?" (e.g. `cart-id` cleared by `complete()`), but debug output previously covered only GraphQL request/response. Now both flow through one channel keyed by a single `sessionId`, so one filter in your logs gives the full interleaved timeline.
|
|
84
|
+
|
|
85
|
+
**Additive (backward-compatible)** — everything is opt-in and off by default:
|
|
86
|
+
1. `DebugOptions.remote?: boolean | RemoteDebugOptions | RemoteDebugSink` — ship debug events to a backend ingest endpoint. Pass a pre-built transport (a `RemoteDebugSink`) to share **one** channel/`sessionId` across the client and the cookie stores.
|
|
87
|
+
2. New `createRemoteDebugTransport(...)` factory (+ `RemoteDebugTransport` / `RemoteDebugSink` / `RemoteDebugOptions` types).
|
|
88
|
+
3. Cookie stores emit a new `DebugEvent` with `phase: 'cookie'` (`{ name, action: 'set' | 'clear', value?, maxAge? }`) when wired with a sink:
|
|
89
|
+
- `createBrowserCartCookieStore({ onDebug })` — `cart-id` (when you own the cookie store).
|
|
90
|
+
- `useCartManager({ cookieDebug })` / `<CartManagerProvider cookieDebug={...}>` — `cart-id` when the cart manager owns the cookie store.
|
|
91
|
+
- `createCurrencyStore({ onDebug })` / `createLanguageStore(initial, { onDebug })` — currency / language.
|
|
92
|
+
- `<StorefrontProvider cookieDebug={...}>` forwards the sink to the currency + language stores.
|
|
93
|
+
4. `DebugOptions`, `DebugEvent`, `RemoteDebugOptions`, `RemoteDebugSink` are exported for typing.
|
|
94
|
+
|
|
95
|
+
**Usage example** (one shared channel across GraphQL + cookies):
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {
|
|
99
|
+
createStorefrontClient,
|
|
100
|
+
createRemoteDebugTransport,
|
|
101
|
+
createBrowserCartCookieStore,
|
|
102
|
+
} from "@doswiftly/storefront-sdk";
|
|
103
|
+
|
|
104
|
+
const debugOn = process.env.NEXT_PUBLIC_SDK_DEBUG_REMOTE === "true";
|
|
105
|
+
const transport = debugOn
|
|
106
|
+
? createRemoteDebugTransport({
|
|
107
|
+
endpoint: `${apiUrl}/storefront/debug-logs`,
|
|
108
|
+
shopSlug,
|
|
109
|
+
fetch: globalThis.fetch,
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
112
|
+
|
|
113
|
+
const client = createStorefrontClient({
|
|
114
|
+
apiUrl,
|
|
115
|
+
shopSlug,
|
|
116
|
+
debug: transport
|
|
117
|
+
? { userErrors: true, response: true, timing: true, remote: transport }
|
|
118
|
+
: false,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// cart-id set/clear on the same channel:
|
|
122
|
+
const cartCookieStore = createBrowserCartCookieStore(
|
|
123
|
+
transport ? { onDebug: (e) => transport.capture(e) } : undefined,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// currency + language via the provider:
|
|
127
|
+
// <StorefrontProvider cookieDebug={transport ? (e) => transport.capture(e) : undefined} … />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Behaviour:
|
|
131
|
+
- **Batched** (default 10 events / 5s); `keepalive` is used on page-hide/unload flushes so trailing events survive a navigation or payment-gateway redirect.
|
|
132
|
+
- **Fire-and-forget**: a failed send is swallowed — telemetry can never break the request that produced it.
|
|
133
|
+
- Cookies are **not** sent (`credentials: 'omit'`); the shop is identified by the `X-Shop-Slug` header.
|
|
134
|
+
- **Redaction**: the SDK masks `Authorization` / `customerAccessToken` in _headers_. It does **not** redact request `variables` or response bodies — the backend ingest endpoint masks emails + credential-named fields. Enable `response` / `headers` only for operations whose payload you're comfortable sending to your backend.
|
|
135
|
+
- Default endpoint is `${apiUrl}/storefront/debug-logs`; override via `RemoteDebugOptions.endpoint`.
|
|
136
|
+
|
|
137
|
+
**Migration checklist for existing storefronts**:
|
|
138
|
+
- [ ] No action required — all of the above is opt-in and defaults to off.
|
|
139
|
+
- [ ] To adopt: build one transport (gated behind an env flag), pass it to `createStorefrontClient`, the cart cookie store, and `<StorefrontProvider cookieDebug>`, then confirm requests to `/storefront/debug-logs`.
|
|
140
|
+
|
|
3
141
|
## 19.0.0
|
|
4
142
|
|
|
5
143
|
### Major Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAQjB,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAgHvF"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 0 runtime dependencies.
|
|
12
12
|
*/
|
|
13
13
|
import { createExecute, resolveDebugOptions } from './execute';
|
|
14
|
+
import { maybeAttachRemoteTransport } from './remote-debug-transport';
|
|
14
15
|
import { compose } from './compose';
|
|
15
16
|
import { dedupe } from './dedupe';
|
|
16
17
|
import { hashQuery } from './hash';
|
|
@@ -24,7 +25,13 @@ export function createStorefrontClient(config) {
|
|
|
24
25
|
// Normalise the public `boolean | 'verbose' | DebugOptions` union into the
|
|
25
26
|
// canonical ResolvedDebugOptions shape (or null = no logging). Env var
|
|
26
27
|
// fallback (`DOSWIFTLY_SDK_DEBUG`) applies only when `debug` is undefined.
|
|
27
|
-
|
|
28
|
+
// When `debug.remote` is set, the resolved sink is wrapped so every event is
|
|
29
|
+
// also shipped to the backend ingest endpoint (default `${apiUrl}/storefront/debug-logs`).
|
|
30
|
+
const resolvedDebug = maybeAttachRemoteTransport(resolveDebugOptions(debug), debug, {
|
|
31
|
+
apiUrl,
|
|
32
|
+
shopSlug,
|
|
33
|
+
fetch: customFetch,
|
|
34
|
+
});
|
|
28
35
|
// Create the innermost execute function (native fetch)
|
|
29
36
|
const innerExecute = createExecute({ endpoint, fetch: customFetch, debug: resolvedDebug });
|
|
30
37
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote debug transport — batches debug events and ships them to a backend ingest
|
|
3
|
+
* endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
|
|
4
|
+
*
|
|
5
|
+
* Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
|
|
6
|
+
* storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
|
|
7
|
+
* after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
|
|
8
|
+
* matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
|
|
9
|
+
* otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
|
|
10
|
+
*
|
|
11
|
+
* Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
|
|
12
|
+
* call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
|
|
13
|
+
*/
|
|
14
|
+
import type { DebugEvent, DebugOptions } from './types';
|
|
15
|
+
import type { ResolvedDebugOptions } from './execute';
|
|
16
|
+
export interface RemoteDebugTransportConfig {
|
|
17
|
+
/** Fully-resolved ingest endpoint URL. */
|
|
18
|
+
endpoint: string;
|
|
19
|
+
/** Shop slug sent as `X-Shop-Slug` so the backend can resolve the tenant. */
|
|
20
|
+
shopSlug: string;
|
|
21
|
+
/** Fetch implementation (the same one the client transport uses). */
|
|
22
|
+
fetch: typeof globalThis.fetch;
|
|
23
|
+
batchSize?: number;
|
|
24
|
+
flushIntervalMs?: number;
|
|
25
|
+
sessionId?: string;
|
|
26
|
+
sdkVersion?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RemoteDebugTransport {
|
|
29
|
+
/** Buffer one debug event; flushes automatically per the batching policy. */
|
|
30
|
+
capture(event: DebugEvent): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createRemoteDebugTransport(config: RemoteDebugTransportConfig): RemoteDebugTransport;
|
|
33
|
+
/**
|
|
34
|
+
* Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
|
|
35
|
+
* the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function cookieDebugEvent(name: string, action: 'set' | 'clear', value?: string, maxAge?: number): DebugEvent;
|
|
38
|
+
/**
|
|
39
|
+
* If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
|
|
40
|
+
* remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
|
|
41
|
+
* local `log` behaviour is preserved exactly.
|
|
42
|
+
*/
|
|
43
|
+
export declare function maybeAttachRemoteTransport(resolved: ResolvedDebugOptions | null, debugConfig: boolean | 'verbose' | DebugOptions | undefined, context: {
|
|
44
|
+
apiUrl: string;
|
|
45
|
+
shopSlug: string;
|
|
46
|
+
fetch: typeof globalThis.fetch;
|
|
47
|
+
}): ResolvedDebugOptions | null;
|
|
48
|
+
//# sourceMappingURL=remote-debug-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-debug-transport.d.ts","sourceRoot":"","sources":["../../../src/core/client/remote-debug-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAuC,MAAM,SAAS,CAAC;AAC7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMtD,MAAM,WAAW,0BAA0B;IACzC,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAClC;AAuDD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,oBAAoB,CAmFnG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,GAAG,OAAO,EACvB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAKZ;AAOD;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EACrC,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,EAC3D,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,GAC5E,oBAAoB,GAAG,IAAI,CA0C7B"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote debug transport — batches debug events and ships them to a backend ingest
|
|
3
|
+
* endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
|
|
4
|
+
*
|
|
5
|
+
* Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
|
|
6
|
+
* storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
|
|
7
|
+
* after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
|
|
8
|
+
* matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
|
|
9
|
+
* otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
|
|
10
|
+
*
|
|
11
|
+
* Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
|
|
12
|
+
* call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
|
|
13
|
+
*/
|
|
14
|
+
import { CART_COOKIE_NAME } from '../cart/cookie-config';
|
|
15
|
+
const DEFAULT_BATCH_SIZE = 10;
|
|
16
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
17
|
+
/**
|
|
18
|
+
* Pull identifying ids out of operation variables so the backend log is filterable by cart /
|
|
19
|
+
* order without parsing the whole payload. `cartId` comes from an explicit `cartId` variable, or
|
|
20
|
+
* from the `id` variable on cart operations (cart mutations pass the cart as `id: cartId`). The
|
|
21
|
+
* operation-name check is anchored (`^cart`) so only cart operations (`CartAddLines`, …) qualify —
|
|
22
|
+
* an unrelated op that merely contains "cart" (e.g. `ProductInCartBadge`) won't mislabel its `id`.
|
|
23
|
+
*/
|
|
24
|
+
function extractIdentifiers(event) {
|
|
25
|
+
// Cookie events carry the value directly — surface the cart-id so its set/clear correlates with
|
|
26
|
+
// the cart's GraphQL operations under one `cartId` filter.
|
|
27
|
+
if (event.phase === 'cookie') {
|
|
28
|
+
const cookie = event.data;
|
|
29
|
+
return cookie.name === CART_COOKIE_NAME && typeof cookie.value === 'string'
|
|
30
|
+
? { cartId: cookie.value }
|
|
31
|
+
: {};
|
|
32
|
+
}
|
|
33
|
+
const variables = event.data.variables;
|
|
34
|
+
if (!variables || typeof variables !== 'object')
|
|
35
|
+
return {};
|
|
36
|
+
const v = variables;
|
|
37
|
+
const op = event.operationName ?? '';
|
|
38
|
+
const out = {};
|
|
39
|
+
const cartId = typeof v.cartId === 'string'
|
|
40
|
+
? v.cartId
|
|
41
|
+
: /^cart/i.test(op) && typeof v.id === 'string'
|
|
42
|
+
? v.id
|
|
43
|
+
: undefined;
|
|
44
|
+
if (cartId)
|
|
45
|
+
out.cartId = cartId;
|
|
46
|
+
if (typeof v.orderId === 'string')
|
|
47
|
+
out.orderId = v.orderId;
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate a session correlation id. Prefers `crypto.randomUUID`; falls back to a
|
|
52
|
+
* timestamp + random suffix when unavailable (uniqueness within one client is all that is
|
|
53
|
+
* required here — there is no security property to preserve).
|
|
54
|
+
*/
|
|
55
|
+
function generateSessionId() {
|
|
56
|
+
const c = globalThis.crypto;
|
|
57
|
+
if (c && typeof c.randomUUID === 'function')
|
|
58
|
+
return c.randomUUID();
|
|
59
|
+
return `sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
60
|
+
}
|
|
61
|
+
export function createRemoteDebugTransport(config) {
|
|
62
|
+
const { endpoint, shopSlug, fetch: fetchFn, batchSize = DEFAULT_BATCH_SIZE, flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS, sdkVersion, } = config;
|
|
63
|
+
const sessionId = config.sessionId ?? generateSessionId();
|
|
64
|
+
let buffer = [];
|
|
65
|
+
let timer = null;
|
|
66
|
+
function clearFlushTimer() {
|
|
67
|
+
if (timer !== null) {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
timer = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function flush(useKeepalive = false) {
|
|
73
|
+
if (buffer.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
const events = buffer;
|
|
76
|
+
buffer = [];
|
|
77
|
+
clearFlushTimer();
|
|
78
|
+
// Fire-and-forget — a transport failure must never surface to the caller.
|
|
79
|
+
void Promise.resolve()
|
|
80
|
+
.then(() => fetchFn(endpoint, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'X-Shop-Slug': shopSlug,
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify({ sessionId, sdkVersion, events }),
|
|
87
|
+
// `keepalive` only for unload flushes — on a live page a large batch can exceed the
|
|
88
|
+
// browser's keepalive body quota (~64KB) and be rejected (then silently swallowed),
|
|
89
|
+
// whereas an ordinary fetch has no such cap. Unload flushes need it to survive navigation.
|
|
90
|
+
keepalive: useKeepalive,
|
|
91
|
+
credentials: 'omit',
|
|
92
|
+
}))
|
|
93
|
+
.catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
// Flush on page hide / unload so trailing events survive a navigation or gateway redirect.
|
|
96
|
+
// Browser-only — guarded so the core stays usable in Node / Edge / Deno. These listeners live for
|
|
97
|
+
// the transport's lifetime (no teardown): the storefront client is expected to be created once per
|
|
98
|
+
// app (the SDK's singleton/Context convention), so they are registered once. Do not build a new
|
|
99
|
+
// client with `debug.remote` on every render — that would accumulate listeners.
|
|
100
|
+
const g = globalThis;
|
|
101
|
+
if (typeof g.addEventListener === 'function') {
|
|
102
|
+
g.addEventListener('pagehide', () => flush(true));
|
|
103
|
+
g.addEventListener('visibilitychange', () => {
|
|
104
|
+
if (g.document?.visibilityState === 'hidden')
|
|
105
|
+
flush(true);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
capture(event) {
|
|
110
|
+
buffer.push({
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
phase: event.phase,
|
|
113
|
+
operationName: event.operationName ?? null,
|
|
114
|
+
...extractIdentifiers(event),
|
|
115
|
+
data: event.data,
|
|
116
|
+
});
|
|
117
|
+
if (buffer.length >= batchSize) {
|
|
118
|
+
flush();
|
|
119
|
+
}
|
|
120
|
+
else if (timer === null) {
|
|
121
|
+
timer = setTimeout(() => flush(), flushIntervalMs);
|
|
122
|
+
// Don't keep a Node/Edge event loop alive for a pending flush (browser timers are numbers
|
|
123
|
+
// with no `unref`; Node/Edge timers are objects that have it).
|
|
124
|
+
if (timer && typeof timer === 'object' && 'unref' in timer) {
|
|
125
|
+
timer.unref();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
|
|
133
|
+
* the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
|
|
134
|
+
*/
|
|
135
|
+
export function cookieDebugEvent(name, action, value, maxAge) {
|
|
136
|
+
const data = { name, action };
|
|
137
|
+
if (value !== undefined)
|
|
138
|
+
data.value = value;
|
|
139
|
+
if (maxAge !== undefined)
|
|
140
|
+
data.maxAge = maxAge;
|
|
141
|
+
return { phase: 'cookie', operationName: undefined, data };
|
|
142
|
+
}
|
|
143
|
+
/** True when `remote` is a pre-built sink (has a callable `capture`) rather than options/boolean. */
|
|
144
|
+
function isRemoteSink(remote) {
|
|
145
|
+
return typeof remote === 'object' && typeof remote.capture === 'function';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
|
|
149
|
+
* remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
|
|
150
|
+
* local `log` behaviour is preserved exactly.
|
|
151
|
+
*/
|
|
152
|
+
export function maybeAttachRemoteTransport(resolved, debugConfig, context) {
|
|
153
|
+
if (!resolved)
|
|
154
|
+
return resolved;
|
|
155
|
+
// `remote` only exists on the object form of `debug` (`DebugOptions`).
|
|
156
|
+
if (typeof debugConfig !== 'object' || !debugConfig.remote) {
|
|
157
|
+
return resolved;
|
|
158
|
+
}
|
|
159
|
+
const remote = debugConfig.remote;
|
|
160
|
+
// A pre-built sink (e.g. a transport shared with the cookie stores) is used as-is, so the whole
|
|
161
|
+
// app reports through one channel + sessionId. Otherwise build a transport from the options.
|
|
162
|
+
let transport;
|
|
163
|
+
if (isRemoteSink(remote)) {
|
|
164
|
+
transport = remote;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const remoteOpts = remote === true ? {} : remote;
|
|
168
|
+
transport = createRemoteDebugTransport({
|
|
169
|
+
endpoint: remoteOpts.endpoint ?? `${context.apiUrl.replace(/\/$/, '')}/storefront/debug-logs`,
|
|
170
|
+
shopSlug: context.shopSlug,
|
|
171
|
+
fetch: context.fetch,
|
|
172
|
+
batchSize: remoteOpts.batchSize,
|
|
173
|
+
flushIntervalMs: remoteOpts.flushIntervalMs,
|
|
174
|
+
sessionId: remoteOpts.sessionId,
|
|
175
|
+
sdkVersion: remoteOpts.sdkVersion,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const originalLog = resolved.log;
|
|
179
|
+
return {
|
|
180
|
+
...resolved,
|
|
181
|
+
// Telemetry must never break the request: `execute` calls `debug.log` synchronously outside any
|
|
182
|
+
// try/catch, so a throwing local sink or remote `capture` is swallowed here.
|
|
183
|
+
log: (event) => {
|
|
184
|
+
try {
|
|
185
|
+
originalLog(event);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* local sink threw — ignore */
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
transport.capture(event);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* remote sink threw — ignore */
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -110,14 +110,64 @@ export interface DebugOptions {
|
|
|
110
110
|
userErrors?: boolean;
|
|
111
111
|
/** Custom logger sink. Default `console.log('[StorefrontSDK]', event.phase, event.operationName, event.data)`. Use for routing into `pino` / `winston`. */
|
|
112
112
|
log?: (event: DebugEvent) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Ship debug events to a remote ingest endpoint, in addition to the local `log` sink.
|
|
115
|
+
*
|
|
116
|
+
* Pass `true` to use defaults, or a `RemoteDebugOptions` object to customise the endpoint and
|
|
117
|
+
* batching. Events are buffered and sent with `fetch` (with `keepalive` on page-unload flushes so
|
|
118
|
+
* trailing events survive a navigation); a failed send is swallowed so telemetry can never break
|
|
119
|
+
* the request that produced it. Cookies are not sent (`credentials: 'omit'`). Default `false`.
|
|
120
|
+
*
|
|
121
|
+
* Redaction note: the SDK masks `Authorization` / `customerAccessToken` in *headers*. It does NOT
|
|
122
|
+
* redact the request `variables` or response body it ships — the backend ingest endpoint is the
|
|
123
|
+
* safety net there (it masks emails and credential-named fields). Enable `response` / `headers`
|
|
124
|
+
* only for operations whose payload you are comfortable sending to your backend.
|
|
125
|
+
*
|
|
126
|
+
* Which events are shipped is governed by the other flags above — `remote: true` alone ships the
|
|
127
|
+
* minimal set (request variables + response status + `userErrors`); enable `response` / `headers`
|
|
128
|
+
* / `timing` to ship more.
|
|
129
|
+
*
|
|
130
|
+
* Pass a pre-built transport (a `RemoteDebugSink`, e.g. from `createRemoteDebugTransport`) to
|
|
131
|
+
* share ONE channel + sessionId across the GraphQL client and the cookie stores, so a single
|
|
132
|
+
* `sessionId` in your logs gives the full interleaved timeline (GraphQL + cookie set/clear).
|
|
133
|
+
*/
|
|
134
|
+
remote?: boolean | RemoteDebugOptions | RemoteDebugSink;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Configuration for the remote debug transport (`DebugOptions.remote`). All fields optional.
|
|
138
|
+
*/
|
|
139
|
+
export interface RemoteDebugOptions {
|
|
140
|
+
/** Ingest endpoint URL. Default `${apiUrl}/storefront/debug-logs`. */
|
|
141
|
+
endpoint?: string;
|
|
142
|
+
/** Flush the buffer once this many events accumulate. Default `10`. */
|
|
143
|
+
batchSize?: number;
|
|
144
|
+
/** Flush the buffer after this many milliseconds even if `batchSize` is not reached. Default `5000`. */
|
|
145
|
+
flushIntervalMs?: number;
|
|
146
|
+
/** Correlation id grouping every event from one client instance. Default a generated id. */
|
|
147
|
+
sessionId?: string;
|
|
148
|
+
/** Optional client/build version label attached to every batch. */
|
|
149
|
+
sdkVersion?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Minimal surface of a debug transport — accepts already-built `DebugEvent`s. A `RemoteDebugSink`
|
|
153
|
+
* created once (via `createRemoteDebugTransport`) and shared between the GraphQL client and the
|
|
154
|
+
* cookie stores forms a single debug channel with one `sessionId`.
|
|
155
|
+
*/
|
|
156
|
+
export interface RemoteDebugSink {
|
|
157
|
+
capture(event: DebugEvent): void;
|
|
113
158
|
}
|
|
114
159
|
/**
|
|
115
160
|
* Structured event handed to a custom `DebugOptions.log` sink. Stable contract:
|
|
116
161
|
* `phase` and `operationName` are always present; `data` carries the merged
|
|
117
162
|
* payload selected by `DebugOptions` flags.
|
|
163
|
+
*
|
|
164
|
+
* `phase: 'cookie'` events come from the SDK cookie layer (cart / currency / language) rather than
|
|
165
|
+
* the GraphQL transport — `operationName` is `undefined` and `data` is
|
|
166
|
+
* `{ name, action: 'set' | 'clear', value?, maxAge? }`. They let you see exactly when a cookie was
|
|
167
|
+
* written or removed (e.g. `cart-id` cleared by `complete()`) alongside the GraphQL timeline.
|
|
118
168
|
*/
|
|
119
169
|
export interface DebugEvent {
|
|
120
|
-
phase: 'request' | 'response';
|
|
170
|
+
phase: 'request' | 'response' | 'cookie';
|
|
121
171
|
operationName: string | undefined;
|
|
122
172
|
data: Record<string, unknown>;
|
|
123
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;;;GAKG;AACH,qBAAa,mBAAmB,CAAC,OAAO,GAAG,OAAO,EAAE,UAAU,GAAG,OAAO,CAAE,SAAQ,MAAM;IAIpD,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IAH7D,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC;gBAEnC,KAAK,EAAE,MAAM,EAAS,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,YAAA;IAIpD,QAAQ,IAAI,MAAM;CAG5B;AAMD,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,wDAAwD;IACxD,UAAU,EAAE,OAAO,CAAC;IACpB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,2BAA2B;IAC3B,IAAI,EAAE,CAAC,CAAC;IACR,8BAA8B;IAC9B,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAMD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAMD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAMhG,MAAM,WAAW,YAAY;IAC3B,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB;IACjB,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAMzC;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,2GAA2G;IAC3G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wGAAwG;IACxG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gKAAgK;IAChK,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sIAAsI;IACtI,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2JAA2J;IAC3J,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -254,6 +254,8 @@ export type AvailableShippingMethod = {
|
|
|
254
254
|
isFree: Scalars['Boolean']['output'];
|
|
255
255
|
/** Display name of the method (e.g. "DPD Standard"). */
|
|
256
256
|
name: Scalars['String']['output'];
|
|
257
|
+
/** Pickup-point selection config — present only for pickup methods (deliveryType LOCKER / PICKUP_POINT) whose carrier exposes a public pickup mechanism. Null for HOME delivery and when the merchant has not configured the carrier's public widget token. Carries only browser-safe data (e.g. the InPost Geowidget public token), never the carrier API secret. */
|
|
258
|
+
pickupConfig?: Maybe<ShippingPickupConfig>;
|
|
257
259
|
/** Cost of the method for the current cart (in the buyer preferred currency). */
|
|
258
260
|
price: Money;
|
|
259
261
|
/** Merchant-configured display position — lower values come first in the picker. */
|
|
@@ -997,6 +999,7 @@ export type CartWarning = {
|
|
|
997
999
|
target: Scalars['String']['output'];
|
|
998
1000
|
};
|
|
999
1001
|
export declare const CartWarningCode: {
|
|
1002
|
+
readonly DiscountCodeNotApplicable: "DISCOUNT_CODE_NOT_APPLICABLE";
|
|
1000
1003
|
readonly MerchandiseNotAvailable: "MERCHANDISE_NOT_AVAILABLE";
|
|
1001
1004
|
readonly MerchandiseNotEnoughStock: "MERCHANDISE_NOT_ENOUGH_STOCK";
|
|
1002
1005
|
readonly PaymentsAmountRegionMismatch: "PAYMENTS_AMOUNT_REGION_MISMATCH";
|
|
@@ -1587,6 +1590,7 @@ export declare const DiscountErrorCode: {
|
|
|
1587
1590
|
readonly Inactive: "INACTIVE";
|
|
1588
1591
|
readonly MinimumOrderNotMet: "MINIMUM_ORDER_NOT_MET";
|
|
1589
1592
|
readonly MinimumQuantityNotMet: "MINIMUM_QUANTITY_NOT_MET";
|
|
1593
|
+
readonly NotApplicableToCart: "NOT_APPLICABLE_TO_CART";
|
|
1590
1594
|
readonly NotFound: "NOT_FOUND";
|
|
1591
1595
|
readonly NotStarted: "NOT_STARTED";
|
|
1592
1596
|
readonly ShopNotFound: "SHOP_NOT_FOUND";
|
|
@@ -2343,6 +2347,8 @@ export type Order = Node & {
|
|
|
2343
2347
|
cancelledAt?: Maybe<Scalars['DateTime']['output']>;
|
|
2344
2348
|
/** When the order was confirmed (e.g. payment authorised / approved). Null until confirmation. */
|
|
2345
2349
|
confirmedAt?: Maybe<Scalars['DateTime']['output']>;
|
|
2350
|
+
/** Per-code discount allocations on the order (parity with `Cart.discountAllocations`). One entry per code that reduced the price; empty when no discount applied. The sum of `amount` equals the order-level discount. */
|
|
2351
|
+
discountAllocations: Array<OrderDiscountAllocation>;
|
|
2346
2352
|
/** When the order expired (e.g. pending payment timed out). Null when not expired. */
|
|
2347
2353
|
expiredAt?: Maybe<Scalars['DateTime']['output']>;
|
|
2348
2354
|
/** Fulfillment progress (UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED, IN_TRANSIT, DELIVERED, PARTIALLY_RETURNED, RETURNED, LOST). */
|
|
@@ -2380,6 +2386,12 @@ export type OrderConnection = {
|
|
|
2380
2386
|
/** Total orders count */
|
|
2381
2387
|
totalCount: Scalars['Int']['output'];
|
|
2382
2388
|
};
|
|
2389
|
+
export type OrderDiscountAllocation = {
|
|
2390
|
+
/** Amount discounted by this code on the order, in the order currency. */
|
|
2391
|
+
amount: Money;
|
|
2392
|
+
/** The discount code that produced this allocation. */
|
|
2393
|
+
discountCode: Scalars['String']['output'];
|
|
2394
|
+
};
|
|
2383
2395
|
export type OrderEdge = {
|
|
2384
2396
|
/** Cursor */
|
|
2385
2397
|
cursor: Scalars['String']['output'];
|
|
@@ -2637,6 +2649,13 @@ export type PickupPointInput = {
|
|
|
2637
2649
|
/** Courier network code (e.g. inpost, orlen, dpd) */
|
|
2638
2650
|
provider: Scalars['String']['input'];
|
|
2639
2651
|
};
|
|
2652
|
+
export declare const PickupSelectionMode: {
|
|
2653
|
+
/** No browser widget — query points server-side (by city / postal code) and render your own list. `widgetToken` / `scriptUrl` are null for this mode. */
|
|
2654
|
+
readonly Search: "SEARCH";
|
|
2655
|
+
/** Render the carrier map widget (e.g. InPost Geowidget) initialised with `pickupConfig.widgetToken` + `pickupConfig.scriptUrl`. The buyer picks a point on the map. */
|
|
2656
|
+
readonly Widget: "WIDGET";
|
|
2657
|
+
};
|
|
2658
|
+
export type PickupSelectionMode = typeof PickupSelectionMode[keyof typeof PickupSelectionMode];
|
|
2640
2659
|
export type PointsEstimate = {
|
|
2641
2660
|
/** Base points to earn */
|
|
2642
2661
|
basePoints: Scalars['Int']['output'];
|
|
@@ -3456,6 +3475,16 @@ export type ShippingCarrier = {
|
|
|
3456
3475
|
/** Carrier service code as configured by the merchant (e.g. "inpost_paczkomat"). Useful when integrating carrier widgets. */
|
|
3457
3476
|
serviceCode?: Maybe<Scalars['String']['output']>;
|
|
3458
3477
|
};
|
|
3478
|
+
export type ShippingPickupConfig = {
|
|
3479
|
+
/** Carrier code this config belongs to (e.g. "inpost"). Matches `carrier.serviceCode`'s provider. */
|
|
3480
|
+
provider: Scalars['String']['output'];
|
|
3481
|
+
/** CDN URL of the carrier widget script to load before rendering the map. Null for SEARCH mode. */
|
|
3482
|
+
scriptUrl?: Maybe<Scalars['String']['output']>;
|
|
3483
|
+
/** How to let the buyer pick a point — WIDGET (carrier map) or SEARCH (server-side point lookup). */
|
|
3484
|
+
selectionMode: PickupSelectionMode;
|
|
3485
|
+
/** PUBLIC widget token used to initialise the carrier map (InPost Geowidget domain-scoped token). Never the carrier API secret. Null for SEARCH mode and when the merchant has not configured the public token (do not render the map — offer SEARCH or hide the method). */
|
|
3486
|
+
widgetToken?: Maybe<Scalars['String']['output']>;
|
|
3487
|
+
};
|
|
3459
3488
|
export type Shop = {
|
|
3460
3489
|
/** Shop physical address */
|
|
3461
3490
|
address?: Maybe<ShopAddress>;
|
|
@@ -3980,6 +4009,7 @@ export type CartAvailableShippingMethodsQuery = {
|
|
|
3980
4009
|
cart?: Maybe<(Pick<Cart, 'id' | 'requiresShipping'> & {
|
|
3981
4010
|
availableShippingMethods: {
|
|
3982
4011
|
methods: Array<(Pick<AvailableShippingMethod, 'id' | 'name' | 'description' | 'deliveryType' | 'isFree' | 'sortOrder'> & {
|
|
4012
|
+
pickupConfig?: Maybe<Pick<ShippingPickupConfig, 'provider' | 'selectionMode' | 'widgetToken' | 'scriptUrl'>>;
|
|
3983
4013
|
carrier?: Maybe<(Pick<ShippingCarrier, 'id' | 'name' | 'serviceCode'> & {
|
|
3984
4014
|
logo?: Maybe<Pick<Image, 'id' | 'url' | 'altText' | 'width' | 'height' | 'thumbhash'>>;
|
|
3985
4015
|
})>;
|
|
@@ -5227,6 +5257,7 @@ export type PageInfoFragment = Pick<PageInfo, 'hasNextPage' | 'hasPreviousPage'
|
|
|
5227
5257
|
export type UserErrorFragment = Pick<UserError, 'message' | 'code' | 'field'>;
|
|
5228
5258
|
export type CartWarningFragment = Pick<CartWarning, 'message' | 'code' | 'target'>;
|
|
5229
5259
|
export type AvailableShippingMethodFragment = (Pick<AvailableShippingMethod, 'id' | 'name' | 'description' | 'deliveryType' | 'isFree' | 'sortOrder'> & {
|
|
5260
|
+
pickupConfig?: Maybe<Pick<ShippingPickupConfig, 'provider' | 'selectionMode' | 'widgetToken' | 'scriptUrl'>>;
|
|
5230
5261
|
carrier?: Maybe<(Pick<ShippingCarrier, 'id' | 'name' | 'serviceCode'> & {
|
|
5231
5262
|
logo?: Maybe<Pick<Image, 'id' | 'url' | 'altText' | 'width' | 'height' | 'thumbhash'>>;
|
|
5232
5263
|
})>;
|