@doswiftly/storefront-operations 19.1.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/AGENTS.md CHANGED
@@ -27,7 +27,7 @@ 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**: 19.2.0
31
31
  - **Queries**: 52
32
32
  - **Mutations**: 41
33
33
  - **Fragments**: 104
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
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
+
3
77
  ## 19.1.0
4
78
 
5
79
  ### Minor Changes
package/README.md CHANGED
@@ -550,7 +550,7 @@ full executable body of each operation.
550
550
  | `ShippingCarrier` | `ShippingCarrier` | Carrier offering the shipping method — id, display name, logo image (transformable), internal service code. |
551
551
  | `DeliveryEstimate` | `DeliveryEstimate` | Estimated delivery window — `minDays` to `maxDays` plus a human-readable description (e.g. "2-4 business days"). |
552
552
  | `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. |
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. 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
554
 
555
555
  #### Attribute Filters
556
556
 
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,6 +1,6 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **19.1.0**
3
+ > Schema version: **19.2.0**
4
4
  > 52 queries · 41 mutations · 104 fragments
5
5
 
6
6
  Auto-generated from `.graphql` source files. Do not edit by hand — this file is
@@ -2961,6 +2961,12 @@ fragment Order on Order {
2961
2961
  ...MailingAddress
2962
2962
  }
2963
2963
  itemCount
2964
+ discountAllocations {
2965
+ discountCode
2966
+ amount {
2967
+ ...Money
2968
+ }
2969
+ }
2964
2970
  canCreatePayment
2965
2971
  paymentMethodType
2966
2972
  }
@@ -3982,7 +3988,7 @@ fragment FreeShippingProgress on FreeShippingProgress {
3982
3988
 
3983
3989
  **Section**: Shipping Methods
3984
3990
 
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.
3991
+ **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
3992
 
3987
3993
  **Uses fragments**: `DeliveryEstimate`, `FreeShippingProgress`, `Money`, `ShippingCarrier`
3988
3994
 
@@ -3993,6 +3999,12 @@ fragment AvailableShippingMethod on AvailableShippingMethod {
3993
3999
  name
3994
4000
  description
3995
4001
  deliveryType
4002
+ pickupConfig {
4003
+ provider
4004
+ selectionMode
4005
+ widgetToken
4006
+ scriptUrl
4007
+ }
3996
4008
  carrier {
3997
4009
  ...ShippingCarrier
3998
4010
  }
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "19.1.0",
2
+ "schemaVersion": "19.2.0",
3
3
  "queries": [
4
4
  {
5
5
  "name": "Shop",
@@ -2096,7 +2096,7 @@
2096
2096
  "MailingAddress",
2097
2097
  "Money"
2098
2098
  ],
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}",
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 discountAllocations {\n discountCode\n amount {\n ...Money\n }\n }\n canCreatePayment\n paymentMethodType\n}",
2100
2100
  "onType": "Order"
2101
2101
  },
2102
2102
  {
@@ -2597,7 +2597,7 @@
2597
2597
  "name": "AvailableShippingMethod",
2598
2598
  "kind": "fragment",
2599
2599
  "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.",
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. 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
2601
  "variables": [],
2602
2602
  "fragmentRefs": [
2603
2603
  "DeliveryEstimate",
@@ -2605,7 +2605,7 @@
2605
2605
  "Money",
2606
2606
  "ShippingCarrier"
2607
2607
  ],
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}",
2608
+ "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
2609
  "onType": "AvailableShippingMethod"
2610
2610
  },
2611
2611
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "19.1.0",
3
+ "version": "19.2.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
  """
@@ -1882,6 +1887,7 @@ type CartWarning {
1882
1887
  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
1888
  """
1884
1889
  enum CartWarningCode {
1890
+ DISCOUNT_CODE_NOT_APPLICABLE
1885
1891
  MERCHANDISE_NOT_AVAILABLE
1886
1892
  MERCHANDISE_NOT_ENOUGH_STOCK
1887
1893
  PAYMENTS_AMOUNT_REGION_MISMATCH
@@ -2893,6 +2899,7 @@ enum DiscountErrorCode {
2893
2899
  INACTIVE
2894
2900
  MINIMUM_ORDER_NOT_MET
2895
2901
  MINIMUM_QUANTITY_NOT_MET
2902
+ NOT_APPLICABLE_TO_CART
2896
2903
  NOT_FOUND
2897
2904
  NOT_STARTED
2898
2905
  SHOP_NOT_FOUND
@@ -4430,6 +4437,11 @@ type Order implements Node {
4430
4437
  """
4431
4438
  confirmedAt: DateTime
4432
4439
 
4440
+ """
4441
+ 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.
4442
+ """
4443
+ discountAllocations: [OrderDiscountAllocation!]!
4444
+
4433
4445
  """
4434
4446
  When the order expired (e.g. pending payment timed out). Null when not expired.
4435
4447
  """
@@ -4515,6 +4527,17 @@ type OrderConnection {
4515
4527
  totalCount: Int!
4516
4528
  }
4517
4529
 
4530
+ """
4531
+ Per-code discount applied to an order. Mirrors a cart discount allocation; the sum across allocations equals the order-level discount.
4532
+ """
4533
+ type OrderDiscountAllocation {
4534
+ """Amount discounted by this code on the order, in the order currency."""
4535
+ amount: Money!
4536
+
4537
+ """The discount code that produced this allocation."""
4538
+ discountCode: String!
4539
+ }
4540
+
4518
4541
  """Order edge for pagination"""
4519
4542
  type OrderEdge {
4520
4543
  """Cursor"""
@@ -5032,6 +5055,21 @@ input PickupPointInput {
5032
5055
  provider: String!
5033
5056
  }
5034
5057
 
5058
+ """
5059
+ How the buyer selects a pickup point for a shipping method that requires one.
5060
+ """
5061
+ enum PickupSelectionMode {
5062
+ """
5063
+ No browser widget — query points server-side (by city / postal code) and render your own list. `widgetToken` / `scriptUrl` are null for this mode.
5064
+ """
5065
+ SEARCH
5066
+
5067
+ """
5068
+ Render the carrier map widget (e.g. InPost Geowidget) initialised with `pickupConfig.widgetToken` + `pickupConfig.scriptUrl`. The buyer picks a point on the map.
5069
+ """
5070
+ WIDGET
5071
+ }
5072
+
5035
5073
  """Points estimate for an order"""
5036
5074
  type PointsEstimate {
5037
5075
  """Base points to earn"""
@@ -6911,6 +6949,31 @@ type ShippingCarrier {
6911
6949
  serviceCode: String
6912
6950
  }
6913
6951
 
6952
+ """
6953
+ 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`).
6954
+ """
6955
+ type ShippingPickupConfig {
6956
+ """
6957
+ Carrier code this config belongs to (e.g. "inpost"). Matches `carrier.serviceCode`'s provider.
6958
+ """
6959
+ provider: String!
6960
+
6961
+ """
6962
+ CDN URL of the carrier widget script to load before rendering the map. Null for SEARCH mode.
6963
+ """
6964
+ scriptUrl: String
6965
+
6966
+ """
6967
+ How to let the buyer pick a point — WIDGET (carrier map) or SEARCH (server-side point lookup).
6968
+ """
6969
+ selectionMode: PickupSelectionMode!
6970
+
6971
+ """
6972
+ 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).
6973
+ """
6974
+ widgetToken: String
6975
+ }
6976
+
6914
6977
  """Shop information"""
6915
6978
  type Shop {
6916
6979
  """Shop physical address"""