@doswiftly/storefront-operations 19.1.0 → 20.0.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/AGENTS.md CHANGED
@@ -27,9 +27,9 @@ consumer's `codegen.ts` references this package's `.graphql` files as
27
27
  live in the consumer's repo.
28
28
 
29
29
  <!-- AUTOGEN:STATS:BEGIN — auto-regenerated, do not edit by hand -->
30
- - **Schema version**: 19.1.0
30
+ - **Schema version**: 20.0.0
31
31
  - **Queries**: 52
32
- - **Mutations**: 41
32
+ - **Mutations**: 44
33
33
  - **Fragments**: 104
34
34
  <!-- AUTOGEN:STATS:END -->
35
35
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,124 @@
1
1
  # Changelog
2
2
 
3
+ ## 20.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 486afc8: Cart access is now capability-based: a cart is reached with its id **and** a one-time secret instead of being tied to the signed-in customer. This fixes two long-standing cart bugs — a guest inheriting a previous visitor's cart id getting "cart not found", and a signed-in buyer being told to sign in again on their own cart — and aligns the cart contract with the industry norm.
8
+
9
+ **Why**: the `cart-id` cookie outlives the auth session and is shared across tabs and workers, so gating cart writes on the customer made carts brittle. The cart token is now a bearer capability; the customer link is enrichment only. There are no more "unauthenticated"/"forbidden" cart errors — a missing or expired cart, or a missing or wrong secret, returns a single `CART_NOT_FOUND`, which the SDK already auto-recovers from by creating a fresh cart.
10
+
11
+ **The SDK handles the secret for you.** `useCartManager`, the browser cart cookie store, and the client/server middleware read and persist it automatically. The breaking points below matter only if you read the `cart-id` cookie yourself, run server-side cart reads, or built on the removed session-error path.
12
+
13
+ **Breaking**
14
+ 1. **Composite cart cookie.** The `cart-id` cookie value is now `"<cartId>.<secret>"`. If you read it directly (for example in a Server Component), parse it with the new `parseCartCookieValue()` / `readCartCredentials()` instead of treating the raw value as the cart id. A legacy plain-id cookie still parses (with `cartSecret: null`) and degrades to a fresh cart on the next write.
15
+ 2. **Server-side cart reads must forward the secret.** SSR / edge cart reads now need the secret header or they come back empty. Add `serverCartSecretMiddleware(await readCartCredentials())` to your server client middleware (next to your currency / language middleware).
16
+ 3. **The cart session-error path is removed.** `CART_UNAUTHENTICATED` no longer exists in the contract, and with it the recovery runner's `onSessionExpired` listener, the `CartSessionRequiredError` class, the `isCartSessionError` predicate and the `CART_SESSION_ERROR_CODES` constant. If you handled a "cart needs sign-in" signal, remove it — a stale or unreachable cart now flows through the normal `CART_NOT_FOUND` recovery you already have. (The auth-level session refresh — `sessionRetryMiddleware`, `useSessionExpired`, `createSessionExpiredEmitter` — is unchanged; it handles the customer token, not the cart.)
17
+ 4. **`ensureCart` shape.** A custom `ensureCart` passed to `createCartRecoveryRunner` must now return `{ cart, secret }` (the create outcome) rather than just the cart, so the runner can persist the secret.
18
+
19
+ **New**
20
+ - `CartClient.create()` now returns the one-time `secret` alongside the cart.
21
+ - `CartClient.merge(guestCartId)` — on login, fold the customer's prior cart into the guest cart (quantities sum per variant, guest checkout fields win, the guest cart id and secret are kept so the stored cookie stays valid). Requires an authenticated request.
22
+ - `CartClient.downgradeOnLogout(cartId)` — on logout, clear the customer link, contact details, addresses and saved-payment selection while keeping items, coupons, shipping method, currency, notes and the secret. `useLogout` now calls this automatically before signing out, so customer details do not linger in the cart on a shared device.
23
+ - `CartClient.recoveryRedeem(token)` — redeem a signed cart-recovery link; the cart secret is rotated and the new one is revealed once (persist it into the cookie).
24
+ - `cartSecretMiddleware`, `serverCartSecretMiddleware`, `readCartCredentials`, `parseCartCookieValue`, `formatCartCookieValue` and the `CartCredentials` type for working with the secret directly.
25
+
26
+ ### Patch Changes
27
+
28
+ - 17f4832: Two cart-cookie robustness fixes for capability-based carts:
29
+ - **A malformed `cart-id` cookie no longer breaks the GraphQL client.** A corrupt cookie value (for example a stray `%` left by another tool, or a hand-crafted bad value) made `getCookie` throw while reading the cart secret on every request, taking down the whole request pipeline for that browser until the cookie was cleared. The cookie is now treated as absent when it can't be decoded, so a fresh cart is created as intended.
30
+ - **A server-known cart seed can now carry the cart secret.** When you seed a cart id from the server (via `useCartManager({ initialCartId })`, `<CartManagerProvider initialCartId>`, or `createCartRecoveryRunner({ initialCartId })`), you can pass the composite `"<cartId>.<secret>"` value — the same value stored in the `cart-id` cookie — instead of a bare id. The seed is persisted as a composite cookie so reads and writes send the secret and reach the seeded cart. This unblocks magic-link checkout, embedded-iframe, customer-service "view this cart", and server-side dev-seed flows where the cart id is known up front.
31
+
32
+ Migration: no changes required for existing code. A bare-id seed keeps its previous behaviour (it degrades to a fresh cart on the first write, since the secret is required to reach an existing cart). To make a seeded cart reachable, read the raw `cart-id` cookie server-side and pass it through unchanged, or build the value with `formatCartCookieValue({ cartId, cartSecret })`.
33
+
34
+ - 933492b: Deprecate the `createCartStore` cart-state system in favour of `useCartManager`.
35
+
36
+ `createCartStore` — and its `CartProvider` / `useCartStore` / `useCartStoreApi`
37
+ helpers — predate capability-based carts: the store does not carry the cart secret,
38
+ so mutations against a capability cart fail. These exports are now marked
39
+ `@deprecated` and will be removed in a future major release.
40
+
41
+ Migration: switch to `useCartManager` (React), which manages the cart secret, the
42
+ `cart-id` cookie, and stale-cart recovery automatically and exposes the full cart +
43
+ checkout lifecycle. For a framework-agnostic core, use `createCartRecoveryRunner`.
44
+ No runtime behaviour changes in this release — the deprecation only adds IDE hints.
45
+
46
+ - 486afc8: `Order` now actually returns the `discountAllocations` breakdown it documents. The per-code discount allocations were added to the order contract previously, but the SDK's `Order` selection did not request the field, so it always came back empty. The order confirmation page can now render the same per-code discount rows the buyer saw on the cart.
47
+
48
+ ## 19.2.0
49
+
50
+ ### Minor Changes
51
+
52
+ - b60dcaf: Discount codes now report real per-code applicability and reasons on the cart and order.
53
+
54
+ **Why**: a recognised discount code previously always showed `isApplicable: true` even when it
55
+ reduced nothing — minimum-order not met, code expired, or no cart items within the code's product
56
+ scope — and `discountAllocations` only ever reflected the first code. The contract now tells the
57
+ truth per code, so a storefront can show the buyer exactly which codes apply and why the others do
58
+ not, and the order carries the same breakdown the buyer saw in the cart.
59
+
60
+ **Additive (backward-compatible)**:
61
+ 1. `Cart.discountCodes[].isApplicable` is computed per code — `true` only when the code reduces the
62
+ price or grants free shipping — instead of being hard-coded to `true`.
63
+ 2. `Cart.discountAllocations` contains one entry per applicable code, not just the first.
64
+ 3. `cartDiscountCodesUpdate` returns a `warning` per non-applicable code
65
+ (`CartWarningCode.DISCOUNT_CODE_NOT_APPLICABLE`) whose localized message explains the reason. The
66
+ code stays on the cart, so adding a qualifying product can still activate it.
67
+ 4. New `DiscountErrorCode.NOT_APPLICABLE_TO_CART` — the code is valid but no cart items fall within
68
+ its product/collection/category scope.
69
+ 5. New `Order.discountAllocations` — the per-code breakdown frozen onto the order at checkout
70
+ (parity with `Cart.discountAllocations`), so a confirmation page renders the same rows.
71
+ 6. `cartValidateDiscountCode` (the read-only preview) now mirrors the apply result: a code that is
72
+ recognised but out of scope for the cart returns `isValid: false` with
73
+ `error.code: NOT_APPLICABLE_TO_CART`. Inline "is this code valid?" feedback no longer reports a code
74
+ as valid when applying it would reduce nothing — preview and apply always agree.
75
+
76
+ **Usage example**:
77
+
78
+ ```ts
79
+ const { cart, warnings } = await cartDiscountCodesUpdate({
80
+ id,
81
+ discountCodes: ["SUMMER20"],
82
+ });
83
+
84
+ for (const entry of cart.discountCodes) {
85
+ if (!entry.isApplicable) {
86
+ // The code is recognised but not reducing the price — explain why.
87
+ const reason = warnings.find((w) => w.target === entry.code)?.message;
88
+ showInlineHint(entry.code, reason);
89
+ }
90
+ }
91
+ ```
92
+
93
+ **Migration checklist for existing storefronts**:
94
+ - [ ] If you render a discount chip, branch on `discountCodes[].isApplicable` instead of assuming the code applied.
95
+ - [ ] Surface `cartDiscountCodesUpdate.warnings` (code `DISCOUNT_CODE_NOT_APPLICABLE`) next to the chip to tell the buyer why a code is inactive.
96
+ - [ ] Sum `discountAllocations[].amount` to display a per-code discount breakdown; it equals the cart/order discount total.
97
+ - [ ] Optionally read `Order.discountAllocations` on the confirmation page for the same breakdown after checkout.
98
+ - [ ] 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).
99
+
100
+ - d7b0185: Add `pickupConfig` to `AvailableShippingMethod` — render carrier pickup-point pickers at checkout
101
+
102
+ 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:
103
+ - `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.
104
+ - `selectionMode: SEARCH` — no browser widget; look points up server-side and render your own list. `widgetToken` and `scriptUrl` are `null` for this mode.
105
+
106
+ `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.
107
+
108
+ 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`).
109
+
110
+ - 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.
111
+
112
+ `availableShippingMethods` now returns a `pickupConfig` for methods whose `deliveryType` is `PICKUP_POINT` or `LOCKER`:
113
+ - `provider` — carrier code the config belongs to (e.g. `"inpost"`).
114
+ - `selectionMode` — `WIDGET` (render the carrier map) or `SEARCH` (query points server-side and render your own list).
115
+ - `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.
116
+ - `scriptUrl` — CDN URL of the carrier widget script to load before rendering the map. Null in `SEARCH` mode.
117
+
118
+ `pickupConfig` is `null` for `HOME` delivery methods.
119
+
120
+ **Additive (backward-compatible)** — existing code that does not read `pickupConfig` keeps working unchanged.
121
+
3
122
  ## 19.1.0
4
123
 
5
124
  ### Minor Changes
package/README.md CHANGED
@@ -333,12 +333,15 @@ full executable body of each operation.
333
333
 
334
334
  | Operation | Description |
335
335
  | --- | --- |
336
- | `CartCreate` | Creates a new cart and optionally pre-populates it with line items. Cart ID is a UUID stored by the SDK in the `cart-id` cookie (30-day TTL); the cart itself expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path. |
336
+ | `CartCreate` | Creates a new cart and optionally pre-populates it with line items. Returns a one-time `secret` (the cart access capability) alongside the cart — the SDK persists it in the `cart-id` cookie and sends it as the `x-cart-secret` header on later cart operations; a direct API caller must store it immediately, as it cannot be retrieved again. The cart ID is a UUID; the cart expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path. |
337
337
  | `CartAddLines` | Adds line items to a cart. Each line is `{ merchandiseId, quantity, attributes?, attributeSelections? }`. If the same variant + identical attributes are added twice, quantities merge into one row instead of duplicating. Validates stock (`INSUFFICIENT_STOCK`) and configurator attributes (`ATTRIBUTE_REQUIRED`, `ATTRIBUTE_OPTION_INVALID`). Triggers cart re-pricing including discount recalculation. |
338
338
  | `CartUpdateLines` | Updates quantity and/or attributes of existing cart lines by `id`. Setting `quantity: 0` auto-deletes the line. Passing `attributes: []` clears them; omitting the field preserves existing values. Re-validates stock and re-prices the cart after each update. |
339
339
  | `CartRemoveLines` | Removes specific lines from cart by `lineIds[]`. Internally delegates to `cartUpdateLines` with `quantity: 0` — both endpoints are functionally equivalent; this one exists for API ergonomics when intent is explicit removal. Triggers cart re-pricing. |
340
340
  | `CartDiscountCodesUpdate` | Replaces (NOT appends) the cart's discount codes with the given list. Pass `[]` to clear all codes. Each code is validated against the discounts table (existence, active status); invalid codes appear in `userErrors[]` as `DISCOUNT_CODE_INVALID`. Triggers cart re-pricing — discount allocations are recomputed and stored in `cart.discountAmount`. Single canonical replace-all entry point — prior append/single-remove variants were removed in favor of this explicit caller-controlled list semantics. |
341
- | `CartUpdateBuyerIdentity` | Associates a customer with the cart. Despite the input shape accepting `email`, `phone`, `countryCode`, `languageCode`, only `customerId` is currently persisted other fields are silently ignored. Does NOT trigger tax / shipping recalculation; pure cart-to-customer linking. Use during login or guest-to-account upgrade. |
341
+ | `CartUpdateBuyerIdentity` | Set the buyer's email and phone on the cart (guest checkout contact details). The cart is bound to a customer automatically from the authenticated session — there is no `customerId` input, so a guest cannot claim another shopper's account. Sign in and the cart attaches to that customer (re-binding overwrites, last-write-wins); the `customerId` is then readable on `cart.buyerIdentity`. Use during guest checkout to capture contact info and after login to attach the buyer. Does not trigger tax / shipping recalculation. |
342
+ | `CartMerge` | Merge a guest cart into the signed-in customer's existing cart right after login. Pass the guest cart id; its secret travels in the cart credential header and the customer is taken from the authenticated session (never the client). Line quantities are summed per variant, the buyer's in-session checkout fields win, and the prior customer cart is discarded. The returned cart keeps the SAME id and secret as the guest cart, so the stored cart-id stays valid — no cookie re-issue. Requires authentication (returns `CART_MERGE_REQUIRES_AUTH` otherwise) and refuses carts held in different currencies (`CART_CURRENCY_MISMATCH`). |
343
+ | `CartDowngradeOnLogout` | Downgrade a cart to guest on logout. Pass the cart id (its secret travels in the cart credential header). Clears the customer association, contact details, addresses and payment selection, but keeps line items, discount codes, the selected shipping method, currency and notes. The cart id and secret are unchanged (no rotation), so the stored cart-id stays valid and the buyer keeps their items as a guest. Call from the logout flow before tearing down the auth session so the next person on a shared device sees none of the previous buyer's data. Gated by the cart secret only (no auth) — a missing/wrong secret returns `CART_NOT_FOUND`. |
344
+ | `CartRecoveryRedeem` | Redeem a signed cart recovery link (from an abandoned-cart email). Pass the token taken from the link's query parameter. On success the cart is recovered (made active again) and its access secret is ROTATED — the NEW secret is returned once in `secret` (persist it immediately; the previous secret stops working), and the SDK sets the cart-id cookie to the recovered cart. Buyer self-service: the merchant only sends the link, never takes over the cart. A bad link returns `CART_RECOVERY_LINK_EXPIRED` or `CART_RECOVERY_LINK_INVALID` without exposing any cart content; `CART_NOT_FOUND` if the cart no longer exists. |
342
345
  | `CartUpdateNote` | Sets a free-text note on the cart (gift message, special instructions). Pass empty string to clear. Stored on the `Cart` row, propagated to the `Order` at checkout completion, visible to merchant in admin. |
343
346
 
344
347
  #### Customer Auth Mutations
@@ -380,7 +383,7 @@ full executable body of each operation.
380
383
  | `CartSetShippingAddress` | Phase 3 unify-cart-graphql-surface: wszystkie fulfillment + payment + completion operations teraz na Cart aggregate (zamiast Checkout dual-aggregate). Klient robi typowy checkout flow: cart create/add items → setShipping/Billing/Method → selectPayment → (optional) applyGiftCard → cartComplete → Order created. Sets the shipping address on the cart (full replace, not patch). Triggers cart re-pricing (tax recalculation per address country/region). Address format validated against `CartAddressInput` constraints (firstName/lastName/streetLine1/city/country/postalCode required). Errors: `INVALID_ADDRESS`, `CART_NOT_FOUND`. |
381
384
  | `CartSetBillingAddress` | Sets the billing address on the cart (full replace). Independent of shipping address — pass it explicitly even when "billing same as shipping". Errors: `INVALID_ADDRESS`, `CART_NOT_FOUND`. |
382
385
  | `CartSelectShippingMethod` | Selects a shipping method by `shippingMethodId` (typed `ID!`, a stable shipping-method UUID — NOT a per-request token). The id comes from a list of methods available for the current address + cart subtotal (queryable separately). Errors: `SHIPPING_METHOD_REQUIRED`, `ZIP_CODE_NOT_SUPPORTED`, `CART_NOT_FOUND`. |
383
- | `CartSelectPaymentMethod` | Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`. |
386
+ | `CartSelectPaymentMethod` | Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`. |
384
387
  | `CartApplyGiftCard` | Applies a gift card to the cart, stackable with discount codes. Consumption is FIFO: each card consumes `min(remainingBalance, paymentDue)` against the current cart total in the order they were applied. The gift card balance is NOT debited yet — actual deduction happens atomically at `cartComplete`. Errors: `GIFT_CARD_NOT_FOUND`, `GIFT_CARD_DEPLETED`, `GIFT_CARD_UNUSABLE`. |
385
388
  | `CartRemoveGiftCard` | Removes a gift card from the applied list and recalculates FIFO `appliedAmount` for the remaining cards. Since gift card balances are only debited at `cartComplete`, removing before completion has no effect on the underlying gift card balance. |
386
389
  | `CartUpdateGiftCardRecipient` | Sets per-line-item recipient details (name, email, message) for digital gift card products in the cart (line items where the variant represents a gift-card SKU). Required before `cartComplete` for any line item with a gift-card variant. Recipient details propagated to the resulting order. |
@@ -550,7 +553,7 @@ full executable body of each operation.
550
553
  | `ShippingCarrier` | `ShippingCarrier` | Carrier offering the shipping method — id, display name, logo image (transformable), internal service code. |
551
554
  | `DeliveryEstimate` | `DeliveryEstimate` | Estimated delivery window — `minDays` to `maxDays` plus a human-readable description (e.g. "2-4 business days"). |
552
555
  | `FreeShippingProgress` | `FreeShippingProgress` | Free-shipping progress info — `qualifies` flag, current cart amount, threshold, remaining to qualify, percent progress, and a localized message ("Add 20 PLN more for free shipping"). Use to render a free-shipping progress bar in the cart drawer. |
553
- | `AvailableShippingMethod` | `AvailableShippingMethod` | One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. Spread on the checkout shipping step. |
556
+ | `AvailableShippingMethod` | `AvailableShippingMethod` | One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. For pickup methods, `pickupConfig` carries the public data needed to render the carrier point picker — `selectionMode = WIDGET` gives a `widgetToken` (e.g. the InPost Geowidget public token, never a carrier API secret) + `scriptUrl` to load the map; `selectionMode = SEARCH` means query points server-side. `pickupConfig` is null for HOME methods and when the merchant has not configured the carrier's public widget token (do not render a map then). Spread on the checkout shipping step. |
554
557
 
555
558
  #### Attribute Filters
556
559
 
package/fragments.graphql CHANGED
@@ -339,6 +339,12 @@ fragment Order on Order {
339
339
  ...MailingAddress
340
340
  }
341
341
  itemCount
342
+ discountAllocations {
343
+ discountCode
344
+ amount {
345
+ ...Money
346
+ }
347
+ }
342
348
  canCreatePayment
343
349
  paymentMethodType
344
350
  }
@@ -1016,12 +1022,18 @@ fragment FreeShippingProgress on FreeShippingProgress {
1016
1022
  message
1017
1023
  }
1018
1024
 
1019
- # One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. Spread on the checkout shipping step.
1025
+ # One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. For pickup methods, `pickupConfig` carries the public data needed to render the carrier point picker — `selectionMode = WIDGET` gives a `widgetToken` (e.g. the InPost Geowidget public token, never a carrier API secret) + `scriptUrl` to load the map; `selectionMode = SEARCH` means query points server-side. `pickupConfig` is null for HOME methods and when the merchant has not configured the carrier's public widget token (do not render a map then). Spread on the checkout shipping step.
1020
1026
  fragment AvailableShippingMethod on AvailableShippingMethod {
1021
1027
  id
1022
1028
  name
1023
1029
  description
1024
1030
  deliveryType
1031
+ pickupConfig {
1032
+ provider
1033
+ selectionMode
1034
+ widgetToken
1035
+ scriptUrl
1036
+ }
1025
1037
  carrier {
1026
1038
  ...ShippingCarrier
1027
1039
  }
package/llms-full.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **19.1.0**
4
- > 52 queries · 41 mutations · 104 fragments
3
+ > Schema version: **20.0.0**
4
+ > 52 queries · 44 mutations · 104 fragments
5
5
 
6
6
  Auto-generated from `.graphql` source files. Do not edit by hand — this file is
7
7
  regenerated on every release to match the published schema.
@@ -1385,7 +1385,7 @@ query Location($id: ID!) {
1385
1385
 
1386
1386
  **Section**: Cart Mutations
1387
1387
 
1388
- **Description**: Creates a new cart and optionally pre-populates it with line items. Cart ID is a UUID stored by the SDK in the `cart-id` cookie (30-day TTL); the cart itself expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.
1388
+ **Description**: Creates a new cart and optionally pre-populates it with line items. Returns a one-time `secret` (the cart access capability) alongside the cart — the SDK persists it in the `cart-id` cookie and sends it as the `x-cart-secret` header on later cart operations; a direct API caller must store it immediately, as it cannot be retrieved again. The cart ID is a UUID; the cart expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.
1389
1389
 
1390
1390
  **Variables**:
1391
1391
  - `$input`: `CartCreateInput`
@@ -1399,6 +1399,7 @@ mutation CartCreate($input: CartCreateInput) {
1399
1399
  cart {
1400
1400
  ...Cart
1401
1401
  }
1402
+ secret
1402
1403
  userErrors {
1403
1404
  ...UserError
1404
1405
  }
@@ -1529,7 +1530,7 @@ mutation CartDiscountCodesUpdate($id: ID!, $discountCodes: [String!]!) {
1529
1530
 
1530
1531
  **Section**: Cart Mutations
1531
1532
 
1532
- **Description**: Associates a customer with the cart. Despite the input shape accepting `email`, `phone`, `countryCode`, `languageCode`, only `customerId` is currently persisted other fields are silently ignored. Does NOT trigger tax / shipping recalculation; pure cart-to-customer linking. Use during login or guest-to-account upgrade.
1533
+ **Description**: Set the buyer's email and phone on the cart (guest checkout contact details). The cart is bound to a customer automatically from the authenticated session — there is no `customerId` input, so a guest cannot claim another shopper's account. Sign in and the cart attaches to that customer (re-binding overwrites, last-write-wins); the `customerId` is then readable on `cart.buyerIdentity`. Use during guest checkout to capture contact info and after login to attach the buyer. Does not trigger tax / shipping recalculation.
1533
1534
 
1534
1535
  **Variables**:
1535
1536
  - `$id`: `ID!`
@@ -1554,6 +1555,91 @@ mutation CartUpdateBuyerIdentity($id: ID!, $buyerIdentity: CartBuyerIdentityInpu
1554
1555
  }
1555
1556
  ```
1556
1557
 
1558
+ ### Mutation: `CartMerge`
1559
+
1560
+ **Section**: Cart Mutations
1561
+
1562
+ **Description**: Merge a guest cart into the signed-in customer's existing cart right after login. Pass the guest cart id; its secret travels in the cart credential header and the customer is taken from the authenticated session (never the client). Line quantities are summed per variant, the buyer's in-session checkout fields win, and the prior customer cart is discarded. The returned cart keeps the SAME id and secret as the guest cart, so the stored cart-id stays valid — no cookie re-issue. Requires authentication (returns `CART_MERGE_REQUIRES_AUTH` otherwise) and refuses carts held in different currencies (`CART_CURRENCY_MISMATCH`).
1563
+
1564
+ **Variables**:
1565
+ - `$guestCartId`: `ID!`
1566
+
1567
+ **Fragments used**: `Cart`, `CartWarning`, `UserError`
1568
+
1569
+ **GraphQL**:
1570
+ ```graphql
1571
+ mutation CartMerge($guestCartId: ID!) {
1572
+ cartMerge(guestCartId: $guestCartId) {
1573
+ cart {
1574
+ ...Cart
1575
+ }
1576
+ userErrors {
1577
+ ...UserError
1578
+ }
1579
+ warnings {
1580
+ ...CartWarning
1581
+ }
1582
+ }
1583
+ }
1584
+ ```
1585
+
1586
+ ### Mutation: `CartDowngradeOnLogout`
1587
+
1588
+ **Section**: Cart Mutations
1589
+
1590
+ **Description**: Downgrade a cart to guest on logout. Pass the cart id (its secret travels in the cart credential header). Clears the customer association, contact details, addresses and payment selection, but keeps line items, discount codes, the selected shipping method, currency and notes. The cart id and secret are unchanged (no rotation), so the stored cart-id stays valid and the buyer keeps their items as a guest. Call from the logout flow before tearing down the auth session so the next person on a shared device sees none of the previous buyer's data. Gated by the cart secret only (no auth) — a missing/wrong secret returns `CART_NOT_FOUND`.
1591
+
1592
+ **Variables**:
1593
+ - `$cartId`: `ID!`
1594
+
1595
+ **Fragments used**: `Cart`, `CartWarning`, `UserError`
1596
+
1597
+ **GraphQL**:
1598
+ ```graphql
1599
+ mutation CartDowngradeOnLogout($cartId: ID!) {
1600
+ cartDowngradeOnLogout(cartId: $cartId) {
1601
+ cart {
1602
+ ...Cart
1603
+ }
1604
+ userErrors {
1605
+ ...UserError
1606
+ }
1607
+ warnings {
1608
+ ...CartWarning
1609
+ }
1610
+ }
1611
+ }
1612
+ ```
1613
+
1614
+ ### Mutation: `CartRecoveryRedeem`
1615
+
1616
+ **Section**: Cart Mutations
1617
+
1618
+ **Description**: Redeem a signed cart recovery link (from an abandoned-cart email). Pass the token taken from the link's query parameter. On success the cart is recovered (made active again) and its access secret is ROTATED — the NEW secret is returned once in `secret` (persist it immediately; the previous secret stops working), and the SDK sets the cart-id cookie to the recovered cart. Buyer self-service: the merchant only sends the link, never takes over the cart. A bad link returns `CART_RECOVERY_LINK_EXPIRED` or `CART_RECOVERY_LINK_INVALID` without exposing any cart content; `CART_NOT_FOUND` if the cart no longer exists.
1619
+
1620
+ **Variables**:
1621
+ - `$token`: `String!`
1622
+
1623
+ **Fragments used**: `Cart`, `CartWarning`, `UserError`
1624
+
1625
+ **GraphQL**:
1626
+ ```graphql
1627
+ mutation CartRecoveryRedeem($token: String!) {
1628
+ cartRecoveryRedeem(token: $token) {
1629
+ cart {
1630
+ ...Cart
1631
+ }
1632
+ secret
1633
+ userErrors {
1634
+ ...UserError
1635
+ }
1636
+ warnings {
1637
+ ...CartWarning
1638
+ }
1639
+ }
1640
+ }
1641
+ ```
1642
+
1557
1643
  ### Mutation: `CartUpdateNote`
1558
1644
 
1559
1645
  **Section**: Cart Mutations
@@ -1975,7 +2061,7 @@ mutation CartSelectShippingMethod($input: CartSelectShippingMethodInput!) {
1975
2061
 
1976
2062
  **Section**: Cart Completion Mutations
1977
2063
 
1978
- **Description**: Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.
2064
+ **Description**: Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`.
1979
2065
 
1980
2066
  **Variables**:
1981
2067
  - `$input`: `CartSelectPaymentMethodInput!`
@@ -2961,6 +3047,12 @@ fragment Order on Order {
2961
3047
  ...MailingAddress
2962
3048
  }
2963
3049
  itemCount
3050
+ discountAllocations {
3051
+ discountCode
3052
+ amount {
3053
+ ...Money
3054
+ }
3055
+ }
2964
3056
  canCreatePayment
2965
3057
  paymentMethodType
2966
3058
  }
@@ -3982,7 +4074,7 @@ fragment FreeShippingProgress on FreeShippingProgress {
3982
4074
 
3983
4075
  **Section**: Shipping Methods
3984
4076
 
3985
- **Description**: One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. Spread on the checkout shipping step.
4077
+ **Description**: One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. For pickup methods, `pickupConfig` carries the public data needed to render the carrier point picker — `selectionMode = WIDGET` gives a `widgetToken` (e.g. the InPost Geowidget public token, never a carrier API secret) + `scriptUrl` to load the map; `selectionMode = SEARCH` means query points server-side. `pickupConfig` is null for HOME methods and when the merchant has not configured the carrier's public widget token (do not render a map then). Spread on the checkout shipping step.
3986
4078
 
3987
4079
  **Uses fragments**: `DeliveryEstimate`, `FreeShippingProgress`, `Money`, `ShippingCarrier`
3988
4080
 
@@ -3993,6 +4085,12 @@ fragment AvailableShippingMethod on AvailableShippingMethod {
3993
4085
  name
3994
4086
  description
3995
4087
  deliveryType
4088
+ pickupConfig {
4089
+ provider
4090
+ selectionMode
4091
+ widgetToken
4092
+ scriptUrl
4093
+ }
3996
4094
  carrier {
3997
4095
  ...ShippingCarrier
3998
4096
  }
package/mutations.graphql CHANGED
@@ -7,12 +7,13 @@
7
7
  # Cart Mutations
8
8
  # ============================================
9
9
 
10
- # Creates a new cart and optionally pre-populates it with line items. Cart ID is a UUID stored by the SDK in the `cart-id` cookie (30-day TTL); the cart itself expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.
10
+ # Creates a new cart and optionally pre-populates it with line items. Returns a one-time `secret` (the cart access capability) alongside the cart — the SDK persists it in the `cart-id` cookie and sends it as the `x-cart-secret` header on later cart operations; a direct API caller must store it immediately, as it cannot be retrieved again. The cart ID is a UUID; the cart expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.
11
11
  mutation CartCreate($input: CartCreateInput) {
12
12
  cartCreate(input: $input) {
13
13
  cart {
14
14
  ...Cart
15
15
  }
16
+ secret
16
17
  userErrors {
17
18
  ...UserError
18
19
  }
@@ -82,7 +83,7 @@ mutation CartDiscountCodesUpdate($id: ID!, $discountCodes: [String!]!) {
82
83
  }
83
84
  }
84
85
 
85
- # Associates a customer with the cart. Despite the input shape accepting `email`, `phone`, `countryCode`, `languageCode`, only `customerId` is currently persisted other fields are silently ignored. Does NOT trigger tax / shipping recalculation; pure cart-to-customer linking. Use during login or guest-to-account upgrade.
86
+ # Set the buyer's email and phone on the cart (guest checkout contact details). The cart is bound to a customer automatically from the authenticated session — there is no `customerId` input, so a guest cannot claim another shopper's account. Sign in and the cart attaches to that customer (re-binding overwrites, last-write-wins); the `customerId` is then readable on `cart.buyerIdentity`. Use during guest checkout to capture contact info and after login to attach the buyer. Does not trigger tax / shipping recalculation.
86
87
  mutation CartUpdateBuyerIdentity($id: ID!, $buyerIdentity: CartBuyerIdentityInput!) {
87
88
  cartUpdateBuyerIdentity(id: $id, buyerIdentity: $buyerIdentity) {
88
89
  cart {
@@ -97,6 +98,52 @@ mutation CartUpdateBuyerIdentity($id: ID!, $buyerIdentity: CartBuyerIdentityInpu
97
98
  }
98
99
  }
99
100
 
101
+ # Merge a guest cart into the signed-in customer's existing cart right after login. Pass the guest cart id; its secret travels in the cart credential header and the customer is taken from the authenticated session (never the client). Line quantities are summed per variant, the buyer's in-session checkout fields win, and the prior customer cart is discarded. The returned cart keeps the SAME id and secret as the guest cart, so the stored cart-id stays valid — no cookie re-issue. Requires authentication (returns `CART_MERGE_REQUIRES_AUTH` otherwise) and refuses carts held in different currencies (`CART_CURRENCY_MISMATCH`).
102
+ mutation CartMerge($guestCartId: ID!) {
103
+ cartMerge(guestCartId: $guestCartId) {
104
+ cart {
105
+ ...Cart
106
+ }
107
+ userErrors {
108
+ ...UserError
109
+ }
110
+ warnings {
111
+ ...CartWarning
112
+ }
113
+ }
114
+ }
115
+
116
+ # Downgrade a cart to guest on logout. Pass the cart id (its secret travels in the cart credential header). Clears the customer association, contact details, addresses and payment selection, but keeps line items, discount codes, the selected shipping method, currency and notes. The cart id and secret are unchanged (no rotation), so the stored cart-id stays valid and the buyer keeps their items as a guest. Call from the logout flow before tearing down the auth session so the next person on a shared device sees none of the previous buyer's data. Gated by the cart secret only (no auth) — a missing/wrong secret returns `CART_NOT_FOUND`.
117
+ mutation CartDowngradeOnLogout($cartId: ID!) {
118
+ cartDowngradeOnLogout(cartId: $cartId) {
119
+ cart {
120
+ ...Cart
121
+ }
122
+ userErrors {
123
+ ...UserError
124
+ }
125
+ warnings {
126
+ ...CartWarning
127
+ }
128
+ }
129
+ }
130
+
131
+ # Redeem a signed cart recovery link (from an abandoned-cart email). Pass the token taken from the link's query parameter. On success the cart is recovered (made active again) and its access secret is ROTATED — the NEW secret is returned once in `secret` (persist it immediately; the previous secret stops working), and the SDK sets the cart-id cookie to the recovered cart. Buyer self-service: the merchant only sends the link, never takes over the cart. A bad link returns `CART_RECOVERY_LINK_EXPIRED` or `CART_RECOVERY_LINK_INVALID` without exposing any cart content; `CART_NOT_FOUND` if the cart no longer exists.
132
+ mutation CartRecoveryRedeem($token: String!) {
133
+ cartRecoveryRedeem(token: $token) {
134
+ cart {
135
+ ...Cart
136
+ }
137
+ secret
138
+ userErrors {
139
+ ...UserError
140
+ }
141
+ warnings {
142
+ ...CartWarning
143
+ }
144
+ }
145
+ }
146
+
100
147
  # Sets a free-text note on the cart (gift message, special instructions). Pass empty string to clear. Stored on the `Cart` row, propagated to the `Order` at checkout completion, visible to merchant in admin.
101
148
  mutation CartUpdateNote($id: ID!, $note: String!) {
102
149
  cartUpdateNote(id: $id, note: $note) {
@@ -333,7 +380,7 @@ mutation CartSelectShippingMethod($input: CartSelectShippingMethodInput!) {
333
380
  # Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks
334
381
  # a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing
335
382
  # from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category.
336
- # Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.
383
+ # Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`.
337
384
  mutation CartSelectPaymentMethod($input: CartSelectPaymentMethodInput!) {
338
385
  cartSelectPaymentMethod(input: $input) {
339
386
  cart {
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "19.1.0",
2
+ "schemaVersion": "20.0.0",
3
3
  "queries": [
4
4
  {
5
5
  "name": "Shop",
@@ -1079,7 +1079,7 @@
1079
1079
  "name": "CartCreate",
1080
1080
  "kind": "mutation",
1081
1081
  "section": "Cart Mutations",
1082
- "description": "Creates a new cart and optionally pre-populates it with line items. Cart ID is a UUID stored by the SDK in the `cart-id` cookie (30-day TTL); the cart itself expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.",
1082
+ "description": "Creates a new cart and optionally pre-populates it with line items. Returns a one-time `secret` (the cart access capability) alongside the cart — the SDK persists it in the `cart-id` cookie and sends it as the `x-cart-secret` header on later cart operations; a direct API caller must store it immediately, as it cannot be retrieved again. The cart ID is a UUID; the cart expires server-side after 72 hours of inactivity. The `warnings` field is reserved for non-blocking issues — current implementation returns it empty in this path.",
1083
1083
  "variables": [
1084
1084
  {
1085
1085
  "name": "input",
@@ -1092,7 +1092,7 @@
1092
1092
  "CartWarning",
1093
1093
  "UserError"
1094
1094
  ],
1095
- "body": "mutation CartCreate($input: CartCreateInput) {\n cartCreate(input: $input) {\n cart {\n ...Cart\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1095
+ "body": "mutation CartCreate($input: CartCreateInput) {\n cartCreate(input: $input) {\n cart {\n ...Cart\n }\n secret\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1096
1096
  },
1097
1097
  {
1098
1098
  "name": "CartAddLines",
@@ -1194,7 +1194,7 @@
1194
1194
  "name": "CartUpdateBuyerIdentity",
1195
1195
  "kind": "mutation",
1196
1196
  "section": "Cart Mutations",
1197
- "description": "Associates a customer with the cart. Despite the input shape accepting `email`, `phone`, `countryCode`, `languageCode`, only `customerId` is currently persisted other fields are silently ignored. Does NOT trigger tax / shipping recalculation; pure cart-to-customer linking. Use during login or guest-to-account upgrade.",
1197
+ "description": "Set the buyer's email and phone on the cart (guest checkout contact details). The cart is bound to a customer automatically from the authenticated session — there is no `customerId` input, so a guest cannot claim another shopper's account. Sign in and the cart attaches to that customer (re-binding overwrites, last-write-wins); the `customerId` is then readable on `cart.buyerIdentity`. Use during guest checkout to capture contact info and after login to attach the buyer. Does not trigger tax / shipping recalculation.",
1198
1198
  "variables": [
1199
1199
  {
1200
1200
  "name": "id",
@@ -1214,6 +1214,63 @@
1214
1214
  ],
1215
1215
  "body": "mutation CartUpdateBuyerIdentity($id: ID!, $buyerIdentity: CartBuyerIdentityInput!) {\n cartUpdateBuyerIdentity(id: $id, buyerIdentity: $buyerIdentity) {\n cart {\n ...Cart\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1216
1216
  },
1217
+ {
1218
+ "name": "CartMerge",
1219
+ "kind": "mutation",
1220
+ "section": "Cart Mutations",
1221
+ "description": "Merge a guest cart into the signed-in customer's existing cart right after login. Pass the guest cart id; its secret travels in the cart credential header and the customer is taken from the authenticated session (never the client). Line quantities are summed per variant, the buyer's in-session checkout fields win, and the prior customer cart is discarded. The returned cart keeps the SAME id and secret as the guest cart, so the stored cart-id stays valid — no cookie re-issue. Requires authentication (returns `CART_MERGE_REQUIRES_AUTH` otherwise) and refuses carts held in different currencies (`CART_CURRENCY_MISMATCH`).",
1222
+ "variables": [
1223
+ {
1224
+ "name": "guestCartId",
1225
+ "type": "ID!",
1226
+ "defaultValue": null
1227
+ }
1228
+ ],
1229
+ "fragmentRefs": [
1230
+ "Cart",
1231
+ "CartWarning",
1232
+ "UserError"
1233
+ ],
1234
+ "body": "mutation CartMerge($guestCartId: ID!) {\n cartMerge(guestCartId: $guestCartId) {\n cart {\n ...Cart\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1235
+ },
1236
+ {
1237
+ "name": "CartDowngradeOnLogout",
1238
+ "kind": "mutation",
1239
+ "section": "Cart Mutations",
1240
+ "description": "Downgrade a cart to guest on logout. Pass the cart id (its secret travels in the cart credential header). Clears the customer association, contact details, addresses and payment selection, but keeps line items, discount codes, the selected shipping method, currency and notes. The cart id and secret are unchanged (no rotation), so the stored cart-id stays valid and the buyer keeps their items as a guest. Call from the logout flow before tearing down the auth session so the next person on a shared device sees none of the previous buyer's data. Gated by the cart secret only (no auth) — a missing/wrong secret returns `CART_NOT_FOUND`.",
1241
+ "variables": [
1242
+ {
1243
+ "name": "cartId",
1244
+ "type": "ID!",
1245
+ "defaultValue": null
1246
+ }
1247
+ ],
1248
+ "fragmentRefs": [
1249
+ "Cart",
1250
+ "CartWarning",
1251
+ "UserError"
1252
+ ],
1253
+ "body": "mutation CartDowngradeOnLogout($cartId: ID!) {\n cartDowngradeOnLogout(cartId: $cartId) {\n cart {\n ...Cart\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1254
+ },
1255
+ {
1256
+ "name": "CartRecoveryRedeem",
1257
+ "kind": "mutation",
1258
+ "section": "Cart Mutations",
1259
+ "description": "Redeem a signed cart recovery link (from an abandoned-cart email). Pass the token taken from the link's query parameter. On success the cart is recovered (made active again) and its access secret is ROTATED — the NEW secret is returned once in `secret` (persist it immediately; the previous secret stops working), and the SDK sets the cart-id cookie to the recovered cart. Buyer self-service: the merchant only sends the link, never takes over the cart. A bad link returns `CART_RECOVERY_LINK_EXPIRED` or `CART_RECOVERY_LINK_INVALID` without exposing any cart content; `CART_NOT_FOUND` if the cart no longer exists.",
1260
+ "variables": [
1261
+ {
1262
+ "name": "token",
1263
+ "type": "String!",
1264
+ "defaultValue": null
1265
+ }
1266
+ ],
1267
+ "fragmentRefs": [
1268
+ "Cart",
1269
+ "CartWarning",
1270
+ "UserError"
1271
+ ],
1272
+ "body": "mutation CartRecoveryRedeem($token: String!) {\n cartRecoveryRedeem(token: $token) {\n cart {\n ...Cart\n }\n secret\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1273
+ },
1217
1274
  {
1218
1275
  "name": "CartUpdateNote",
1219
1276
  "kind": "mutation",
@@ -1518,7 +1575,7 @@
1518
1575
  "name": "CartSelectPaymentMethod",
1519
1576
  "kind": "mutation",
1520
1577
  "section": "Cart Completion Mutations",
1521
- "description": "Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.",
1578
+ "description": "Selects a payment method on the cart by category (`methodType` — BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProvider` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`.",
1522
1579
  "variables": [
1523
1580
  {
1524
1581
  "name": "input",
@@ -2096,7 +2153,7 @@
2096
2153
  "MailingAddress",
2097
2154
  "Money"
2098
2155
  ],
2099
- "body": "fragment Order on Order {\n id\n orderNumber\n accessToken\n totals {\n total {\n ...Money\n }\n subtotal {\n ...Money\n }\n totalTax {\n ...Money\n }\n totalShipping {\n ...Money\n }\n }\n status\n paymentStatus\n fulfillmentStatus\n processedAt\n confirmedAt\n cancelledAt\n expiredAt\n shippingAddress {\n ...MailingAddress\n }\n itemCount\n canCreatePayment\n paymentMethodType\n}",
2156
+ "body": "fragment Order on Order {\n id\n orderNumber\n accessToken\n totals {\n total {\n ...Money\n }\n subtotal {\n ...Money\n }\n totalTax {\n ...Money\n }\n totalShipping {\n ...Money\n }\n }\n status\n paymentStatus\n fulfillmentStatus\n processedAt\n confirmedAt\n cancelledAt\n expiredAt\n shippingAddress {\n ...MailingAddress\n }\n itemCount\n discountAllocations {\n discountCode\n amount {\n ...Money\n }\n }\n canCreatePayment\n paymentMethodType\n}",
2100
2157
  "onType": "Order"
2101
2158
  },
2102
2159
  {
@@ -2597,7 +2654,7 @@
2597
2654
  "name": "AvailableShippingMethod",
2598
2655
  "kind": "fragment",
2599
2656
  "section": "Shipping Methods",
2600
- "description": "One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. Spread on the checkout shipping step.",
2657
+ "description": "One shipping method offered for the destination + cart — id, name, carrier, price, free-shipping progress, estimated delivery, sort order. `deliveryType` signals whether to render a pickup-point / locker picker (`HOME` vs `PICKUP_POINT` / `LOCKER`) so the storefront does not have to infer it from the method name. For pickup methods, `pickupConfig` carries the public data needed to render the carrier point picker — `selectionMode = WIDGET` gives a `widgetToken` (e.g. the InPost Geowidget public token, never a carrier API secret) + `scriptUrl` to load the map; `selectionMode = SEARCH` means query points server-side. `pickupConfig` is null for HOME methods and when the merchant has not configured the carrier's public widget token (do not render a map then). Spread on the checkout shipping step.",
2601
2658
  "variables": [],
2602
2659
  "fragmentRefs": [
2603
2660
  "DeliveryEstimate",
@@ -2605,7 +2662,7 @@
2605
2662
  "Money",
2606
2663
  "ShippingCarrier"
2607
2664
  ],
2608
- "body": "fragment AvailableShippingMethod on AvailableShippingMethod {\n id\n name\n description\n deliveryType\n carrier {\n ...ShippingCarrier\n }\n price {\n ...Money\n }\n isFree\n estimatedDelivery {\n ...DeliveryEstimate\n }\n freeShippingProgress {\n ...FreeShippingProgress\n }\n sortOrder\n}",
2665
+ "body": "fragment AvailableShippingMethod on AvailableShippingMethod {\n id\n name\n description\n deliveryType\n pickupConfig {\n provider\n selectionMode\n widgetToken\n scriptUrl\n }\n carrier {\n ...ShippingCarrier\n }\n price {\n ...Money\n }\n isFree\n estimatedDelivery {\n ...DeliveryEstimate\n }\n freeShippingProgress {\n ...FreeShippingProgress\n }\n sortOrder\n}",
2609
2666
  "onType": "AvailableShippingMethod"
2610
2667
  },
2611
2668
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "19.1.0",
3
+ "version": "20.0.0",
4
4
  "description": "GraphQL operations for DoSwiftly Storefront - SSOT from backend",
5
5
  "homepage": "https://doswiftly.pl",
6
6
  "publishConfig": {
package/schema.graphql CHANGED
@@ -453,6 +453,11 @@ type AvailableShippingMethod {
453
453
  """Display name of the method (e.g. "DPD Standard")."""
454
454
  name: String!
455
455
 
456
+ """
457
+ 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.
458
+ """
459
+ pickupConfig: ShippingPickupConfig
460
+
456
461
  """
457
462
  Cost of the method for the current cart (in the buyer preferred currency).
458
463
  """
@@ -1196,11 +1201,6 @@ input CartBuyerIdentityInput {
1196
1201
  """
1197
1202
  countryCode: CountryCode
1198
1203
 
1199
- """
1200
- ID of a signed-in customer that owns this cart. Pass to bind a guest cart to a customer record.
1201
- """
1202
- customerId: ID
1203
-
1204
1204
  """Buyer email address. Persisted to the order at checkout completion."""
1205
1205
  email: String
1206
1206
 
@@ -1360,6 +1360,11 @@ type CartCreatePayload {
1360
1360
  """The created cart on success; null when `userErrors` is non-empty."""
1361
1361
  cart: Cart
1362
1362
 
1363
+ """
1364
+ One-time cart access secret, returned only here on creation. The SDK persists it for you; if you call the API directly, store it immediately — it cannot be retrieved again.
1365
+ """
1366
+ secret: String
1367
+
1363
1368
  """
1364
1369
  Business / validation errors. Each entry carries a stable `code` (e.g. `CART_NOT_FOUND`, `NOT_ENOUGH_IN_STOCK`, `INVALID_MERCHANDISE_LINE`) — branch on `code`, never on `message`.
1365
1370
  """
@@ -1407,6 +1412,22 @@ type CartDiscountCodesUpdatePayload {
1407
1412
  warnings: [CartWarning!]!
1408
1413
  }
1409
1414
 
1415
+ """Result of `cartDowngradeOnLogout`."""
1416
+ type CartDowngradeOnLogoutPayload {
1417
+ """
1418
+ The downgraded cart on success — same id and secret (no rotation), with the customer association, contact details, addresses and payment selection cleared but line items kept. Null when `userErrors` is non-empty.
1419
+ """
1420
+ cart: Cart
1421
+
1422
+ """
1423
+ Business / validation errors carrying a stable `CartErrorCode` in `code` (e.g. `CART_NOT_FOUND`).
1424
+ """
1425
+ userErrors: [UserError!]!
1426
+
1427
+ """Non-fatal advisories — the mutation itself succeeded."""
1428
+ warnings: [CartWarning!]!
1429
+ }
1430
+
1410
1431
  """
1411
1432
  A single line item in the cart — one variant, a quantity and the data attached to it (cost, attributes, configurator selections).
1412
1433
  """
@@ -1569,6 +1590,22 @@ input CartLineUpdateInput {
1569
1590
  quantity: Int!
1570
1591
  }
1571
1592
 
1593
+ """Result of `cartMerge`."""
1594
+ type CartMergePayload {
1595
+ """
1596
+ The merged cart on success — it keeps the same id and secret as the guest cart passed in, so the stored cart-id cookie stays valid. Null when `userErrors` is non-empty.
1597
+ """
1598
+ cart: Cart
1599
+
1600
+ """
1601
+ Business / validation errors carrying a stable `CartErrorCode` in `code` — e.g. `CART_NOT_FOUND`, `CART_CURRENCY_MISMATCH`, `CART_MERGE_REQUIRES_AUTH`.
1602
+ """
1603
+ userErrors: [UserError!]!
1604
+
1605
+ """Non-fatal advisories — the mutation itself succeeded."""
1606
+ warnings: [CartWarning!]!
1607
+ }
1608
+
1572
1609
  """Recommendations based on cart contents"""
1573
1610
  type CartRecommendations {
1574
1611
  """Products frequently bought together with cart items"""
@@ -1578,6 +1615,27 @@ type CartRecommendations {
1578
1615
  youMayAlsoLike: [ProductRecommendation!]!
1579
1616
  }
1580
1617
 
1618
+ """Result of `cartRecoveryRedeem`."""
1619
+ type CartRecoveryRedeemPayload {
1620
+ """
1621
+ The recovered cart on success. Null when `userErrors` is non-empty (invalid / expired link).
1622
+ """
1623
+ cart: Cart
1624
+
1625
+ """
1626
+ The NEW cart access secret, returned only here on a successful redemption (the link rotates the secret). Persist it immediately — it cannot be retrieved again; the previous secret no longer works.
1627
+ """
1628
+ secret: String
1629
+
1630
+ """
1631
+ Business / validation errors carrying a stable `CartErrorCode` in `code` — `CART_RECOVERY_LINK_EXPIRED` / `CART_RECOVERY_LINK_INVALID` (bad or expired link), `CART_NOT_FOUND` (the cart is gone or already completed — create a fresh one) or `CART_RECOVERY_REDEEM_FAILED` (unexpected failure). No cart content is exposed on failure.
1632
+ """
1633
+ userErrors: [UserError!]!
1634
+
1635
+ """Non-fatal advisories — the mutation itself succeeded."""
1636
+ warnings: [CartWarning!]!
1637
+ }
1638
+
1581
1639
  """Input for `cartRemoveGiftCard`."""
1582
1640
  input CartRemoveGiftCardInput {
1583
1641
  """ID of the cart to update."""
@@ -1882,6 +1940,7 @@ type CartWarning {
1882
1940
  Stable, machine-readable codes for non-fatal cart warnings. The mutation succeeded; the storefront can surface a toast or stay silent based on the code.
1883
1941
  """
1884
1942
  enum CartWarningCode {
1943
+ DISCOUNT_CODE_NOT_APPLICABLE
1885
1944
  MERCHANDISE_NOT_AVAILABLE
1886
1945
  MERCHANDISE_NOT_ENOUGH_STOCK
1887
1946
  PAYMENTS_AMOUNT_REGION_MISMATCH
@@ -2893,6 +2952,7 @@ enum DiscountErrorCode {
2893
2952
  INACTIVE
2894
2953
  MINIMUM_ORDER_NOT_MET
2895
2954
  MINIMUM_QUANTITY_NOT_MET
2955
+ NOT_APPLICABLE_TO_CART
2896
2956
  NOT_FOUND
2897
2957
  NOT_STARTED
2898
2958
  SHOP_NOT_FOUND
@@ -4060,6 +4120,30 @@ type Mutation {
4060
4120
  id: ID!
4061
4121
  ): CartDiscountCodesUpdatePayload!
4062
4122
 
4123
+ """
4124
+ Downgrade a cart to guest on logout — clears the customer association, contact details, addresses and payment selection, but keeps the line items, discount codes, selected shipping method, currency and notes. The cart id and secret are unchanged (no rotation), so the stored cart-id stays valid and the buyer keeps their items as a guest. Call from the logout flow (with the cart secret) before tearing down the auth session; the next person on a shared device then sees none of the previous buyer's data.
4125
+ """
4126
+ cartDowngradeOnLogout(
4127
+ """UUID of the cart to downgrade to guest."""
4128
+ cartId: ID!
4129
+ ): CartDowngradeOnLogoutPayload!
4130
+
4131
+ """
4132
+ Merge a guest cart into the signed-in customer's existing cart on login. Call right after authenticating, passing the guest cart id (its secret travels in the cart credential header). Line quantities are summed per variant and the buyer's in-session checkout fields win. The returned cart keeps the SAME id and secret as the guest cart, so the stored cart-id stays valid. Requires authentication (`CART_MERGE_REQUIRES_AUTH` otherwise); refuses carts in different currencies (`CART_CURRENCY_MISMATCH`).
4133
+ """
4134
+ cartMerge(
4135
+ """UUID of the guest cart to merge from and keep (the survivor)."""
4136
+ guestCartId: ID!
4137
+ ): CartMergePayload!
4138
+
4139
+ """
4140
+ Redeem a signed cart recovery link (from an abandoned-cart email). Pass the token from the link; on success the cart is recovered and its access secret is ROTATED — the NEW secret is returned once in `secret` (persist it; the previous one no longer works), and the SDK sets the cart-id cookie to the recovered cart. Buyer self-service: the merchant sends the link, never takes over the cart. Returns `CART_RECOVERY_LINK_EXPIRED` / `CART_RECOVERY_LINK_INVALID` for a bad link (no cart content is exposed) and `CART_NOT_FOUND` when the cart no longer exists.
4141
+ """
4142
+ cartRecoveryRedeem(
4143
+ """The signed recovery token taken from the recovery link."""
4144
+ token: String!
4145
+ ): CartRecoveryRedeemPayload!
4146
+
4063
4147
  """
4064
4148
  Remove a previously applied gift card from the cart by its `CartAppliedGiftCard.id`. The storefront never has to handle the full gift card code on the client.
4065
4149
  """
@@ -4430,6 +4514,11 @@ type Order implements Node {
4430
4514
  """
4431
4515
  confirmedAt: DateTime
4432
4516
 
4517
+ """
4518
+ 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.
4519
+ """
4520
+ discountAllocations: [OrderDiscountAllocation!]!
4521
+
4433
4522
  """
4434
4523
  When the order expired (e.g. pending payment timed out). Null when not expired.
4435
4524
  """
@@ -4515,6 +4604,17 @@ type OrderConnection {
4515
4604
  totalCount: Int!
4516
4605
  }
4517
4606
 
4607
+ """
4608
+ Per-code discount applied to an order. Mirrors a cart discount allocation; the sum across allocations equals the order-level discount.
4609
+ """
4610
+ type OrderDiscountAllocation {
4611
+ """Amount discounted by this code on the order, in the order currency."""
4612
+ amount: Money!
4613
+
4614
+ """The discount code that produced this allocation."""
4615
+ discountCode: String!
4616
+ }
4617
+
4518
4618
  """Order edge for pagination"""
4519
4619
  type OrderEdge {
4520
4620
  """Cursor"""
@@ -5032,6 +5132,21 @@ input PickupPointInput {
5032
5132
  provider: String!
5033
5133
  }
5034
5134
 
5135
+ """
5136
+ How the buyer selects a pickup point for a shipping method that requires one.
5137
+ """
5138
+ enum PickupSelectionMode {
5139
+ """
5140
+ No browser widget — query points server-side (by city / postal code) and render your own list. `widgetToken` / `scriptUrl` are null for this mode.
5141
+ """
5142
+ SEARCH
5143
+
5144
+ """
5145
+ Render the carrier map widget (e.g. InPost Geowidget) initialised with `pickupConfig.widgetToken` + `pickupConfig.scriptUrl`. The buyer picks a point on the map.
5146
+ """
5147
+ WIDGET
5148
+ }
5149
+
5035
5150
  """Points estimate for an order"""
5036
5151
  type PointsEstimate {
5037
5152
  """Base points to earn"""
@@ -6911,6 +7026,31 @@ type ShippingCarrier {
6911
7026
  serviceCode: String
6912
7027
  }
6913
7028
 
7029
+ """
7030
+ Pickup-point selection config for a shipping method whose `deliveryType` is LOCKER or PICKUP_POINT. Contains ONLY public, browser-safe data — for InPost this is the domain-scoped Geowidget token, never the ShipX API secret. Use it to render the carrier map (`selectionMode = WIDGET`) or to drive a server-side point search (`selectionMode = SEARCH`).
7031
+ """
7032
+ type ShippingPickupConfig {
7033
+ """
7034
+ Carrier code this config belongs to (e.g. "inpost"). Matches `carrier.serviceCode`'s provider.
7035
+ """
7036
+ provider: String!
7037
+
7038
+ """
7039
+ CDN URL of the carrier widget script to load before rendering the map. Null for SEARCH mode.
7040
+ """
7041
+ scriptUrl: String
7042
+
7043
+ """
7044
+ How to let the buyer pick a point — WIDGET (carrier map) or SEARCH (server-side point lookup).
7045
+ """
7046
+ selectionMode: PickupSelectionMode!
7047
+
7048
+ """
7049
+ 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).
7050
+ """
7051
+ widgetToken: String
7052
+ }
7053
+
6914
7054
  """Shop information"""
6915
7055
  type Shop {
6916
7056
  """Shop physical address"""