@asteby/storefront 0.5.0 → 0.7.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.
@@ -38,6 +38,30 @@ type SelectedOption = {
38
38
  name: string;
39
39
  value: string;
40
40
  };
41
+ /**
42
+ * Server-resolved availability state (stock policy of the market x `show_stock`).
43
+ * The storefront never computes it. Treat an unknown value as `out_of_stock`.
44
+ */
45
+ type AvailabilityStatus = 'in_stock' | 'low_stock' | 'out_of_stock' | 'backorder' | 'preorder';
46
+ /**
47
+ * Availability of a variant (or the summary of a product). `quantity` and
48
+ * `max_per_order` are already filtered by the shop's `show_stock`: with `none`
49
+ * or `in_stock_label` the quantity is always `null`. Never infer a number from
50
+ * any other field.
51
+ */
52
+ type Availability = {
53
+ status: AvailabilityStatus;
54
+ /** Units the buyer may see; `null` when hidden. With `low_stock_threshold:N` it only comes when `status` is `low_stock`. */
55
+ quantity: number | null;
56
+ /** Max per order: min(max line quantity, visible quantity). `null` = no cap to show. */
57
+ max_per_order: number | null;
58
+ /** `YYYY-MM-DD`, only `backorder` / `preorder`, when OPS knows it. */
59
+ restock_eta: string | null;
60
+ /** RFC 3339, release of a `preorder`. */
61
+ preorder_release_at: string | null;
62
+ /** The buyer can subscribe to "notify me when it is back" (`restock.subscribe`). */
63
+ notify_available: boolean;
64
+ };
41
65
  type ProductVariant = {
42
66
  id: string;
43
67
  sku: string;
@@ -45,8 +69,12 @@ type ProductVariant = {
45
69
  selected_options: SelectedOption[];
46
70
  price: Money;
47
71
  compare_at_price: Money | null;
72
+ /** Derived from `availability.status` when the server sends it. */
48
73
  available_for_sale: boolean;
74
+ /** `null` when the stock is not tracked. Derived from `availability.quantity` when the server sends it. */
49
75
  quantity_available: number | null;
76
+ /** Absent on servers older than contract 1.1: use `resolveAvailability` to polyfill it. */
77
+ availability?: Availability;
50
78
  image: Image | null;
51
79
  barcode: string | null;
52
80
  };
@@ -81,6 +109,8 @@ type Product = {
81
109
  price_range: MoneyRange;
82
110
  compare_at_price_range: MoneyRange | null;
83
111
  available_for_sale: boolean;
112
+ /** Summary: the best state among the variants. Absent on servers older than contract 1.1. */
113
+ availability?: Availability;
84
114
  seo: SEO;
85
115
  /** RFC 3339. */
86
116
  updated_at: string;
@@ -113,6 +143,7 @@ type ProductConnection = Connection<Product> & {
113
143
  type StockLevel = {
114
144
  sku: string;
115
145
  quantity_available: number | null;
146
+ availability?: Availability;
116
147
  };
117
148
  type ShopContact = {
118
149
  email: string | null;
@@ -220,10 +251,17 @@ type AddressInput = Partial<Omit<Address, 'id' | 'address1' | 'city' | 'zip' | '
220
251
  type CartLineMerchandise = ProductVariant & {
221
252
  product: Pick<Product, 'handle' | 'title' | 'featured_image'>;
222
253
  };
254
+ /** Why a cart line cannot be bought as it is. `max_quantity` is `null` when `show_stock` hides it. */
255
+ type CartLineProblem = {
256
+ code: 'out_of_stock' | 'insufficient_stock';
257
+ max_quantity: number | null;
258
+ };
223
259
  type CartLine = {
224
260
  id: string;
225
261
  quantity: number;
226
262
  merchandise: CartLineMerchandise;
263
+ /** Computed when the cart is read, before checkout. `null`/absent = fine. */
264
+ problem?: CartLineProblem | null;
227
265
  cost: {
228
266
  total: Money;
229
267
  unit: Money;
@@ -256,7 +294,10 @@ type CartDelivery = {
256
294
  };
257
295
  type CartCost = {
258
296
  subtotal: Money;
297
+ /** Total discount (automatic tiers + codes). Use `discounts` to label each one. */
259
298
  discount: Money;
299
+ /** One entry per applied discount (contract 1.2). Absent on older servers. */
300
+ discounts?: CartDiscountLine[];
260
301
  shipping: Money | null;
261
302
  tax: Money | null;
262
303
  total: Money;
@@ -275,6 +316,8 @@ type Cart = {
275
316
  delivery: CartDelivery;
276
317
  cost: CartCost;
277
318
  note?: string | null;
319
+ /** Automatic-discount tiers and free-shipping progress (contract 1.2); `null`/absent without the promotions addon. */
320
+ promotions?: CartPromotions | null;
278
321
  updated_at: string;
279
322
  };
280
323
  type CartLineInput = {
@@ -337,6 +380,10 @@ type PaymentMethod = {
337
380
  /** `builtin` (manual transfer/cash) or `provider` (a payment addon). */
338
381
  kind: 'builtin' | 'provider';
339
382
  default: boolean;
383
+ /** Embedded-payment options the provider offers (contract 1.2). Absent: fall back to a redirect. */
384
+ options?: PaymentOption[];
385
+ /** Public data to mount the provider form in the browser (contract 1.2). */
386
+ client?: PaymentClient;
340
387
  };
341
388
  type PaymentMethods = {
342
389
  methods: PaymentMethod[];
@@ -371,6 +418,12 @@ type CheckoutInput = {
371
418
  /** Absolute URL; the API appends `?checkout=<id>` to it. */
372
419
  return_url: string;
373
420
  note?: string;
421
+ /** `embedded` pays in the page (`checkouts.pay`); `redirect` (default) sends the buyer to the provider. */
422
+ payment_flow?: PaymentFlow;
423
+ /** The option the buyer chose; one of `PaymentMethod.options[].key`. */
424
+ payment_option?: PaymentOptionKey;
425
+ /** The total the buyer saw. A different server total answers `422 price_mismatch`. */
426
+ expected_total?: Money;
374
427
  };
375
428
  type CheckoutPayment = {
376
429
  provider: string;
@@ -385,6 +438,12 @@ type CheckoutPayment = {
385
438
  instructions_data?: Record<string, unknown>;
386
439
  /** Offline payment instructions (OXXO reference, SPEI CLABE, manual transfer data) as text; `null` when none. */
387
440
  instructions?: string | null;
441
+ /** `embedded` when the buyer pays in the page (contract 1.2). */
442
+ flow?: PaymentFlow;
443
+ /** Public data to mount the provider form. */
444
+ client?: PaymentClient;
445
+ /** Pending action of the last attempt (voucher, 3DS challenge). */
446
+ action?: PaymentAction | null;
388
447
  };
389
448
  type CheckoutInfo = {
390
449
  /** Checkout session id; use it with `payment.instructions`. */
@@ -504,12 +563,273 @@ type Article = {
504
563
  seo: SEO;
505
564
  };
506
565
  type StorefrontErrorCode = 'validation' | 'unauthorized' | 'forbidden' | 'not_found' | 'conflict' | 'insufficient_stock' | 'price_mismatch' | 'invalid_state' | 'rate_limited' | 'not_modified' | 'server_error' | 'network' | 'timeout' | 'aborted' | 'invalid_response' | (string & {});
566
+ /** One affected line of a `409 insufficient_stock`. `line_id` is `null` when the line did not exist yet (add). */
567
+ type InsufficientStockLine = {
568
+ line_id: string | null;
569
+ variant_id: string;
570
+ sku: string;
571
+ status: AvailabilityStatus;
572
+ max_quantity: number | null;
573
+ };
574
+ type InsufficientStockDetails = {
575
+ lines: InsufficientStockLine[];
576
+ };
507
577
  type StorefrontErrorBody = {
508
578
  error: {
509
579
  code: StorefrontErrorCode;
510
580
  message: string;
511
581
  field?: string;
582
+ details?: unknown;
583
+ };
584
+ };
585
+ type RestockSubscriptionInput = {
586
+ sku: string;
587
+ /** At least one of `email` / `phone`. */
588
+ email?: string;
589
+ phone?: string;
590
+ /** Language of the notice, e.g. `es-MX`. */
591
+ locale: string;
592
+ market?: string;
593
+ /** Explicit consent to receive the notice. */
594
+ consent: true;
595
+ };
596
+ type RestockSubscriptionResult = {
597
+ status: 'pending';
598
+ };
599
+ type PaymentFlow = 'redirect' | 'embedded';
600
+ /** `card` and `cash` are paid in the page; `wallet` and `credits` always leave it (provider account). */
601
+ type PaymentOptionKey = 'card' | 'cash' | 'wallet' | 'credits';
602
+ /** A cash point (OXXO, 7 Eleven...). The list comes from the provider, never from the shop code. */
603
+ type PaymentPoint = {
604
+ id: string;
605
+ payment_method_id: string;
606
+ payment_point?: string | null;
607
+ label: string;
608
+ logo_url?: string | null;
609
+ group: 'main' | 'more';
610
+ };
611
+ type PaymentOption = {
612
+ key: PaymentOptionKey;
613
+ family?: string;
614
+ /** Provider label, e.g. "Tarjeta de crédito o débito". */
615
+ label?: string;
616
+ /** `true`: choosing it sends the buyer to the provider. */
617
+ redirect: boolean;
618
+ max_installments?: number;
619
+ points?: PaymentPoint[];
620
+ /** Provider-supplied promo text, e.g. "up to 12 payments". */
621
+ label_max?: string | null;
622
+ };
623
+ /** Origins the provider needs in the page's CSP, declared by the provider. */
624
+ type PaymentClientCsp = {
625
+ script?: string[];
626
+ frame?: string[];
627
+ connect?: string[];
628
+ img?: string[];
629
+ form?: string[];
630
+ };
631
+ /** Public data to mount a provider form. `public_key` is public by design; never a secret. */
632
+ type PaymentClient = {
633
+ /** Adapter key, e.g. `mercadopago_bricks`. */
634
+ kind: string;
635
+ sdk_url: string;
636
+ public_key: string;
637
+ locale?: string;
638
+ market?: string;
639
+ options?: PaymentOption[];
640
+ csp?: PaymentClientCsp;
641
+ test_mode?: boolean;
642
+ };
643
+ /** Neutral reasons for a rejected payment (provider codes are translated server-side). */
644
+ type DeclineCode = 'insufficient_funds' | 'invalid_cvc' | 'invalid_expiry' | 'invalid_card_data' | 'call_issuer' | 'card_disabled' | 'duplicate' | 'max_attempts' | 'authentication_failed' | 'declined';
645
+ type PaymentVoucher = {
646
+ payment_point: string;
647
+ reference: string;
648
+ /** https page with the printable voucher. */
649
+ voucher_url?: string | null;
650
+ barcode?: string | null;
651
+ clabe?: string | null;
652
+ /** The exact amount to pay. The cart sends a decimal string plus `currency`; a `Money` object is also accepted. */
653
+ amount?: Money | string | number;
654
+ currency?: string;
655
+ /** RFC 3339. */
656
+ expires_at?: string | null;
657
+ };
658
+ type PaymentAction = {
659
+ type: 'none' | 'challenge' | 'voucher' | 'redirect';
660
+ challenge?: {
661
+ url?: string;
662
+ creq?: string;
663
+ } | null;
664
+ voucher?: PaymentVoucher | null;
665
+ redirect_url?: string | null;
666
+ };
667
+ type PaymentAttemptStatus = 'paid' | 'pending' | 'requires_action' | 'failed' | 'expired' | 'cancelled';
668
+ /** What the buyer should do next after a rejection (stable enum from the cart). */
669
+ type PaymentNextStep = 'retry' | 'fix_card_data' | 'use_other_card' | 'pay_cash' | 'contact_issuer' | 'check_email' | 'rebuild_cart';
670
+ /** `POST /storefront/checkouts/:id/payment`. The amount is the server's; the browser only brings `provider_data`. */
671
+ type PaymentSubmitInput = {
672
+ method: 'card' | 'cash' | 'bank_transfer';
673
+ /** What the provider SDK gave the browser (token, payment_method_id...). Never logged or stored. */
674
+ provider_data: Record<string, unknown>;
675
+ device_session_id?: string;
676
+ /** The total the buyer saw. */
677
+ expected_total?: Money;
678
+ };
679
+ type PaymentAttemptResult = {
680
+ payment: {
681
+ provider: string;
682
+ status: PaymentAttemptStatus;
683
+ /** Server classification: `approved | pending | rejected | redirect | challenge`. */
684
+ outcome?: string;
685
+ decline_code?: DeclineCode | null;
686
+ next_step?: PaymentNextStep | null;
687
+ retryable?: boolean;
688
+ /** Server-written texts (es / en). The helpers prefer the shop's own catalog but fall back to these. */
689
+ message?: string | null;
690
+ message_en?: string | null;
691
+ action?: PaymentAction | null;
692
+ };
693
+ checkout?: CheckoutInfo;
694
+ order?: {
695
+ id?: string;
696
+ number?: string | null;
697
+ };
698
+ attempt?: {
699
+ n: number;
700
+ };
701
+ attempts_left?: number;
702
+ };
703
+ /** `GET /storefront/checkouts/:id/payment-config`: what to mount for an embedded checkout. */
704
+ type PaymentConfig = {
705
+ provider: string;
706
+ flow: PaymentFlow;
707
+ option: PaymentOptionKey | null;
708
+ payable: boolean;
709
+ /** Why it is not payable: `not_embedded | paid | closed | expired | too_many_attempts | payment_in_progress`. */
710
+ reason: string | null;
711
+ amount: Money;
712
+ currency: string;
713
+ public_key: string | null;
714
+ methods: string[];
715
+ options: PaymentOption[];
716
+ client: PaymentClient | null;
717
+ checkout: CheckoutInfo;
718
+ /** Pending voucher / challenge / redirect of an earlier attempt, to show again after a reload. */
719
+ action: PaymentAction | null;
720
+ attempts_left: number;
721
+ };
722
+ type PromotionReward = {
723
+ kind: 'percent';
724
+ percent: string;
725
+ } | {
726
+ kind: 'fixed';
727
+ amount: Money;
728
+ } | {
729
+ kind: 'free_shipping';
730
+ };
731
+ type PromotionTier = {
732
+ id: string;
733
+ threshold: Money;
734
+ reward: PromotionReward;
735
+ reached: boolean;
736
+ /** Position of the milestone on the bar (0-100), computed by OPS. */
737
+ percent_of_max: number;
738
+ };
739
+ type CartPromotions = {
740
+ version: 1;
741
+ promotion_id: string;
742
+ title: string;
743
+ basis: Money;
744
+ tiers: PromotionTier[];
745
+ applied: {
746
+ tier_id: string;
747
+ reward: PromotionReward;
748
+ amount: Money;
749
+ } | null;
750
+ next_tier: {
751
+ tier_id: string;
752
+ threshold: Money;
753
+ remaining: Money;
754
+ reward: PromotionReward;
755
+ percent: number;
756
+ } | null;
757
+ max_reached: boolean;
758
+ percent_of_max: number;
759
+ combines_with_codes: boolean;
760
+ ends_at: string | null;
761
+ excluded_line_ids: string[];
762
+ suppressed: {
763
+ reason: 'code_better' | 'list_final' | 'limit_reached';
764
+ } | null;
765
+ free_shipping: {
766
+ /** `0.00` = no minimum. */
767
+ threshold: Money;
768
+ basis: Money;
769
+ remaining: Money;
770
+ reached: boolean;
771
+ percent: number;
772
+ } | null;
773
+ };
774
+ type CartDiscountLine = {
775
+ kind: 'automatic' | 'code';
776
+ id: string;
777
+ title: string;
778
+ code: string | null;
779
+ amount: Money;
780
+ };
781
+ type CartRecommendation = {
782
+ product: Pick<Product, 'id' | 'handle' | 'title' | 'featured_image' | 'options' | 'vendor'>;
783
+ /** Only sellable variants, with the server price. */
784
+ variants: ProductVariant[];
785
+ default_variant_id: string;
786
+ source: 'curated' | 'bought_together' | 'bestseller' | 'similar';
787
+ unlocks: {
788
+ tier_id: string;
789
+ reward: PromotionReward;
790
+ remaining_after: Money;
791
+ } | null;
792
+ };
793
+ type CartRecommendations = {
794
+ nodes: CartRecommendation[];
795
+ promotions: CartPromotions | null;
796
+ };
797
+ type RecommendationPlacement = 'checkout_upsell' | 'cart' | 'drawer';
798
+ /** Public announcement behind the entry pop-up (`GET /storefront/promotions`). Amounts come from the engine. */
799
+ type PromotionAnnouncement = {
800
+ id: string;
801
+ version: number;
802
+ placement: 'entry_popup';
803
+ headline: string;
804
+ subheadline?: string | null;
805
+ cta_label?: string | null;
806
+ /** Internal path. */
807
+ cta_url?: string | null;
808
+ image?: {
809
+ url: string;
810
+ alt: string;
811
+ } | null;
812
+ tiers: {
813
+ threshold: Money;
814
+ reward: PromotionReward;
815
+ }[];
816
+ free_shipping_threshold: Money | null;
817
+ /** Server-generated fine print. */
818
+ legal_text: string;
819
+ combines_with_codes: boolean;
820
+ starts_at: string | null;
821
+ ends_at: string | null;
822
+ frequency?: 'session' | 'days' | 'campaign';
823
+ frequency_days?: number;
824
+ trigger?: {
825
+ delay_ms?: number;
826
+ pageviews?: number;
827
+ scroll_percent?: number;
512
828
  };
513
829
  };
830
+ type PromotionsList = {
831
+ nodes: unknown[];
832
+ announcements: PromotionAnnouncement[];
833
+ };
514
834
 
515
- export type { ProductSort as $, Address as A, LocationContacts as B, Cart as C, DeliveryInput as D, LocationKind as E, Facet as F, LocationListParams as G, LocationPickup as H, Image as I, MenuItem as J, Money as K, Location as L, Menu as M, MoneyRange as N, NearestLocation as O, Order as P, OrderLine as Q, Page as R, PageInfo as S, PaymentMethod as T, PaymentMethods as U, PickupOption as V, Product as W, ProductAttributeValue as X, ProductCategory as Y, ProductConnection as Z, ProductOption as _, AddressInput as a, ProductVariant as a0, ReturnRequest as a1, ReturnRequestInput as a2, SEO as a3, Section as a4, SelectedOption as a5, ShippingRate as a6, Shop as a7, ShopBranch as a8, ShopContact as a9, ShopPolicies as aa, StockLevel as ab, StorefrontErrorBody as ac, StorefrontErrorCode as ad, Theme as ae, UnavailableRateProvider as af, Article as b, CartBuyer as c, CartBuyerInput as d, CartCost as e, CartDelivery as f, CartDiscountCode as g, CartLine as h, CartLineInput as i, CartLineMerchandise as j, CartLineUpdateInput as k, CheckoutInfo as l, CheckoutInput as m, CheckoutPayment as n, CheckoutResult as o, Collection as p, Connection as q, Customer as r, CustomerIdentifier as s, CustomerSession as t, CustomerSessionInput as u, DeliveryMethod as v, DeliveryOptions as w, FacetValue as x, InvoiceRequest as y, InvoiceResult as z };
835
+ export type { Page as $, Address as A, CustomerSession as B, Cart as C, CustomerSessionInput as D, DeclineCode as E, DeliveryInput as F, DeliveryMethod as G, DeliveryOptions as H, Facet as I, FacetValue as J, Image as K, InsufficientStockDetails as L, InsufficientStockLine as M, InvoiceRequest as N, InvoiceResult as O, Location as P, LocationContacts as Q, LocationKind as R, LocationListParams as S, LocationPickup as T, Menu as U, MenuItem as V, Money as W, MoneyRange as X, NearestLocation as Y, Order as Z, OrderLine as _, AddressInput as a, PageInfo as a0, PaymentAction as a1, PaymentAttemptResult as a2, PaymentAttemptStatus as a3, PaymentClient as a4, PaymentClientCsp as a5, PaymentConfig as a6, PaymentFlow as a7, PaymentMethod as a8, PaymentMethods as a9, ShippingRate as aA, Shop as aB, ShopBranch as aC, ShopContact as aD, ShopPolicies as aE, StockLevel as aF, StorefrontErrorBody as aG, StorefrontErrorCode as aH, Theme as aI, UnavailableRateProvider as aJ, PaymentNextStep as aa, PaymentOption as ab, PaymentOptionKey as ac, PaymentPoint as ad, PaymentSubmitInput as ae, PaymentVoucher as af, PickupOption as ag, Product as ah, ProductAttributeValue as ai, ProductCategory as aj, ProductConnection as ak, ProductOption as al, ProductSort as am, ProductVariant as an, PromotionAnnouncement as ao, PromotionReward as ap, PromotionTier as aq, PromotionsList as ar, RecommendationPlacement as as, RestockSubscriptionInput as at, RestockSubscriptionResult as au, ReturnRequest as av, ReturnRequestInput as aw, SEO as ax, Section as ay, SelectedOption as az, Article as b, Availability as c, AvailabilityStatus as d, CartBuyer as e, CartBuyerInput as f, CartCost as g, CartDelivery as h, CartDiscountCode as i, CartDiscountLine as j, CartLine as k, CartLineInput as l, CartLineMerchandise as m, CartLineProblem as n, CartLineUpdateInput as o, CartPromotions as p, CartRecommendation as q, CartRecommendations as r, CheckoutInfo as s, CheckoutInput as t, CheckoutPayment as u, CheckoutResult as v, Collection as w, Connection as x, Customer as y, CustomerIdentifier as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/storefront",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Typed client for the Storefront API v1 (catalog, cart, checkout, customer, content) using only global fetch — Node 20+, edge runtimes and browsers — plus an in-memory mock of the whole contract.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {