@tiledev/sdk-shopify 0.9.1 → 0.10.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.
Files changed (143) hide show
  1. package/README.md +650 -50
  2. package/dist/alertSettings.d.ts.map +1 -1
  3. package/dist/alertSettings.js +2 -0
  4. package/dist/alertSettings.js.map +1 -1
  5. package/dist/attribution.d.ts +21 -0
  6. package/dist/attribution.d.ts.map +1 -1
  7. package/dist/attribution.js +17 -0
  8. package/dist/attribution.js.map +1 -1
  9. package/dist/auth/pkce.d.ts +30 -0
  10. package/dist/auth/pkce.d.ts.map +1 -0
  11. package/dist/auth/pkce.js +128 -0
  12. package/dist/auth/pkce.js.map +1 -0
  13. package/dist/auth/session.d.ts +118 -0
  14. package/dist/auth/session.d.ts.map +1 -0
  15. package/dist/auth/session.js +896 -0
  16. package/dist/auth/session.js.map +1 -0
  17. package/dist/auth/storeCredit.d.ts +64 -0
  18. package/dist/auth/storeCredit.d.ts.map +1 -0
  19. package/dist/auth/storeCredit.js +274 -0
  20. package/dist/auth/storeCredit.js.map +1 -0
  21. package/dist/cart.d.ts +25 -1
  22. package/dist/cart.d.ts.map +1 -1
  23. package/dist/cart.js +95 -2
  24. package/dist/cart.js.map +1 -1
  25. package/dist/cartPolicy.js +4 -4
  26. package/dist/checkout.d.ts +123 -0
  27. package/dist/checkout.d.ts.map +1 -0
  28. package/dist/checkout.js +205 -0
  29. package/dist/checkout.js.map +1 -0
  30. package/dist/customer.d.ts.map +1 -1
  31. package/dist/customer.js +8 -0
  32. package/dist/customer.js.map +1 -1
  33. package/dist/index.d.ts +12 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +74 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/messages.d.ts.map +1 -1
  38. package/dist/messages.js +2 -0
  39. package/dist/messages.js.map +1 -1
  40. package/dist/money.d.ts +16 -0
  41. package/dist/money.d.ts.map +1 -1
  42. package/dist/money.js +55 -0
  43. package/dist/money.js.map +1 -1
  44. package/dist/orders.d.ts +99 -0
  45. package/dist/orders.d.ts.map +1 -0
  46. package/dist/orders.js +439 -0
  47. package/dist/orders.js.map +1 -0
  48. package/dist/productPage.d.ts +100 -0
  49. package/dist/productPage.d.ts.map +1 -0
  50. package/dist/productPage.js +298 -0
  51. package/dist/productPage.js.map +1 -0
  52. package/dist/productStore.d.ts +1 -1
  53. package/dist/productStore.d.ts.map +1 -1
  54. package/dist/productStore.js +63 -5
  55. package/dist/productStore.js.map +1 -1
  56. package/dist/products.d.ts.map +1 -1
  57. package/dist/products.js +15 -3
  58. package/dist/products.js.map +1 -1
  59. package/dist/purchaseRules.d.ts +74 -0
  60. package/dist/purchaseRules.d.ts.map +1 -0
  61. package/dist/purchaseRules.js +73 -0
  62. package/dist/purchaseRules.js.map +1 -0
  63. package/dist/queries.d.ts +15 -13
  64. package/dist/queries.d.ts.map +1 -1
  65. package/dist/queries.js +43 -7
  66. package/dist/queries.js.map +1 -1
  67. package/dist/react/ShopifyProvider.d.ts +167 -25
  68. package/dist/react/ShopifyProvider.d.ts.map +1 -1
  69. package/dist/react/ShopifyProvider.js +352 -207
  70. package/dist/react/ShopifyProvider.js.map +1 -1
  71. package/dist/react/appForeground.d.ts +6 -0
  72. package/dist/react/appForeground.d.ts.map +1 -0
  73. package/dist/react/appForeground.js +19 -0
  74. package/dist/react/appForeground.js.map +1 -0
  75. package/dist/react/appForeground.native.d.ts +2 -0
  76. package/dist/react/appForeground.native.d.ts.map +1 -0
  77. package/dist/react/appForeground.native.js +23 -0
  78. package/dist/react/appForeground.native.js.map +1 -0
  79. package/dist/react/index.d.ts +7 -1
  80. package/dist/react/index.d.ts.map +1 -1
  81. package/dist/react/index.js +23 -1
  82. package/dist/react/index.js.map +1 -1
  83. package/dist/react/tileCreditClient.d.ts +4 -0
  84. package/dist/react/tileCreditClient.d.ts.map +1 -0
  85. package/dist/react/tileCreditClient.js +27 -0
  86. package/dist/react/tileCreditClient.js.map +1 -0
  87. package/dist/react/useAppDiscountCode.d.ts +19 -0
  88. package/dist/react/useAppDiscountCode.d.ts.map +1 -0
  89. package/dist/react/useAppDiscountCode.js +44 -0
  90. package/dist/react/useAppDiscountCode.js.map +1 -0
  91. package/dist/react/useCartStoreCredit.d.ts +25 -0
  92. package/dist/react/useCartStoreCredit.d.ts.map +1 -0
  93. package/dist/react/useCartStoreCredit.js +292 -0
  94. package/dist/react/useCartStoreCredit.js.map +1 -0
  95. package/dist/react/useOrders.d.ts +104 -0
  96. package/dist/react/useOrders.d.ts.map +1 -0
  97. package/dist/react/useOrders.js +287 -0
  98. package/dist/react/useOrders.js.map +1 -0
  99. package/dist/react/useProduct.d.ts.map +1 -1
  100. package/dist/react/useProduct.js +1 -9
  101. package/dist/react/useProduct.js.map +1 -1
  102. package/dist/react/useProductPage.d.ts +148 -0
  103. package/dist/react/useProductPage.d.ts.map +1 -0
  104. package/dist/react/useProductPage.js +178 -0
  105. package/dist/react/useProductPage.js.map +1 -0
  106. package/dist/react/useProductRecommendations.d.ts +27 -0
  107. package/dist/react/useProductRecommendations.d.ts.map +1 -0
  108. package/dist/react/useProductRecommendations.js +76 -0
  109. package/dist/react/useProductRecommendations.js.map +1 -0
  110. package/dist/react/useStoreCredit.d.ts +58 -0
  111. package/dist/react/useStoreCredit.d.ts.map +1 -0
  112. package/dist/react/useStoreCredit.js +209 -0
  113. package/dist/react/useStoreCredit.js.map +1 -0
  114. package/dist/react/useTileCredit.d.ts +4 -3
  115. package/dist/react/useTileCredit.d.ts.map +1 -1
  116. package/dist/react/useTileCredit.js +63 -55
  117. package/dist/react/useTileCredit.js.map +1 -1
  118. package/dist/shopify.d.ts.map +1 -1
  119. package/dist/shopify.js +2 -0
  120. package/dist/shopify.js.map +1 -1
  121. package/dist/storedList.d.ts +27 -0
  122. package/dist/storedList.d.ts.map +1 -0
  123. package/dist/storedList.js +152 -0
  124. package/dist/storedList.js.map +1 -0
  125. package/dist/tileCredit.d.ts +25 -10
  126. package/dist/tileCredit.d.ts.map +1 -1
  127. package/dist/tileCredit.js +97 -45
  128. package/dist/tileCredit.js.map +1 -1
  129. package/dist/types.d.ts +407 -34
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +1 -2
  132. package/dist/types.js.map +1 -1
  133. package/dist/variants.d.ts.map +1 -1
  134. package/dist/variants.js +4 -3
  135. package/dist/variants.js.map +1 -1
  136. package/dist/waitlist.d.ts +4 -0
  137. package/dist/waitlist.d.ts.map +1 -0
  138. package/dist/waitlist.js +213 -0
  139. package/dist/waitlist.js.map +1 -0
  140. package/dist/wishlist.d.ts.map +1 -1
  141. package/dist/wishlist.js +70 -47
  142. package/dist/wishlist.js.map +1 -1
  143. package/package.json +2 -2
package/dist/types.d.ts CHANGED
@@ -42,6 +42,7 @@ export interface ShopifyConfig {
42
42
  imageTransforms?: {
43
43
  cart?: ImageTransform;
44
44
  wishlist?: ImageTransform;
45
+ waitlist?: ImageTransform;
45
46
  };
46
47
  }
47
48
  /**
@@ -75,7 +76,7 @@ export interface MetafieldIdentifier {
75
76
  * Settings panel writes this shape, so the field order here mirrors the panel's
76
77
  * Cart / Wishlist / Login / Checkout groups.
77
78
  */
78
- export type AlertMessageKey = 'cart.added' | 'cart.removed' | 'cart.limitExceeded' | 'cart.outOfStock' | 'wishlist.added' | 'wishlist.removed' | 'wishlist.empty' | 'auth.loginSuccess' | 'auth.loginFailed' | 'auth.loggedOut' | 'auth.resetLinkSent' | 'checkout.orderPlaced' | 'checkout.paymentFailed';
79
+ export type AlertMessageKey = 'cart.added' | 'cart.removed' | 'cart.limitExceeded' | 'cart.noMoreStock' | 'cart.outOfStock' | 'wishlist.added' | 'wishlist.removed' | 'wishlist.empty' | 'waitlist.added' | 'auth.loginSuccess' | 'auth.loginFailed' | 'auth.loggedOut' | 'auth.resetLinkSent' | 'checkout.orderPlaced' | 'checkout.paymentFailed';
79
80
  export type AlertMessages = Partial<Record<AlertMessageKey, string>>;
80
81
  /** `(key, fallback) => string`. Returning `''` or throwing keeps the fallback. */
81
82
  export type MessageResolver = (key: string, fallback: string) => string;
@@ -110,6 +111,12 @@ export interface ProductMedia {
110
111
  alt: string | null;
111
112
  /** Still frame — Shopify provides one for videos too, so it doubles as a poster. */
112
113
  posterUrl: string | null;
114
+ /**
115
+ * The still's original size in px (its shape, whatever `imageTransform` asked for). Null when
116
+ * Shopify has none, or the product came from a cache written before these were stored.
117
+ */
118
+ width: number | null;
119
+ height: number | null;
113
120
  /** Playable file for `Video`; null for images and external video. */
114
121
  videoUrl: string | null;
115
122
  /** YouTube/Vimeo embed for `ExternalVideo`; null otherwise. */
@@ -134,6 +141,13 @@ export interface ProductVariant {
134
141
  compareAtPrice: Money | null;
135
142
  selectedOptions: ProductSelectedOption[];
136
143
  image: Image | null;
144
+ /**
145
+ * The pre-order plan the store enrolled this variant in (its first selling-plan allocation), or
146
+ * null when it has none. Add with `sellingPlanId: sellingPlan.id` to pre-order it. Set by every
147
+ * product read (products, collections, search, recommendations, `byIds`) and by `variants.byIds`;
148
+ * absent on a cart line's `merchandise` (the line has `sellingPlanId`), hence optional.
149
+ */
150
+ sellingPlan?: Pick<SellingPlan, 'id' | 'name'> | null;
137
151
  }
138
152
  /** A pre-order/deferred-payment plan the store has enrolled a variant in. */
139
153
  export interface SellingPlan {
@@ -151,6 +165,12 @@ export interface StandaloneVariant extends ProductVariant {
151
165
  handle: string;
152
166
  featuredImage: Image | null;
153
167
  hasVideo: boolean;
168
+ /**
169
+ * The product's tags, e.g. for an app's `isBlocked` rule (an auction's `SEARCH-BLOCKED`) on a waitlist
170
+ * card (`waitlistActionFor`; SDK move 6). Missing on a variant stored before 0.10 read it, until the
171
+ * list reads it again.
172
+ */
173
+ tags?: string[];
154
174
  };
155
175
  /** Present only when the store has enrolled this variant for pre-order. */
156
176
  sellingPlan: SellingPlan | null;
@@ -236,6 +256,12 @@ export interface CartLine {
236
256
  attributes: CartLineAttribute[];
237
257
  /** SellingPlan GID the line was added under; null for a one-off purchase. */
238
258
  sellingPlanId: string | null;
259
+ /**
260
+ * The plan the line was bought on (a pre-order), with what checkout takes now and what is left to
261
+ * pay later, for the whole line; null for a one-off purchase. Optional, as a cart read before the
262
+ * SDK asked for it has none: treat a missing amount as unknown, never as zero.
263
+ */
264
+ sellingPlan?: CartLineSellingPlan | null;
239
265
  /** Needed to render a name and link back to the PDP — `merchandise.title` is only the option value. */
240
266
  product: {
241
267
  id: string;
@@ -248,12 +274,27 @@ export interface CartLine {
248
274
  compareAtAmountPerQuantity: Money | null;
249
275
  };
250
276
  }
277
+ /**
278
+ * A cart line's selling plan and how its payment splits. For a pre-authorize plan, checkout takes
279
+ * nothing (`checkoutCharge` is 0) and the whole price is charged later (`remainingBalance`).
280
+ *
281
+ * Both amounts are for the whole line. Shopify answers them per unit (checked 2026-10-05 on a line
282
+ * of 2 at $250 on a pre-authorize plan: $0 and $250), so the SDK multiplies them by the quantity.
283
+ */
284
+ export interface CartLineSellingPlan {
285
+ id: string;
286
+ name: string;
287
+ /** What checkout charges for the line now. Null when Shopify didn't say. */
288
+ checkoutCharge: Money | null;
289
+ /** What is charged for the line later (when it ships, for a pre-order). Null when Shopify didn't say. */
290
+ remainingBalance: Money | null;
291
+ }
251
292
  export interface CartDiscountCode {
252
293
  code: string;
253
294
  applicable: boolean;
254
295
  }
255
296
  /** One gift card applied to a cart. Pass `.id` to `cartGiftCardCodesRemove`
256
- * when removing (NOT the raw code). See docs section 5.5. */
297
+ * when removing (NOT the raw code). */
257
298
  export interface AppliedGiftCard {
258
299
  id: string;
259
300
  lastCharacters: string;
@@ -318,6 +359,13 @@ export interface CartLineInput {
318
359
  attributes?: CartLineAttribute[];
319
360
  /** SellingPlan GID. Passing it is what makes checkout authorise rather than capture. */
320
361
  sellingPlanId?: string | null;
362
+ /**
363
+ * The most of this variant the cart may hold, usually its stock (`stockCeiling(variant)`). Checked
364
+ * before the write, because Shopify answers 200 to an add past the stock level and clamps it
365
+ * silently. A refusal is `reason: 'stock'` with the `cart.noMoreStock` alert. Never sent to Shopify.
366
+ * Omitted or null: no ceiling.
367
+ */
368
+ maxQuantity?: number | null;
321
369
  /** Where the units came from, for `ShopifyProviderProps.attribution`. Never sent to Shopify. */
322
370
  source?: AttributionSource;
323
371
  }
@@ -345,9 +393,15 @@ export interface CartLineUpdateInput {
345
393
  export interface CartLineGuard {
346
394
  /**
347
395
  * Return the input, optionally decorated, to proceed; `null` to cancel. A decorated add is not
348
- * merged into an existing line for the same variant, so it yields a line per add.
396
+ * merged into an existing line for the same variant, so it yields a line per add. The returned
397
+ * `quantity` is what is added (a guard may lower it to the stock it could reserve).
398
+ *
399
+ * `options.quiet`: the caller reports the outcome itself (Buy again's one summary), so the guard
400
+ * should say nothing about a refusal. It still decides as usual. Set by `addLines(inputs, { quiet })`.
349
401
  */
350
- beforeAdd?(input: CartLineInput): MaybePromise<CartLineInput | null>;
402
+ beforeAdd?(input: CartLineInput, options?: {
403
+ quiet?: boolean;
404
+ }): MaybePromise<CartLineInput | null>;
351
405
  /** Only increases are offered — a decrease is reported through `onReleased` instead. */
352
406
  beforeIncrease?(line: CartLine, nextQuantity: number): MaybePromise<CartLineUpdateInput | null>;
353
407
  /** Reporting only; cannot affect the cart. */
@@ -366,10 +420,18 @@ export interface CartLineReleasedEvent {
366
420
  variantId: string;
367
421
  /** How many units left the cart. For a removal, the whole line. */
368
422
  quantity: number;
369
- /** `rejected` means the guard approved the write and Shopify refused it, so nothing was held. */
423
+ /**
424
+ * `rejected`: the guard approved the write and it didn't land (Shopify refused it, the cart limit
425
+ * refused it, or the cart couldn't be created), so whatever the guard reserved for it goes back.
426
+ */
370
427
  reason: 'decreased' | 'removed' | 'rejected';
371
- /** The line as it was before the write. Null for a rejected add, which never became one. */
428
+ /**
429
+ * The line as it was before the write. For a rejected increase, the line that was to grow; null
430
+ * for a rejected add, which never became one.
431
+ */
372
432
  line: CartLine | null;
433
+ /** A rejected add: the input as the guard approved it, with any attributes it added (its receipt). */
434
+ input?: CartLineInput;
373
435
  cart: Cart | null;
374
436
  }
375
437
  export type MaybePromise<T> = T | Promise<T>;
@@ -378,7 +440,8 @@ export type MaybePromise<T> = T | Promise<T>;
378
440
  * `maxLineItems` policy, `outOfStock` Shopify refusing an unsellable line.
379
441
  * `no-cart` means there was nothing to write to.
380
442
  */
381
- export type CartRejectionReason = 'guard' | 'limit' | 'outOfStock' | 'no-cart';
443
+ /** `stock`: the add would pass the variant's stock (`CartLineInput.maxQuantity`), checked before the write. */
444
+ export type CartRejectionReason = 'guard' | 'limit' | 'stock' | 'outOfStock' | 'no-cart';
382
445
  /**
383
446
  * The outcome of a cart write. Returned instead of a bare boolean so a caller
384
447
  * can tell a guard veto from a limit refusal — both used to read as `false` —
@@ -427,6 +490,134 @@ export interface CustomerAccessToken {
427
490
  /** ISO 8601 expiry timestamp. */
428
491
  expiresAt: string;
429
492
  }
493
+ /**
494
+ * - `password`: email and password, Shopify's classic customer accounts (Storefront
495
+ * `customerAccessToken`). Also what an App Store reviewer signs in with: a review account needs a
496
+ * password, and Shopify's web sign-in sends a one-time code to an inbox the reviewer can't read.
497
+ * - `shopify`: Shopify's hosted web sign-in, new customer accounts (Customer Account API, OAuth 2
498
+ * with PKCE, passwordless).
499
+ */
500
+ export type AuthMethod = 'password' | 'shopify';
501
+ /** The app's Customer Account API client (Shopify admin → Settings → Customer accounts → Headless or Hydrogen). */
502
+ export interface CustomerAccountConfig {
503
+ /** The number in `shopify.com/<shopId>/account`. */
504
+ shopId: string;
505
+ /** The client's id. Register the client as Public (mobile app): there is no secret, PKCE stands in for it. */
506
+ clientId: string;
507
+ /** Default `shop.<shopId>.app://callback`, the form Shopify allows for a mobile client. The app registers the scheme. */
508
+ redirectUri?: string;
509
+ /** Customer Account API version. Default: `ShopifyConfig.apiVersion`, else `2025-07`. */
510
+ apiVersion?: string;
511
+ /** Default `openid email customer-account-api:full`. */
512
+ scopes?: string[];
513
+ /** Language of Shopify's sign-in page (`ui_locales`), e.g. `fr`. */
514
+ locale?: string;
515
+ }
516
+ /**
517
+ * Where session tokens are kept. On a device, pass the keychain (expo-secure-store); AsyncStorage
518
+ * is plain text on disk. Without one, the provider's `storage` is used.
519
+ */
520
+ export interface SecureStorageAdapter {
521
+ getItem(key: string): string | null | Promise<string | null>;
522
+ setItem(key: string, value: string): void | Promise<void>;
523
+ removeItem(key: string): void | Promise<void>;
524
+ }
525
+ /**
526
+ * Opens Shopify's sign-in page in the system browser sheet and resolves with the URL it redirected
527
+ * to. expo-web-browser's `openAuthSessionAsync` fits as is:
528
+ * `(url, redirectUri) => WebBrowser.openAuthSessionAsync(url, redirectUri, { preferEphemeralSession: true })`.
529
+ * Any result but `success` with a `url` is the shopper backing out.
530
+ */
531
+ export type OpenAuthSession = (url: string, redirectUri: string) => Promise<{
532
+ type: string;
533
+ url?: string;
534
+ }>;
535
+ /** `n` cryptographically secure random bytes, e.g. expo-crypto's `getRandomBytes`. */
536
+ export type RandomBytes = (byteCount: number) => Uint8Array;
537
+ export interface AuthOptions {
538
+ /**
539
+ * Which sign-in the app offers now. It can change while the app runs (a Live Layer publish):
540
+ * `password` while the app is in App Store review, `shopify` for shoppers. A shopper already
541
+ * signed in stays signed in when it changes; `CustomerState.sessionKind` says which kind they have.
542
+ */
543
+ method: AuthMethod;
544
+ /** Required for `shopify`. */
545
+ customerAccount?: CustomerAccountConfig;
546
+ secureStorage?: SecureStorageAdapter;
547
+ /** Required for `customer.signIn()`, the system sheet. An in-app web view uses `customer.startSignIn()` instead. */
548
+ openAuthSession?: OpenAuthSession;
549
+ /** Required for `shopify`. Defaults to `crypto.getRandomValues` where the engine has it (web); Hermes doesn't. */
550
+ random?: RandomBytes;
551
+ }
552
+ /**
553
+ * A Shopify web sign-in in progress, for an app that shows the page in its own web view rather than
554
+ * the system sheet: load `url`, and when the web view is about to load a URL for which
555
+ * `isCallback(url)` is true, stop it and pass that URL to `finish`.
556
+ */
557
+ export interface SignInAttempt {
558
+ url: string;
559
+ redirectUri: string;
560
+ isCallback(url: string): boolean;
561
+ /** Signs in. `false` when Shopify refused it (`auth:loginFailed` fires) or this attempt is stale. Throws when the store can't be reached. */
562
+ finish(callbackUrl: string): Promise<boolean>;
563
+ /** The shopper closed the web view. Later `finish` calls return false. */
564
+ cancel(): void;
565
+ }
566
+ /** Where the shopper's store credit comes from: chosen per app on `ShopifyProvider`. */
567
+ export interface StoreCreditOptions {
568
+ /**
569
+ * - `shopify`: Shopify's own store credit (`storeCreditAccounts`). Readable only through the
570
+ * Customer Account API, so only for a `shopify` sign-in.
571
+ * - `tile`: Tile Credit, the tile-credit service's wallet. Works with either sign-in.
572
+ */
573
+ source: 'shopify' | 'tile';
574
+ /** Tile Credit service URL. Default `https://tile-credit.apptile.io`. */
575
+ tileCreditBaseUrl?: string;
576
+ }
577
+ /**
578
+ * Where store credit stands on the cart (`useCartStoreCredit().status`):
579
+ * - `hidden`: nothing to show: no store credit chosen, signed out, or a source this session can't read.
580
+ * - `loading`: the balance is being read for the first time.
581
+ * - `ready`: the balance is known and none of it is on the cart: `apply` can run.
582
+ * - `applying`: an `apply` is on its way.
583
+ * - `applied`: the app's card is on the cart: `remove` can run.
584
+ * - `removing`: a `remove` is on its way.
585
+ * - `atCheckout`: Shopify's store credit, which only Shopify's checkout can take: no actions.
586
+ * - `error`: the balance couldn't be read (`error` says why); `refresh` tries again.
587
+ */
588
+ export type CartStoreCreditStatus = 'hidden' | 'loading' | 'ready' | 'applying' | 'applied' | 'removing' | 'atCheckout' | 'error';
589
+ /**
590
+ * What a line of store-credit history was, the same words for either source, so a screen words each
591
+ * one itself (`useStoreCreditHistory`).
592
+ *
593
+ * - `signupBonus`, `liveShowReward`, `orderReward`: Tile Credit's own grants.
594
+ * - `addedByStore`, `removedByStore`: the store changed the balance by hand.
595
+ * - `spent`: used at checkout. `refunded`: given back (a refund to store credit, or a payment that
596
+ * was voided). `expired`: credit that ran out.
597
+ * - `added`, `removed`: anything else, by which way it moved the balance.
598
+ */
599
+ export type StoreCreditEntryKind = 'signupBonus' | 'liveShowReward' | 'orderReward' | 'addedByStore' | 'removedByStore' | 'spent' | 'refunded' | 'expired' | 'added' | 'removed';
600
+ /** One line of the shopper's store-credit history, from either source. */
601
+ export interface StoreCreditEntry {
602
+ /** Unique within the history. */
603
+ id: string;
604
+ kind: StoreCreditEntryKind;
605
+ /** True when it added to the balance; false when it took from it. */
606
+ isCredit: boolean;
607
+ /** How much, never negative: `isCredit` says which way it went. */
608
+ amount: Money;
609
+ /** ISO 8601. */
610
+ createdAt: string;
611
+ /** When this credit runs out; null when it doesn't, or for a line that took credit away. */
612
+ expiresAt: string | null;
613
+ /**
614
+ * The store's own words for a change it made by hand (Tile Credit's `reason`), when they read as a
615
+ * sentence; null for everything else, and always for Shopify's store credit, which has none.
616
+ */
617
+ note: string | null;
618
+ /** The order it came from, as the shopper knows it (`#1043`), when the source names it. */
619
+ orderName: string | null;
620
+ }
430
621
  export interface OrderLineItem {
431
622
  title: string;
432
623
  quantity: number;
@@ -453,6 +644,67 @@ export interface Order {
453
644
  shippingAddress: Address | null;
454
645
  lineItems: OrderLineItem[];
455
646
  }
647
+ /**
648
+ * Where an order has got to, for a status badge. Cancelled wins over everything; otherwise it follows
649
+ * Shopify's fulfilment status: `FULFILLED`, `PARTIALLY_FULFILLED`, and every other value (unfulfilled,
650
+ * on hold, scheduled, …) reads as `confirmed`.
651
+ */
652
+ export type OrderProgress = 'confirmed' | 'partiallyFulfilled' | 'fulfilled' | 'cancelled';
653
+ /**
654
+ * One past order, as `useOrders` lists it, the same for both sign-ins: Shopify's sign-in reads the
655
+ * Customer Account API, email and password the Storefront API.
656
+ */
657
+ export interface OrderSummary {
658
+ /** GID. Pass it to `useOrder`. */
659
+ id: string;
660
+ /** As the shopper knows it, e.g. `#1001`. */
661
+ name: string;
662
+ /** ISO 8601. */
663
+ processedAt: string;
664
+ progress: OrderProgress;
665
+ /** ISO 8601; null unless cancelled. */
666
+ cancelledAt: string | null;
667
+ totalPrice: Money;
668
+ /** Units ordered (the quantities added up). A list row counts the first 30 lines of an order. */
669
+ itemCount: number;
670
+ /** Shopify's order status page: tracking, addresses and returns. */
671
+ statusPageUrl: string | null;
672
+ }
673
+ /** One line of an order, as bought. */
674
+ export interface OrderLine {
675
+ title: string;
676
+ /** e.g. `S / Blush`; null for a product with one variant. */
677
+ variantTitle: string | null;
678
+ quantity: number;
679
+ imageUrl: string | null;
680
+ /** The price of one, before discounts. */
681
+ unitPrice: Money | null;
682
+ /** The line's total before discounts (`unitPrice` × `quantity`). */
683
+ totalPrice: Money | null;
684
+ /** For Buy again; null when the variant no longer exists. */
685
+ variantId: string | null;
686
+ }
687
+ /**
688
+ * One order in full, as `useOrder` reads it. Tracking, addresses and returns are on Shopify's status
689
+ * page (`statusPageUrl`).
690
+ *
691
+ * The totals read like a receipt: `subtotal` is the items before discounts, and `subtotal` minus
692
+ * `totalDiscount` is what Shopify charged for them. `totalDiscount`, `totalTax` and `totalRefunded`
693
+ * are null when there is none, so a screen shows those rows only when they say something;
694
+ * `totalShipping` keeps a zero, which is free shipping.
695
+ */
696
+ export interface OrderDetails extends OrderSummary {
697
+ lineItems: OrderLine[];
698
+ /** The lines' totals before discounts. */
699
+ subtotal: Money | null;
700
+ totalShipping: Money | null;
701
+ totalTax: Money | null;
702
+ /** Every discount on the items, line and order level together. */
703
+ totalDiscount: Money | null;
704
+ totalRefunded: Money | null;
705
+ /** The codes the shopper entered, e.g. `["WELCOME10"]`; automatic discounts have none. */
706
+ discountCodes: string[];
707
+ }
456
708
  export interface Blog {
457
709
  id: string;
458
710
  handle: string;
@@ -603,9 +855,18 @@ export interface TileCreditRedeemResult {
603
855
  duplicate: boolean;
604
856
  balanceCents: number;
605
857
  }
606
- export type TileCreditErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'validation' | 'conflict' | 'insufficient_balance' | 'shopify_upstream' | 'rate_limited' | 'internal' | 'network';
607
- /** Normalized error class. Branch on `.code`, not `.message`.
608
- * See docs section 7.1 for the taxonomy and UX guidance. */
858
+ /**
859
+ * What went wrong, from the service's HTTP status, plus one the cart adds:
860
+ * - `unauthorized` (401): no token, a token the service refused even after one renewal, or a shop the
861
+ * service doesn't know. The SDK never signs the shopper out over it.
862
+ * - `validation` (400): an amount under the store's minimum or over its maximum (`details.min`/`max`).
863
+ * - `insufficient_balance` (402): more than the shopper has.
864
+ * - `cart_refused`: the card was made, but Shopify didn't put it on the cart (it refused it, or
865
+ * answered without applying it). Nothing was charged: a card only reserves the credit.
866
+ * - `network`: the service couldn't be reached, or took longer than `timeoutMs`.
867
+ */
868
+ export type TileCreditErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'validation' | 'conflict' | 'insufficient_balance' | 'shopify_upstream' | 'rate_limited' | 'internal' | 'network' | 'cart_refused';
869
+ /** Normalized error class. Branch on `.code`, not `.message`: the codes are on `TileCreditErrorCode`. */
609
870
  export declare class TileCreditError extends Error {
610
871
  readonly code: TileCreditErrorCode;
611
872
  readonly status?: number;
@@ -613,10 +874,27 @@ export declare class TileCreditError extends Error {
613
874
  constructor(code: TileCreditErrorCode, message: string, status?: number, details?: Record<string, unknown>);
614
875
  }
615
876
  export interface TileCreditConfig {
616
- /** Cloud Run URL, no trailing slash. */
877
+ /** The service, no trailing slash. Default `https://tile-credit.apptile.io`. */
617
878
  baseUrl?: string;
618
- /** `shcat_…` (Customer Accounts API) OR classic Storefront customer token. */
619
- customerAccessToken: string;
879
+ /**
880
+ * Called before every request for the shopper's token: `shcat_…` (Customer Account API) or a
881
+ * classic Storefront customer token. Null means signed out: the call fails `unauthorized` without
882
+ * reaching the service. `ShopifyProvider`'s `customer.getAccessToken` is one (it refreshes an
883
+ * expiring token first).
884
+ */
885
+ getAccessToken?: () => Promise<string | null>;
886
+ /**
887
+ * Called once when the service answers 401, for a token renewed now even if the old one looked
888
+ * valid; the request is then sent again with it. Null, or left out: no second try. It never signs
889
+ * anyone out, as a 401 can also mean the service doesn't know the shop. `customer.renewAccessToken`
890
+ * is one.
891
+ */
892
+ renewAccessToken?: () => Promise<string | null>;
893
+ /**
894
+ * A fixed token, for a caller that holds one itself. It goes stale and is never renewed: prefer
895
+ * `getAccessToken`. One of the two is required.
896
+ */
897
+ customerAccessToken?: string;
620
898
  /** `{shop}.myshopify.com` — case-insensitive; lower-cased internally. */
621
899
  shopDomain: string;
622
900
  /** Cancels every in-flight request when aborted. */
@@ -651,7 +929,7 @@ export interface TileCreditAPI {
651
929
  }>;
652
930
  getConfig(): Promise<TileCreditPublicConfig>;
653
931
  redeem(input: TileCreditRedeemInput): Promise<TileCreditRedeemResult>;
654
- /** Ledger + gift-cards joined into one history feed. See docs §4.6.
932
+ /** Ledger + gift-cards joined into one history feed.
655
933
  * Note: this issues TWO requests (ledger + list-gift-cards) in parallel;
656
934
  * use `getLedger` on its own if you don't need the card metadata. */
657
935
  getHistory(opts?: {
@@ -732,11 +1010,26 @@ export interface ShopifyCartAPI {
732
1010
  countryCode?: string;
733
1011
  customerAccessToken?: string;
734
1012
  }, opts?: ImageOptions): Promise<Cart>;
735
- /** Apply one or more gift-card codes to a cart. Idempotent per code.
736
- * Requires `buyerIdentity.countryCode` on the cart — Shopify rejects
737
- * gift cards with `INVALID_PAYMENT` otherwise. Callers should set the
738
- * country first (see `setBuyerIdentity` / shop default via `shop.load`). */
1013
+ /**
1014
+ * REPLACES the cart's gift cards with `codes` (`cartGiftCardCodesUpdate`): any card already on the
1015
+ * cart and not in `codes` comes off. To add a card and keep the others, use `addGiftCardCodes`.
1016
+ * Needs `buyerIdentity.countryCode` on the cart.
1017
+ */
739
1018
  applyGiftCardCodes(cartId: string, codes: string[], opts?: ImageOptions): Promise<Cart>;
1019
+ /**
1020
+ * Adds gift-card codes, keeping the cards already on the cart (`cartGiftCardCodesAdd`). Needs
1021
+ * `buyerIdentity.countryCode` on the cart.
1022
+ *
1023
+ * **Shopify can answer without applying a code, and without an error** (a code it doesn't know came
1024
+ * back with no `userErrors` and no warnings, checked on the Storefront API 2026-07): look for each
1025
+ * code's last characters in `appliedGiftCards` afterwards. `useCart().addGiftCardCodes` does, and
1026
+ * throws when one is missing.
1027
+ *
1028
+ * The mutation exists in every Storefront API version Shopify still serves: a request for an older
1029
+ * version than the oldest supported one is answered as that one (2024-01 to 2025-07 were all served
1030
+ * as 2025-10 on 2026-10-05, `x-shopify-api-version`), so no version check is needed.
1031
+ */
1032
+ addGiftCardCodes(cartId: string, codes: string[], opts?: ImageOptions): Promise<Cart>;
740
1033
  /** Remove gift cards by their AppliedGiftCard.id (NOT the raw code). */
741
1034
  removeGiftCardCodes(cartId: string, appliedGiftCardIds: string[], opts?: ImageOptions): Promise<Cart>;
742
1035
  /**
@@ -765,6 +1058,8 @@ export interface ShopifyCustomerAPI {
765
1058
  email: string;
766
1059
  password: string;
767
1060
  }): Promise<CustomerAccessToken>;
1061
+ /** A fresh token for one that hasn't expired yet. Throws when Shopify won't renew it. */
1062
+ renew(accessToken: string): Promise<CustomerAccessToken>;
768
1063
  logout(accessToken: string): Promise<void>;
769
1064
  profile(accessToken: string): Promise<Customer | null>;
770
1065
  recoverPassword(email: string): Promise<void>;
@@ -799,16 +1094,28 @@ export interface WishlistItem {
799
1094
  /** ms epoch. */
800
1095
  addedAt: number;
801
1096
  /**
802
- * Hydrated by `init()` / `refresh()`. `undefined` = not fetched yet, `null` = no longer resolves
803
- * upstream (the entry is purged unless `keepDeleted` is set).
1097
+ * The product as last fetched, stored with the entry so the list draws offline (images past the
1098
+ * first and the media list are not kept; `hasVideo` is). Updated by `add(product)` and `refresh()`.
1099
+ * `undefined` = never fetched (an entry saved by id, or one past the storage budget), `null` = no
1100
+ * longer resolves upstream (the entry is purged unless `keepDeleted` is set).
804
1101
  */
805
1102
  product?: Product | null;
806
1103
  }
807
1104
  export interface WishlistInitOptions {
808
1105
  /** Defaults to `window.localStorage` on web, no-op elsewhere. */
809
1106
  storage?: WishlistStorageAdapter;
810
- /** Defaults to `tile:shopify:wishlist:v1`. */
1107
+ /**
1108
+ * Defaults to `tile:shopify:wishlist:v1`. An app moving from Apptile's engine passes its old key
1109
+ * (`<apptile app id>_WishlistProducts`): entries are read in either shape and written in one both
1110
+ * understand.
1111
+ */
811
1112
  storageKey?: string;
1113
+ /**
1114
+ * Keys an earlier app kept its wishlist under, merged into `storageKey` once each and left as they
1115
+ * were. Entries not saved yet are added, newest first. Reads sdk-shopify's `{ productId, basic,
1116
+ * addedAt }` and Apptile's `{ id, handle }` (a numeric product id).
1117
+ */
1118
+ migrateFrom?: string[];
812
1119
  /** Product IDs per hydration request. Default 100, near Shopify's query cost ceiling. */
813
1120
  batchSize?: number;
814
1121
  /** Default `true`. False for lazy hydration — call `refresh()` on your own schedule. */
@@ -826,6 +1133,66 @@ export interface WishlistRefreshOptions {
826
1133
  */
827
1134
  keepDeleted?: boolean;
828
1135
  }
1136
+ /**
1137
+ * One size or colour a shopper is waiting on (sold out, or held in other carts), kept on the device
1138
+ * like the wishlist. Keyed by variant: you wait on a size, and pre-order is decided per variant.
1139
+ */
1140
+ export interface WaitlistItem {
1141
+ /** The variant's GID: the list's key. */
1142
+ variantId: string;
1143
+ /** Known once the variant has been fetched: Apptile's engine stored only the product's handle. */
1144
+ productId?: string;
1145
+ productHandle?: string;
1146
+ /** ms epoch; 0 for an entry an earlier app stored without a date. */
1147
+ addedAt: number;
1148
+ /**
1149
+ * The variant as last fetched (stock, price, pre-order plan, its product's card fields), stored
1150
+ * with the entry so the list draws offline. `undefined` = never fetched, `null` = the store no
1151
+ * longer has it: kept, in case it comes back, for the screen to leave out.
1152
+ */
1153
+ variant?: StandaloneVariant | null;
1154
+ }
1155
+ /** Joining: a variant by id, with its fetched details when the caller has them. */
1156
+ export interface WaitlistEntryInput {
1157
+ variantId: string;
1158
+ productId?: string;
1159
+ productHandle?: string;
1160
+ variant?: StandaloneVariant;
1161
+ }
1162
+ export interface WaitlistInitOptions {
1163
+ /** Defaults to `window.localStorage` on web, no-op elsewhere. */
1164
+ storage?: WishlistStorageAdapter;
1165
+ /**
1166
+ * Defaults to `tile:shopify:waitlist:v1`. An app moving from Apptile's engine passes its old key
1167
+ * (`<apptile app id>_WaitlistProducts`, entries `{ id, handle }`: the numeric variant id and the
1168
+ * product's handle).
1169
+ */
1170
+ storageKey?: string;
1171
+ /**
1172
+ * Keys an earlier app kept its waitlist under, merged into `storageKey` once each and left as they
1173
+ * were. Also reads `{ variantId, productId, addedAt }` (an ISO date or ms).
1174
+ */
1175
+ migrateFrom?: string[];
1176
+ /** Variant IDs per request. Default 100. */
1177
+ batchSize?: number;
1178
+ /** Default `true`. False for lazy fetching — call `refresh()` on your own schedule. */
1179
+ hydrateOnInit?: boolean;
1180
+ }
1181
+ export type WaitlistChangeListener = (items: WaitlistItem[]) => void;
1182
+ export interface ShopifyWaitlistAPI {
1183
+ init(opts?: WaitlistInitOptions): Promise<WaitlistItem[]>;
1184
+ isReady(): boolean;
1185
+ /** Joins. Joining a variant already on the list moves it to the front, dated now. */
1186
+ add(entry: WaitlistEntryInput): Promise<WaitlistItem>;
1187
+ remove(variantId: string): Promise<boolean>;
1188
+ has(variantId: string): boolean;
1189
+ list(): WaitlistItem[];
1190
+ count(): number;
1191
+ clear(): Promise<void>;
1192
+ /** Fetches every variant again (stock, price, pre-order plan). Rejects, changing nothing, when it can't. */
1193
+ refresh(): Promise<WaitlistItem[]>;
1194
+ onChange(listener: WaitlistChangeListener): () => void;
1195
+ }
829
1196
  export type WishlistChangeListener = (items: WishlistItem[]) => void;
830
1197
  export interface ShopifyWishlistAPI {
831
1198
  init(opts?: WishlistInitOptions): Promise<WishlistItem[]>;
@@ -861,6 +1228,7 @@ export interface ShopifyIntegration {
861
1228
  customer: ShopifyCustomerAPI;
862
1229
  blogs: ShopifyBlogsAPI;
863
1230
  wishlist: ShopifyWishlistAPI;
1231
+ waitlist: ShopifyWaitlistAPI;
864
1232
  /** Money format and currency, loaded at init. */
865
1233
  shop: {
866
1234
  load(): Promise<{
@@ -888,33 +1256,38 @@ export interface ShopifyIntegration {
888
1256
  setPolicy(policy?: CartPolicy | null): void;
889
1257
  };
890
1258
  /**
891
- * Tile Credit — customer wallet + gift-card mint + cart apply.
892
- * Configure once per customer session; see `TileCreditClient` docs
893
- * for the full flow. `null` until `shopify.tileCredit.configure(...)`.
1259
+ * Tile Credit without React: one client shared by whoever configures it. A React app uses
1260
+ * `useCartStoreCredit` and `useStoreCredit` instead, which build their own client from the
1261
+ * provider's session.
894
1262
  */
895
1263
  tileCredit: {
896
- /** Bind a customer session to Tile Credit. Rebuild the client on
897
- * logout / new customer. Safe to call multiple times — it replaces
898
- * the underlying client instance. */
1264
+ /** Bind a customer session to Tile Credit. Pass `getAccessToken` (and `renewAccessToken`) so the
1265
+ * token stays fresh; a fixed `customerAccessToken` needs a new `configure` for each token. Safe to
1266
+ * call multiple times — it replaces the underlying client instance. */
899
1267
  configure(config: TileCreditConfig): TileCreditAPI;
900
1268
  /** The active client, or `null` when `configure` hasn't been called. */
901
1269
  client(): TileCreditAPI | null;
902
1270
  /**
903
- * Redeem then apply to a Shopify cart in one call — mints a gift card,
904
- * ensures the cart has a `buyerIdentity.countryCode` (belt + suspenders
905
- * even if already set), and applies the code. Returns the mint result
906
- * AND the updated cart. Docs section 5.7.
1271
+ * @deprecated Use `useCartStoreCredit()`: it keeps one Apply at a time, finds its card on the cart
1272
+ * and writes through the provider's cart queue. Kept for callers without React.
1273
+ *
1274
+ * Mints a gift card and adds it to the cart, keeping the cart's other gift cards. The cart's
1275
+ * country is set only when it has none, with its email kept (and the shopper linked again when
1276
+ * `customerAccessToken` is passed). Throws `TileCreditError('cart_refused')` when Shopify didn't put
1277
+ * the card on the cart.
907
1278
  */
908
1279
  redeemAndApplyToCart(opts: {
909
1280
  cartId: string;
910
1281
  amountCents: number;
911
- /** Persist BEFORE the call for crash-safe retries. Auto-generated
912
- * if omitted (loses that guarantee — see docs section 8). */
1282
+ /** The same key returns the same card, so a retry of one attempt never mints a second.
1283
+ * Auto-generated if omitted. */
913
1284
  idempotencyKey?: string;
914
1285
  reason?: string;
915
- /** ISO country to set on the cart if missing. Defaults to the
1286
+ /** ISO country to set on the cart if it has none. Defaults to the
916
1287
  * shop's `localization.country.isoCode`. */
917
1288
  countryFallback?: string;
1289
+ /** The signed-in shopper's token, so setting the country keeps the cart linked to them. */
1290
+ customerAccessToken?: string;
918
1291
  }): Promise<{
919
1292
  redeemed: TileCreditRedeemResult;
920
1293
  cart: Cart;