@doswiftly/storefront-operations 11.3.1 → 11.5.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 +2 -2
- package/CHANGELOG.md +64 -0
- package/README.md +1 -0
- package/fragments.graphql +1 -0
- package/llms-full.txt +24 -2
- package/operations.json +24 -2
- package/package.json +1 -1
- package/queries.graphql +7 -0
- package/schema.graphql +90 -1
package/AGENTS.md
CHANGED
|
@@ -27,8 +27,8 @@ 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**: 11.
|
|
31
|
-
- **Queries**:
|
|
30
|
+
- **Schema version**: 11.5.0
|
|
31
|
+
- **Queries**: 49
|
|
32
32
|
- **Mutations**: 40
|
|
33
33
|
- **Fragments**: 100
|
|
34
34
|
<!-- AUTOGEN:STATS:END -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 11.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 190fd9d: Version sync with `@doswiftly/storefront-sdk` (linked release pair). No operations file changes — `OrderByToken` query and `Order.accessToken` field were shipped in the previous version (11.4.0); this bump keeps the operations package version aligned with the SDK release that now selects `accessToken` in its built-in `Order` fragment for `cartComplete`.
|
|
8
|
+
|
|
9
|
+
If you already use `@doswiftly/storefront-operations` for codegen, no regeneration is required (`queries.graphql` and `fragments.graphql` are unchanged). The companion `@doswiftly/storefront-sdk` upgrade is where the actual behavior change lands (see that changelog entry).
|
|
10
|
+
|
|
11
|
+
- e6c80ce: Auth route handlers (`createSetTokenHandler`, `createClearTokenHandler`, `createWhoamiHandler`) now accept an optional `isTrustedOrigin` predicate. This unblocks the BFF auth flow when the storefront runs behind a reverse proxy (DoSwiftly hosting, Vercel, custom edge proxy) that rewrites or strips the `Host` header — since 11.3.0 the strict `Origin host = Host` comparison returned 403 for every login, logout, and page-load hydration in that topology, leaving the auth cookie unset and every auth-gated route redirecting to login.
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
Each handler accepts a new `isTrustedOrigin` callback:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
type OriginValidator = (ctx: {
|
|
19
|
+
origin: string;
|
|
20
|
+
originHost: string;
|
|
21
|
+
request: Request;
|
|
22
|
+
}) => boolean | Promise<boolean>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
When the predicate returns truthy the strict `Origin host = Host header` comparison is bypassed; when it returns falsy (or is not configured) the existing strict check applies. A thrown predicate fails closed — the error is logged and the strict check applies as if the predicate returned `false`.
|
|
26
|
+
|
|
27
|
+
Two pre-built predicates are exported:
|
|
28
|
+
- `trustedForwardedHostValidator` — passes when `Origin host` equals `X-Forwarded-Host` (falling back to `X-Original-Host`). Use this when a reverse proxy you control sets one of those headers per request. The DoSwiftly hosting platform and Vercel both qualify.
|
|
29
|
+
- `originAllowlistValidator(['https://shop.example.com', 'other-shop.example'])` — passes when `Origin` matches an entry in a static list. Useful when one storefront is hosted on multiple hostnames (custom apex + platform subdomain) and you do not want to depend on forwarded-host headers.
|
|
30
|
+
|
|
31
|
+
### Upgrade impact
|
|
32
|
+
- New storefronts scaffolded via `doswiftly init` ship with `isTrustedOrigin: trustedForwardedHostValidator` configured by default.
|
|
33
|
+
- Existing storefronts using the SDK behind a reverse proxy need a 3-line change to each route handler under `app/api/auth/`:
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
createSetTokenHandler,
|
|
37
|
+
trustedForwardedHostValidator,
|
|
38
|
+
} from "@doswiftly/storefront-sdk";
|
|
39
|
+
export const POST = createSetTokenHandler({
|
|
40
|
+
isTrustedOrigin: trustedForwardedHostValidator,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
- Storefronts deployed without a reverse proxy (single-tier hosting where the Next.js process receives traffic directly) need no changes — the default strict check still works because the Host header arrives intact.
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
The predicate is invoked AFTER the Origin header is parsed (rejecting malformed origins) and BEFORE the strict Host comparison. Forwarded-host validation is safe because the trusted intermediary overwrites those headers on every inbound request (`headers.set(...)`, not `append`), so an attacker cannot forge them via a browser `fetch()` — browsers cannot set `X-Forwarded-*` from JavaScript (they are forbidden request headers per the fetch spec).
|
|
48
|
+
|
|
49
|
+
Defense-in-depth layers continue to apply: CORS preflight at the backend, `SameSite=Lax` on the auth cookie, `HttpOnly` + `Secure` cookie attributes, and the strict Origin URL parse (still rejects malformed origin and the `?host=trusted` query-string bypass).
|
|
50
|
+
|
|
51
|
+
## 11.4.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- 9bd60e8: Added `OrderByToken` query and `accessToken` field on the `Order` type for guest order summary access.
|
|
56
|
+
|
|
57
|
+
**What's new for storefronts:**
|
|
58
|
+
- **`Order.accessToken: String!`** — opaque per-order token returned in `cartComplete.order.accessToken`. Pass it to `OrderByToken` to render the order summary for guests who have not signed in. The token is permanent until the order is deleted and stable across reads (safe to share via signed URLs in confirmation emails).
|
|
59
|
+
- **`OrderByToken($token: String!, $email: String)` query** — fetches a single order using the access token. The optional `email` argument is a defense-in-depth guard: when provided, it must match the order's buyer email case-insensitively; on mismatch the query returns `null` exactly like an invalid token (response shape is identical so attackers cannot distinguish the failure mode).
|
|
60
|
+
- Rate-limited to 5 requests per minute per IP+shop. Throttled clients receive a GraphQL error with `extensions.code: THROTTLED`.
|
|
61
|
+
- Response carries `Cache-Control: no-store` so per-customer data is never served from CDN or browser cache between users.
|
|
62
|
+
|
|
63
|
+
**Storage recommendation:** persist the token in an **HTTP-only cookie** (preferred) or `sessionStorage` for the post-checkout summary page — **never `localStorage`** (XSS-readable).
|
|
64
|
+
|
|
65
|
+
**Backward compatibility:** strictly additive. Existing queries (`CustomerOrder`, cart mutations, customer auth) are unchanged — upgrade is opt-in.
|
|
66
|
+
|
|
3
67
|
## 11.3.1
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -201,6 +201,7 @@ full executable body of each operation.
|
|
|
201
201
|
| `Customer` | Full customer profile — basic info plus the first 10 addresses and first 10 orders. Heaviest customer query; for narrow use cases prefer `CustomerProfile` (no orders / addresses) or `CustomerOrder` (single order). Returns null if unauthenticated. |
|
|
202
202
|
| `CustomerProfile` | Lightweight customer profile (no orders, no addresses list). Use for settings / profile pages that only need basic customer info — much cheaper than `Customer`. Returns null if unauthenticated. |
|
|
203
203
|
| `CustomerOrder` | Single order by `orderId`. Returns only orders that belong to the authenticated customer (cross-customer access returns null, not an error). Much cheaper than fetching the full `Customer` payload to access one order. Use on the order detail page. |
|
|
204
|
+
| `OrderByToken` | Fetch a single order using its opaque access token (`Order.accessToken`) — designed for guest order summary pages where the buyer has not signed in. The token is returned in `cartComplete.order.accessToken` immediately after checkout completes; persist it in an HTTP-only cookie (preferred) or `sessionStorage` for the post-checkout page (NEVER `localStorage`). Optional `email` parameter adds defense-in-depth: when provided, it is matched case-insensitively against the order's buyer email; on mismatch the query returns `null` exactly like an invalid token (the response shape is identical, so an attacker cannot distinguish "token valid, wrong email" from "token invalid"). Rate-limited to 5 requests per minute per IP+shop combination to deter token enumeration; clients exceeding the limit receive a GraphQL error with `extensions.code: THROTTLED`. The response is marked `Cache-Control: no-store` so per-customer order data is never served from CDN or browser cache between users. Safe to retry; the token is permanent until the order is deleted. |
|
|
204
205
|
|
|
205
206
|
#### Discount Code Validation
|
|
206
207
|
|
package/fragments.graphql
CHANGED
package/llms-full.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DoSwiftly Storefront Operations — Full Reference
|
|
2
2
|
|
|
3
|
-
> Schema version: **11.
|
|
4
|
-
>
|
|
3
|
+
> Schema version: **11.5.0**
|
|
4
|
+
> 49 queries · 40 mutations · 100 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.
|
|
@@ -430,6 +430,27 @@ query CustomerOrder($orderId: ID!) {
|
|
|
430
430
|
}
|
|
431
431
|
```
|
|
432
432
|
|
|
433
|
+
### Query: `OrderByToken`
|
|
434
|
+
|
|
435
|
+
**Section**: Customer (requires auth)
|
|
436
|
+
|
|
437
|
+
**Description**: Fetch a single order using its opaque access token (`Order.accessToken`) — designed for guest order summary pages where the buyer has not signed in. The token is returned in `cartComplete.order.accessToken` immediately after checkout completes; persist it in an HTTP-only cookie (preferred) or `sessionStorage` for the post-checkout page (NEVER `localStorage`). Optional `email` parameter adds defense-in-depth: when provided, it is matched case-insensitively against the order's buyer email; on mismatch the query returns `null` exactly like an invalid token (the response shape is identical, so an attacker cannot distinguish "token valid, wrong email" from "token invalid"). Rate-limited to 5 requests per minute per IP+shop combination to deter token enumeration; clients exceeding the limit receive a GraphQL error with `extensions.code: THROTTLED`. The response is marked `Cache-Control: no-store` so per-customer order data is never served from CDN or browser cache between users. Safe to retry; the token is permanent until the order is deleted.
|
|
438
|
+
|
|
439
|
+
**Variables**:
|
|
440
|
+
- `$token`: `String!`
|
|
441
|
+
- `$email`: `String`
|
|
442
|
+
|
|
443
|
+
**Fragments used**: `Order`
|
|
444
|
+
|
|
445
|
+
**GraphQL**:
|
|
446
|
+
```graphql
|
|
447
|
+
query OrderByToken($token: String!, $email: String) {
|
|
448
|
+
orderByToken(token: $token, email: $email) {
|
|
449
|
+
...Order
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
433
454
|
### Query: `CartValidateDiscountCode`
|
|
434
455
|
|
|
435
456
|
**Section**: Discount Code Validation
|
|
@@ -2782,6 +2803,7 @@ fragment Customer on Customer {
|
|
|
2782
2803
|
fragment Order on Order {
|
|
2783
2804
|
id
|
|
2784
2805
|
orderNumber
|
|
2806
|
+
accessToken
|
|
2785
2807
|
totals {
|
|
2786
2808
|
total {
|
|
2787
2809
|
...Money
|
package/operations.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion": "11.
|
|
2
|
+
"schemaVersion": "11.5.0",
|
|
3
3
|
"queries": [
|
|
4
4
|
{
|
|
5
5
|
"name": "Shop",
|
|
@@ -324,6 +324,28 @@
|
|
|
324
324
|
],
|
|
325
325
|
"body": "query CustomerOrder($orderId: ID!) {\n customerOrder(orderId: $orderId) {\n ...Order\n }\n}"
|
|
326
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"name": "OrderByToken",
|
|
329
|
+
"kind": "query",
|
|
330
|
+
"section": "Customer (requires auth)",
|
|
331
|
+
"description": "Fetch a single order using its opaque access token (`Order.accessToken`) — designed for guest order summary pages where the buyer has not signed in. The token is returned in `cartComplete.order.accessToken` immediately after checkout completes; persist it in an HTTP-only cookie (preferred) or `sessionStorage` for the post-checkout page (NEVER `localStorage`). Optional `email` parameter adds defense-in-depth: when provided, it is matched case-insensitively against the order's buyer email; on mismatch the query returns `null` exactly like an invalid token (the response shape is identical, so an attacker cannot distinguish \"token valid, wrong email\" from \"token invalid\"). Rate-limited to 5 requests per minute per IP+shop combination to deter token enumeration; clients exceeding the limit receive a GraphQL error with `extensions.code: THROTTLED`. The response is marked `Cache-Control: no-store` so per-customer order data is never served from CDN or browser cache between users. Safe to retry; the token is permanent until the order is deleted.",
|
|
332
|
+
"variables": [
|
|
333
|
+
{
|
|
334
|
+
"name": "token",
|
|
335
|
+
"type": "String!",
|
|
336
|
+
"defaultValue": null
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"name": "email",
|
|
340
|
+
"type": "String",
|
|
341
|
+
"defaultValue": null
|
|
342
|
+
}
|
|
343
|
+
],
|
|
344
|
+
"fragmentRefs": [
|
|
345
|
+
"Order"
|
|
346
|
+
],
|
|
347
|
+
"body": "query OrderByToken($token: String!, $email: String) {\n orderByToken(token: $token, email: $email) {\n ...Order\n }\n}"
|
|
348
|
+
},
|
|
327
349
|
{
|
|
328
350
|
"name": "CartValidateDiscountCode",
|
|
329
351
|
"kind": "query",
|
|
@@ -1995,7 +2017,7 @@
|
|
|
1995
2017
|
"MailingAddress",
|
|
1996
2018
|
"Money"
|
|
1997
2019
|
],
|
|
1998
|
-
"body": "fragment Order on Order {\n id\n orderNumber\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}",
|
|
2020
|
+
"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}",
|
|
1999
2021
|
"onType": "Order"
|
|
2000
2022
|
},
|
|
2001
2023
|
{
|
package/package.json
CHANGED
package/queries.graphql
CHANGED
|
@@ -231,6 +231,13 @@ query CustomerOrder($orderId: ID!) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
# Fetch a single order using its opaque access token (`Order.accessToken`) — designed for guest order summary pages where the buyer has not signed in. The token is returned in `cartComplete.order.accessToken` immediately after checkout completes; persist it in an HTTP-only cookie (preferred) or `sessionStorage` for the post-checkout page (NEVER `localStorage`). Optional `email` parameter adds defense-in-depth: when provided, it is matched case-insensitively against the order's buyer email; on mismatch the query returns `null` exactly like an invalid token (the response shape is identical, so an attacker cannot distinguish "token valid, wrong email" from "token invalid"). Rate-limited to 5 requests per minute per IP+shop combination to deter token enumeration; clients exceeding the limit receive a GraphQL error with `extensions.code: THROTTLED`. The response is marked `Cache-Control: no-store` so per-customer order data is never served from CDN or browser cache between users. Safe to retry; the token is permanent until the order is deleted.
|
|
235
|
+
query OrderByToken($token: String!, $email: String) {
|
|
236
|
+
orderByToken(token: $token, email: $email) {
|
|
237
|
+
...Order
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
234
241
|
# ============================================
|
|
235
242
|
# Discount Code Validation
|
|
236
243
|
# ============================================
|
package/schema.graphql
CHANGED
|
@@ -3571,6 +3571,11 @@ enum NodeType {
|
|
|
3571
3571
|
|
|
3572
3572
|
"""Customer order summary"""
|
|
3573
3573
|
type Order implements Node {
|
|
3574
|
+
"""
|
|
3575
|
+
Opaque access token (UUID v4) umożliwiający dostęp do podsumowania zamówienia bez sesji (guest order access via orderByToken query). Trwały per order — share dla forwarded receipts. Storefront powinien przechowywać HTTP-only cookie / sessionStorage (NIGDY localStorage). Defense-in-depth: orderByToken akceptuje opcjonalny email guard żeby ograniczyć blast radius kompromisu.
|
|
3576
|
+
"""
|
|
3577
|
+
accessToken: String!
|
|
3578
|
+
|
|
3574
3579
|
"""Czy storefront może zainicjować płatność dla tego order"""
|
|
3575
3580
|
canCreatePayment: Boolean!
|
|
3576
3581
|
|
|
@@ -3592,6 +3597,11 @@ type Order implements Node {
|
|
|
3592
3597
|
"""Line items count"""
|
|
3593
3598
|
itemCount: Int!
|
|
3594
3599
|
|
|
3600
|
+
"""
|
|
3601
|
+
Pozycje zamówienia (top-level connection) — eliminuje N+1 vs Order.shipments.items
|
|
3602
|
+
"""
|
|
3603
|
+
lineItems(after: String, first: Int = 10): OrderLineItemConnection!
|
|
3604
|
+
|
|
3595
3605
|
"""Lista meta properties (Relay Connection)"""
|
|
3596
3606
|
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
3597
3607
|
|
|
@@ -3625,9 +3635,12 @@ type Order implements Node {
|
|
|
3625
3635
|
|
|
3626
3636
|
"""Paginated order connection"""
|
|
3627
3637
|
type OrderConnection {
|
|
3628
|
-
"""Order edges"""
|
|
3638
|
+
"""Order edges with cursors"""
|
|
3629
3639
|
edges: [OrderEdge!]!
|
|
3630
3640
|
|
|
3641
|
+
"""Order nodes (shortcut for edges.map(e => e.node))"""
|
|
3642
|
+
nodes: [Order!]!
|
|
3643
|
+
|
|
3631
3644
|
"""Pagination info"""
|
|
3632
3645
|
pageInfo: PageInfo!
|
|
3633
3646
|
|
|
@@ -3656,6 +3669,69 @@ enum OrderFulfillmentStatus {
|
|
|
3656
3669
|
UNFULFILLED
|
|
3657
3670
|
}
|
|
3658
3671
|
|
|
3672
|
+
"""Pozycja zamówienia (line item) z variant snapshot i live enrichment"""
|
|
3673
|
+
type OrderLineItem {
|
|
3674
|
+
"""Unique identifier"""
|
|
3675
|
+
id: ID!
|
|
3676
|
+
|
|
3677
|
+
"""
|
|
3678
|
+
Całkowita cena linii z momentu zakupu (unitPrice × quantity, minor units)
|
|
3679
|
+
"""
|
|
3680
|
+
originalTotalPrice: Money!
|
|
3681
|
+
|
|
3682
|
+
"""
|
|
3683
|
+
Cena jednostkowa z momentu zakupu (minor units, include BUNDLED surcharges)
|
|
3684
|
+
"""
|
|
3685
|
+
originalUnitPrice: Money!
|
|
3686
|
+
|
|
3687
|
+
"""Ilość zamówiona (snapshot z checkout)"""
|
|
3688
|
+
quantity: Int!
|
|
3689
|
+
|
|
3690
|
+
"""Ilość już zrealizowana (shipped — per-item fulfillment tracking)"""
|
|
3691
|
+
quantityFulfilled: Int!
|
|
3692
|
+
|
|
3693
|
+
"""
|
|
3694
|
+
Ilość pozostała do zwrotu/refund (quantity - returnedQuantity, clamp ≥ 0). Industry-standard semantyka dla refund tracking.
|
|
3695
|
+
"""
|
|
3696
|
+
refundableQuantity: Int!
|
|
3697
|
+
|
|
3698
|
+
"""
|
|
3699
|
+
Nazwa produktu z momentu zakupu (snapshot). NIE live `Product.name` — preserved nawet po edycji produktu (KSeF/invoice compliance).
|
|
3700
|
+
"""
|
|
3701
|
+
title: String!
|
|
3702
|
+
|
|
3703
|
+
"""
|
|
3704
|
+
Live wariant produktu (fresh image/price). Null jeśli wariant został usunięty po zakupie — consumer powinien użyć `title` jako fallback.
|
|
3705
|
+
"""
|
|
3706
|
+
variant: ProductVariant
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
"""Paginated order line items (Relay Connection)"""
|
|
3710
|
+
type OrderLineItemConnection {
|
|
3711
|
+
"""Order line item edges with cursors"""
|
|
3712
|
+
edges: [OrderLineItemEdge!]!
|
|
3713
|
+
|
|
3714
|
+
"""Order line item nodes (shortcut for edges.map(e => e.node))"""
|
|
3715
|
+
nodes: [OrderLineItem!]!
|
|
3716
|
+
|
|
3717
|
+
"""Pagination info"""
|
|
3718
|
+
pageInfo: PageInfo!
|
|
3719
|
+
|
|
3720
|
+
"""
|
|
3721
|
+
Total count of line items in the order (convention parity z OrderConnection/MailingAddressConnection)
|
|
3722
|
+
"""
|
|
3723
|
+
totalCount: Int!
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
"""Order line item edge"""
|
|
3727
|
+
type OrderLineItemEdge {
|
|
3728
|
+
"""Cursor for pagination"""
|
|
3729
|
+
cursor: String!
|
|
3730
|
+
|
|
3731
|
+
"""Order line item node"""
|
|
3732
|
+
node: OrderLineItem!
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3659
3735
|
"""Payment status of an order"""
|
|
3660
3736
|
enum OrderPaymentStatus {
|
|
3661
3737
|
AUTHORIZED
|
|
@@ -4701,6 +4777,19 @@ type Query {
|
|
|
4701
4777
|
"""
|
|
4702
4778
|
nodes(ids: [ID!]!, type: NodeType!): [Node]!
|
|
4703
4779
|
|
|
4780
|
+
"""
|
|
4781
|
+
Fetch a single order by its opaque access token (guest order access without a session). Optional `email` argument enables defense-in-depth: if provided, must match the order buyer email (case-insensitive); on mismatch the query returns null exactly like an invalid token. Rate-limited to 5 requests per minute per IP+shop. Response carries Cache-Control: no-store.
|
|
4782
|
+
"""
|
|
4783
|
+
orderByToken(
|
|
4784
|
+
"""
|
|
4785
|
+
Optional email guard (case-insensitive). When provided, must match the order buyer email; on mismatch returns null (same shape as invalid token). The storefront decides per threat model.
|
|
4786
|
+
"""
|
|
4787
|
+
email: String
|
|
4788
|
+
|
|
4789
|
+
"""Opaque access token from Order.accessToken"""
|
|
4790
|
+
token: String!
|
|
4791
|
+
): Order
|
|
4792
|
+
|
|
4704
4793
|
"""Get a page by handle or ID"""
|
|
4705
4794
|
page(
|
|
4706
4795
|
"""Page handle"""
|