@behio/storefront-sdk 0.4.0 → 0.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/README.md CHANGED
@@ -84,6 +84,7 @@ function ProductList() {
84
84
  | `wishlist` | Add, remove, check |
85
85
  | `reviews` | Submit, list, vote helpful |
86
86
  | `addresses` | Address autocomplete with debounce hook |
87
+ | `shipping` | List shipping methods and fetch live carrier quotes (Zaslat.cz + extensible) |
87
88
  | `returns` | Submit return requests |
88
89
  | `consent` | Cookie consent (GDPR) |
89
90
  | `quotes` | B2B quote requests |
@@ -142,6 +142,7 @@ var BehioStorefront = class {
142
142
  this.consent = new ConsentModule(this);
143
143
  this.quotes = new QuotesModule(this);
144
144
  this.addresses = new AddressModule(this);
145
+ this.shipping = new ShippingModule(this);
145
146
  }
146
147
  // --- Public methods ---
147
148
  /** Get basic shop info */
@@ -637,11 +638,33 @@ var CartModule = class {
637
638
  this.client.emit("cart:updated", res.data);
638
639
  return res;
639
640
  }
640
- /** Add a bundle to the cart (price is locked at the bundle's current price) */
641
- async addBundle(bundleId, quantity = 1) {
642
- const res = await this.client.request("POST", "/cart/bundles", {
643
- body: { bundleId, quantity }
644
- });
641
+ /**
642
+ * Add a bundle to the cart. Price is snapshotted at the bundle's current
643
+ * price. Pass either the bundle id or its slug — slug is more ergonomic
644
+ * for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
645
+ *
646
+ * Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
647
+ * the request rejects with HTTP 400 if the resulting cart line would
648
+ * violate any of them. The returned error includes the relevant field
649
+ * (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
650
+ * surface a meaningful message.
651
+ *
652
+ * @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
653
+ * convenience, passing a plain string is treated as the
654
+ * bundle id for backwards compatibility.
655
+ * @param quantity How many bundles to add (defaults to 1). Capped by
656
+ * the bundle's `maxQuantity` if set.
657
+ */
658
+ async addBundle(identifier, quantity = 1) {
659
+ const body = { quantity };
660
+ if (typeof identifier === "string") {
661
+ body.bundleId = identifier;
662
+ } else if ("id" in identifier) {
663
+ body.bundleId = identifier.id;
664
+ } else {
665
+ body.bundleSlug = identifier.slug;
666
+ }
667
+ const res = await this.client.request("POST", "/cart/bundles", { body });
645
668
  if (res.error) return res;
646
669
  this.client.emit("cart:updated", res.data);
647
670
  return res;
@@ -863,6 +886,49 @@ var AddressModule = class {
863
886
  });
864
887
  }
865
888
  };
889
+ var ShippingModule = class {
890
+ constructor(client) {
891
+ this.client = client;
892
+ }
893
+ /**
894
+ * Return the configured shipping methods that pass the current
895
+ * currency + country filter. Fixed-price methods come back with
896
+ * their `pricing[]` row resolved; live-quote methods come back with
897
+ * `price` 0 here — call `quote()` to get the real live price.
898
+ */
899
+ async listMethods(opts) {
900
+ const query = {};
901
+ if (opts?.currency) query.currency = opts.currency;
902
+ if (opts?.country) query.country = opts.country;
903
+ if (opts?.cartTotal != null) query.cartTotal = String(opts.cartTotal);
904
+ if (opts?.cartWeightKg != null) query.cartWeightKg = String(opts.cartWeightKg);
905
+ return this.client.request(
906
+ "GET",
907
+ "/catalog/shipping-methods",
908
+ { query }
909
+ );
910
+ }
911
+ /**
912
+ * Quote shipping for a destination address + cart contents. Each
913
+ * configured method is evaluated:
914
+ * - `priceStrategy="fixed"` → resolved from the merchant's per-currency
915
+ * `pricing[]` rows + free-shipping threshold check.
916
+ * - `priceStrategy="live_quote"` → dispatched to the upstream
917
+ * meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
918
+ * through the merchant's markup/rounding rules.
919
+ *
920
+ * Filter `available: true` for the checkout picker; `available: false`
921
+ * rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
922
+ * …) you can log but should not display.
923
+ */
924
+ async quote(input) {
925
+ return this.client.request(
926
+ "POST",
927
+ "/catalog/shipping/quote",
928
+ { body: input }
929
+ );
930
+ }
931
+ };
866
932
 
867
933
  export {
868
934
  ProductSort,
@@ -142,6 +142,7 @@ var BehioStorefront = class {
142
142
  this.consent = new ConsentModule(this);
143
143
  this.quotes = new QuotesModule(this);
144
144
  this.addresses = new AddressModule(this);
145
+ this.shipping = new ShippingModule(this);
145
146
  }
146
147
  // --- Public methods ---
147
148
  /** Get basic shop info */
@@ -637,11 +638,33 @@ var CartModule = class {
637
638
  this.client.emit("cart:updated", res.data);
638
639
  return res;
639
640
  }
640
- /** Add a bundle to the cart (price is locked at the bundle's current price) */
641
- async addBundle(bundleId, quantity = 1) {
642
- const res = await this.client.request("POST", "/cart/bundles", {
643
- body: { bundleId, quantity }
644
- });
641
+ /**
642
+ * Add a bundle to the cart. Price is snapshotted at the bundle's current
643
+ * price. Pass either the bundle id or its slug — slug is more ergonomic
644
+ * for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
645
+ *
646
+ * Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
647
+ * the request rejects with HTTP 400 if the resulting cart line would
648
+ * violate any of them. The returned error includes the relevant field
649
+ * (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
650
+ * surface a meaningful message.
651
+ *
652
+ * @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
653
+ * convenience, passing a plain string is treated as the
654
+ * bundle id for backwards compatibility.
655
+ * @param quantity How many bundles to add (defaults to 1). Capped by
656
+ * the bundle's `maxQuantity` if set.
657
+ */
658
+ async addBundle(identifier, quantity = 1) {
659
+ const body = { quantity };
660
+ if (typeof identifier === "string") {
661
+ body.bundleId = identifier;
662
+ } else if ("id" in identifier) {
663
+ body.bundleId = identifier.id;
664
+ } else {
665
+ body.bundleSlug = identifier.slug;
666
+ }
667
+ const res = await this.client.request("POST", "/cart/bundles", { body });
645
668
  if (res.error) return res;
646
669
  this.client.emit("cart:updated", res.data);
647
670
  return res;
@@ -863,6 +886,49 @@ var AddressModule = class {
863
886
  });
864
887
  }
865
888
  };
889
+ var ShippingModule = class {
890
+ constructor(client) {
891
+ this.client = client;
892
+ }
893
+ /**
894
+ * Return the configured shipping methods that pass the current
895
+ * currency + country filter. Fixed-price methods come back with
896
+ * their `pricing[]` row resolved; live-quote methods come back with
897
+ * `price` 0 here — call `quote()` to get the real live price.
898
+ */
899
+ async listMethods(opts) {
900
+ const query = {};
901
+ if (_optionalChain([opts, 'optionalAccess', _25 => _25.currency])) query.currency = opts.currency;
902
+ if (_optionalChain([opts, 'optionalAccess', _26 => _26.country])) query.country = opts.country;
903
+ if (_optionalChain([opts, 'optionalAccess', _27 => _27.cartTotal]) != null) query.cartTotal = String(opts.cartTotal);
904
+ if (_optionalChain([opts, 'optionalAccess', _28 => _28.cartWeightKg]) != null) query.cartWeightKg = String(opts.cartWeightKg);
905
+ return this.client.request(
906
+ "GET",
907
+ "/catalog/shipping-methods",
908
+ { query }
909
+ );
910
+ }
911
+ /**
912
+ * Quote shipping for a destination address + cart contents. Each
913
+ * configured method is evaluated:
914
+ * - `priceStrategy="fixed"` → resolved from the merchant's per-currency
915
+ * `pricing[]` rows + free-shipping threshold check.
916
+ * - `priceStrategy="live_quote"` → dispatched to the upstream
917
+ * meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
918
+ * through the merchant's markup/rounding rules.
919
+ *
920
+ * Filter `available: true` for the checkout picker; `available: false`
921
+ * rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
922
+ * …) you can log but should not display.
923
+ */
924
+ async quote(input) {
925
+ return this.client.request(
926
+ "POST",
927
+ "/catalog/shipping/quote",
928
+ { body: input }
929
+ );
930
+ }
931
+ };
866
932
 
867
933
 
868
934
 
package/dist/index.d.mts CHANGED
@@ -50,10 +50,50 @@ interface ShopSeo {
50
50
  ogDescription: string | null;
51
51
  ogImage: string | null;
52
52
  }
53
+ /**
54
+ * Money amount displayed to a customer in a chosen currency.
55
+ *
56
+ * Two cases:
57
+ * - **Fixed price** (preferred): merchant configured an explicit price for
58
+ * this currency. `isApproximate` is `false`/absent and `fxSource` is `null`.
59
+ * - **Approximate / FX-converted**: no fixed price for the requested
60
+ * currency, so Behio converted from the eshop's default currency using
61
+ * today's rate from `fxSource` (CNB or Frankfurter/ECB) plus the eshop's
62
+ * safety margin. UI should show a `≈` hint and offer the base price too.
63
+ */
64
+ /**
65
+ * Payment method available at checkout. Returned by
66
+ * `GET /storefront/v1/catalog/payment-methods` filtered for the customer's
67
+ * chosen currency. Credentials (API keys, merchant ids) stay server-side
68
+ * — only customer-safe fields appear here.
69
+ */
70
+ interface CheckoutPaymentMethod {
71
+ id: string;
72
+ /** Merchant-chosen label, e.g. "Kartou (Stripe)" or "Převodem na účet". */
73
+ name: string;
74
+ description?: string | null;
75
+ /** Provider id: "stripe" | "gopay" | "comgate" | "bank_transfer" | "cod" | "custom". */
76
+ provider: string;
77
+ /** Currencies this method accepts. Empty = any. */
78
+ currencies: string[];
79
+ /** Optional fee added to the order total (e.g. COD surcharge). */
80
+ fee?: number | null;
81
+ feeCurrency?: string | null;
82
+ /** Customer-safe slice of config: bank account, IBAN, instructions, … */
83
+ publicConfig?: Record<string, unknown>;
84
+ }
53
85
  interface ProductPrice {
54
86
  amount: number;
55
87
  currency: string;
56
88
  compareAtPrice?: number | null;
89
+ /** `true` when this amount was FX-converted from the eshop default currency. */
90
+ isApproximate?: boolean;
91
+ /** Provider id ("cnb" | "frankfurter" | "manual") when FX-converted. */
92
+ fxSource?: string | null;
93
+ /** Original amount in the eshop default currency before conversion. */
94
+ baseAmount?: number | null;
95
+ /** ISO code of the currency `baseAmount` is denominated in. */
96
+ baseCurrency?: string | null;
57
97
  }
58
98
  interface ProductVolumePrice {
59
99
  minQuantity: number;
@@ -68,6 +108,17 @@ interface ProductVariant {
68
108
  price: ProductPrice;
69
109
  inStock: boolean;
70
110
  stockQuantity?: number;
111
+ /**
112
+ * Cover image URL for the variant. Falls back to the parent product's
113
+ * cover when the variant has no photo of its own — see `imageIsInherited`.
114
+ */
115
+ imageUrl?: string | null;
116
+ /**
117
+ * `true` when `imageUrl` is borrowed from the parent product because the
118
+ * variant has no cover image of its own. Use this to render a hint like
119
+ * "default photo" or to render the picture in a subtler style.
120
+ */
121
+ imageIsInherited: boolean;
71
122
  }
72
123
  interface ProductLabel {
73
124
  id: string;
@@ -454,10 +505,85 @@ interface Bundle {
454
505
  coverImage: string | null;
455
506
  endsAt: number | null;
456
507
  itemsSum: number;
508
+ /** Absolute saving vs buying the components separately, in `currency`. */
457
509
  savings: number;
510
+ /** Percentage saving, 0–100. 0 when `itemsSum` is zero. */
458
511
  savingsPercent: number;
512
+ /** Minimum bundles per order. Default 1. */
513
+ minQuantity: number;
514
+ /** Maximum bundles per order. `null` = uncapped. */
515
+ maxQuantity: number | null;
516
+ /** Lifetime stock limit. `null` = uncapped. Once exceeded, add-to-cart fails. */
517
+ stockLimit: number | null;
518
+ /** Lifetime units sold (materialized counter). Used for "X sold" badges. */
519
+ soldCount: number;
459
520
  items: BundleItem[];
460
521
  }
522
+ /**
523
+ * Shape returned by `behio.shipping.listMethods()` — the merchant's
524
+ * configured shipping methods filtered by cart currency + destination
525
+ * country. Use this for the "always-on" picker; for live quotes, prefer
526
+ * `behio.shipping.quote()` which can dispatch to the meta-provider
527
+ * (Zaslat, Shippo, …) for a live carrier rate per address.
528
+ */
529
+ interface ShippingMethodSummary {
530
+ id: string;
531
+ name: string;
532
+ description: string | null;
533
+ /** Internal routing id ("zaslat", "ppl_direct", "manual", …). Not for display. */
534
+ provider: string;
535
+ currency: string | null;
536
+ /** Final customer-facing price. Equals `basePrice` unless free-shipping kicks in. */
537
+ price: number;
538
+ /** Per-currency base price from the merchant's config. */
539
+ basePrice: number;
540
+ isFreeShipping: boolean;
541
+ freeShippingThreshold: number | null;
542
+ /** "address" | "pickup_point" | "in_store" | "digital". */
543
+ deliveryType: string;
544
+ supportsPickupPoints: boolean;
545
+ etaDaysMin: number | null;
546
+ etaDaysMax: number | null;
547
+ allowedCountries: string[];
548
+ /** Customer-safe config fields the merchant filled in (pickup address, instructions). */
549
+ publicConfig: Record<string, unknown>;
550
+ }
551
+ /**
552
+ * Input for `behio.shipping.quote()`. At minimum requires the destination
553
+ * country — passing more (zip, weight per item, cart total) lets the
554
+ * meta-provider return better-fitting carriers and triggers free-shipping
555
+ * thresholds correctly.
556
+ */
557
+ interface ShippingQuoteInput {
558
+ destinationAddress: {
559
+ country: string;
560
+ zip?: string;
561
+ city?: string;
562
+ street?: string;
563
+ };
564
+ items?: Array<{
565
+ whItemId: string;
566
+ quantity: number;
567
+ weightKg?: number;
568
+ }>;
569
+ cartTotal?: number;
570
+ currency?: string;
571
+ }
572
+ /**
573
+ * One option returned by `behio.shipping.quote()`. Methods configured for
574
+ * fixed pricing always come back with `available: true` and the merchant's
575
+ * configured price. Methods configured for live quoting come back available
576
+ * only when the upstream meta-provider returned a rate for the destination
577
+ * — otherwise `available: false` with a `reason` (e.g. `"no_rate_returned"`,
578
+ * `"live_quote_not_implemented"`). Filter `available: true` in your
579
+ * checkout picker.
580
+ */
581
+ interface ShippingQuote extends ShippingMethodSummary {
582
+ /** `"fixed"` uses `pricing` rows; `"live_quote"` came from the upstream provider. */
583
+ strategy: "fixed" | "live_quote";
584
+ available: boolean;
585
+ reason: string | null;
586
+ }
461
587
  interface CrossSellItem {
462
588
  productId: string;
463
589
  slug: string | null;
@@ -617,6 +743,7 @@ declare class BehioStorefront {
617
743
  readonly consent: ConsentModule;
618
744
  readonly quotes: QuotesModule;
619
745
  readonly addresses: AddressModule;
746
+ readonly shipping: ShippingModule;
620
747
  /** Get basic shop info */
621
748
  getShopInfo(): Promise<SdkResult<ShopInfo>>;
622
749
  /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
@@ -767,8 +894,28 @@ declare class CartModule {
767
894
  applyGiftCard(code: string): Promise<SdkResult<Cart>>;
768
895
  /** Remove a gift card from the cart */
769
896
  removeGiftCard(): Promise<SdkResult<Cart>>;
770
- /** Add a bundle to the cart (price is locked at the bundle's current price) */
771
- addBundle(bundleId: string, quantity?: number): Promise<SdkResult<Cart>>;
897
+ /**
898
+ * Add a bundle to the cart. Price is snapshotted at the bundle's current
899
+ * price. Pass either the bundle id or its slug — slug is more ergonomic
900
+ * for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
901
+ *
902
+ * Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
903
+ * the request rejects with HTTP 400 if the resulting cart line would
904
+ * violate any of them. The returned error includes the relevant field
905
+ * (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
906
+ * surface a meaningful message.
907
+ *
908
+ * @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
909
+ * convenience, passing a plain string is treated as the
910
+ * bundle id for backwards compatibility.
911
+ * @param quantity How many bundles to add (defaults to 1). Capped by
912
+ * the bundle's `maxQuantity` if set.
913
+ */
914
+ addBundle(identifier: string | {
915
+ id: string;
916
+ } | {
917
+ slug: string;
918
+ }, quantity?: number): Promise<SdkResult<Cart>>;
772
919
  /** Update quantity of a bundle already in the cart */
773
920
  updateBundleQuantity(bundleId: string, quantity: number): Promise<SdkResult<Cart>>;
774
921
  /** Remove a bundle from the cart */
@@ -910,5 +1057,46 @@ declare class AddressModule {
910
1057
  /** Get full structured address from a suggestion's placeId */
911
1058
  getDetail(placeId: string): Promise<SdkResult<AddressDetail>>;
912
1059
  }
1060
+ /**
1061
+ * Storefront shipping module — list configured methods and fetch live
1062
+ * quotes for a destination + cart. Use `listMethods` for the always-on
1063
+ * picker (sidebar, info page) and `quote` once the customer enters a
1064
+ * destination address so live-quote providers (Zaslat etc.) can return
1065
+ * destination-specific prices.
1066
+ */
1067
+ declare class ShippingModule {
1068
+ private client;
1069
+ constructor(client: BehioStorefront);
1070
+ /**
1071
+ * Return the configured shipping methods that pass the current
1072
+ * currency + country filter. Fixed-price methods come back with
1073
+ * their `pricing[]` row resolved; live-quote methods come back with
1074
+ * `price` 0 here — call `quote()` to get the real live price.
1075
+ */
1076
+ listMethods(opts?: {
1077
+ currency?: string;
1078
+ country?: string;
1079
+ cartTotal?: number;
1080
+ cartWeightKg?: number;
1081
+ }): Promise<SdkResult<{
1082
+ items: ShippingMethodSummary[];
1083
+ }>>;
1084
+ /**
1085
+ * Quote shipping for a destination address + cart contents. Each
1086
+ * configured method is evaluated:
1087
+ * - `priceStrategy="fixed"` → resolved from the merchant's per-currency
1088
+ * `pricing[]` rows + free-shipping threshold check.
1089
+ * - `priceStrategy="live_quote"` → dispatched to the upstream
1090
+ * meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
1091
+ * through the merchant's markup/rounding rules.
1092
+ *
1093
+ * Filter `available: true` for the checkout picker; `available: false`
1094
+ * rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
1095
+ * …) you can log but should not display.
1096
+ */
1097
+ quote(input: ShippingQuoteInput): Promise<SdkResult<{
1098
+ items: ShippingQuote[];
1099
+ }>>;
1100
+ }
913
1101
 
914
- export { type ActivePromotion, type AddToCartInput, type AddressDetail, type AddressSuggestion, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CookieConsent, type CookieConsentInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type GiftCardBalance, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, type ProductReview, type ProductReviewsResponse, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type QuoteRequest, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ReturnRequest, type SdkError, type SdkResult, type ShopInfo, type ShopSeo, type SubmitQuoteInput, type SubmitReturnInput, type SubmitReviewInput, type WishlistItem, err, ok, toSdkError };
1102
+ export { type ActivePromotion, type AddToCartInput, type AddressDetail, type AddressSuggestion, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CheckoutPaymentMethod, type CookieConsent, type CookieConsentInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type GiftCardBalance, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, type ProductReview, type ProductReviewsResponse, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type QuoteRequest, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ReturnRequest, type SdkError, type SdkResult, type ShippingMethodSummary, type ShippingQuote, type ShippingQuoteInput, type ShopInfo, type ShopSeo, type SubmitQuoteInput, type SubmitReturnInput, type SubmitReviewInput, type WishlistItem, err, ok, toSdkError };
package/dist/index.d.ts CHANGED
@@ -50,10 +50,50 @@ interface ShopSeo {
50
50
  ogDescription: string | null;
51
51
  ogImage: string | null;
52
52
  }
53
+ /**
54
+ * Money amount displayed to a customer in a chosen currency.
55
+ *
56
+ * Two cases:
57
+ * - **Fixed price** (preferred): merchant configured an explicit price for
58
+ * this currency. `isApproximate` is `false`/absent and `fxSource` is `null`.
59
+ * - **Approximate / FX-converted**: no fixed price for the requested
60
+ * currency, so Behio converted from the eshop's default currency using
61
+ * today's rate from `fxSource` (CNB or Frankfurter/ECB) plus the eshop's
62
+ * safety margin. UI should show a `≈` hint and offer the base price too.
63
+ */
64
+ /**
65
+ * Payment method available at checkout. Returned by
66
+ * `GET /storefront/v1/catalog/payment-methods` filtered for the customer's
67
+ * chosen currency. Credentials (API keys, merchant ids) stay server-side
68
+ * — only customer-safe fields appear here.
69
+ */
70
+ interface CheckoutPaymentMethod {
71
+ id: string;
72
+ /** Merchant-chosen label, e.g. "Kartou (Stripe)" or "Převodem na účet". */
73
+ name: string;
74
+ description?: string | null;
75
+ /** Provider id: "stripe" | "gopay" | "comgate" | "bank_transfer" | "cod" | "custom". */
76
+ provider: string;
77
+ /** Currencies this method accepts. Empty = any. */
78
+ currencies: string[];
79
+ /** Optional fee added to the order total (e.g. COD surcharge). */
80
+ fee?: number | null;
81
+ feeCurrency?: string | null;
82
+ /** Customer-safe slice of config: bank account, IBAN, instructions, … */
83
+ publicConfig?: Record<string, unknown>;
84
+ }
53
85
  interface ProductPrice {
54
86
  amount: number;
55
87
  currency: string;
56
88
  compareAtPrice?: number | null;
89
+ /** `true` when this amount was FX-converted from the eshop default currency. */
90
+ isApproximate?: boolean;
91
+ /** Provider id ("cnb" | "frankfurter" | "manual") when FX-converted. */
92
+ fxSource?: string | null;
93
+ /** Original amount in the eshop default currency before conversion. */
94
+ baseAmount?: number | null;
95
+ /** ISO code of the currency `baseAmount` is denominated in. */
96
+ baseCurrency?: string | null;
57
97
  }
58
98
  interface ProductVolumePrice {
59
99
  minQuantity: number;
@@ -68,6 +108,17 @@ interface ProductVariant {
68
108
  price: ProductPrice;
69
109
  inStock: boolean;
70
110
  stockQuantity?: number;
111
+ /**
112
+ * Cover image URL for the variant. Falls back to the parent product's
113
+ * cover when the variant has no photo of its own — see `imageIsInherited`.
114
+ */
115
+ imageUrl?: string | null;
116
+ /**
117
+ * `true` when `imageUrl` is borrowed from the parent product because the
118
+ * variant has no cover image of its own. Use this to render a hint like
119
+ * "default photo" or to render the picture in a subtler style.
120
+ */
121
+ imageIsInherited: boolean;
71
122
  }
72
123
  interface ProductLabel {
73
124
  id: string;
@@ -454,10 +505,85 @@ interface Bundle {
454
505
  coverImage: string | null;
455
506
  endsAt: number | null;
456
507
  itemsSum: number;
508
+ /** Absolute saving vs buying the components separately, in `currency`. */
457
509
  savings: number;
510
+ /** Percentage saving, 0–100. 0 when `itemsSum` is zero. */
458
511
  savingsPercent: number;
512
+ /** Minimum bundles per order. Default 1. */
513
+ minQuantity: number;
514
+ /** Maximum bundles per order. `null` = uncapped. */
515
+ maxQuantity: number | null;
516
+ /** Lifetime stock limit. `null` = uncapped. Once exceeded, add-to-cart fails. */
517
+ stockLimit: number | null;
518
+ /** Lifetime units sold (materialized counter). Used for "X sold" badges. */
519
+ soldCount: number;
459
520
  items: BundleItem[];
460
521
  }
522
+ /**
523
+ * Shape returned by `behio.shipping.listMethods()` — the merchant's
524
+ * configured shipping methods filtered by cart currency + destination
525
+ * country. Use this for the "always-on" picker; for live quotes, prefer
526
+ * `behio.shipping.quote()` which can dispatch to the meta-provider
527
+ * (Zaslat, Shippo, …) for a live carrier rate per address.
528
+ */
529
+ interface ShippingMethodSummary {
530
+ id: string;
531
+ name: string;
532
+ description: string | null;
533
+ /** Internal routing id ("zaslat", "ppl_direct", "manual", …). Not for display. */
534
+ provider: string;
535
+ currency: string | null;
536
+ /** Final customer-facing price. Equals `basePrice` unless free-shipping kicks in. */
537
+ price: number;
538
+ /** Per-currency base price from the merchant's config. */
539
+ basePrice: number;
540
+ isFreeShipping: boolean;
541
+ freeShippingThreshold: number | null;
542
+ /** "address" | "pickup_point" | "in_store" | "digital". */
543
+ deliveryType: string;
544
+ supportsPickupPoints: boolean;
545
+ etaDaysMin: number | null;
546
+ etaDaysMax: number | null;
547
+ allowedCountries: string[];
548
+ /** Customer-safe config fields the merchant filled in (pickup address, instructions). */
549
+ publicConfig: Record<string, unknown>;
550
+ }
551
+ /**
552
+ * Input for `behio.shipping.quote()`. At minimum requires the destination
553
+ * country — passing more (zip, weight per item, cart total) lets the
554
+ * meta-provider return better-fitting carriers and triggers free-shipping
555
+ * thresholds correctly.
556
+ */
557
+ interface ShippingQuoteInput {
558
+ destinationAddress: {
559
+ country: string;
560
+ zip?: string;
561
+ city?: string;
562
+ street?: string;
563
+ };
564
+ items?: Array<{
565
+ whItemId: string;
566
+ quantity: number;
567
+ weightKg?: number;
568
+ }>;
569
+ cartTotal?: number;
570
+ currency?: string;
571
+ }
572
+ /**
573
+ * One option returned by `behio.shipping.quote()`. Methods configured for
574
+ * fixed pricing always come back with `available: true` and the merchant's
575
+ * configured price. Methods configured for live quoting come back available
576
+ * only when the upstream meta-provider returned a rate for the destination
577
+ * — otherwise `available: false` with a `reason` (e.g. `"no_rate_returned"`,
578
+ * `"live_quote_not_implemented"`). Filter `available: true` in your
579
+ * checkout picker.
580
+ */
581
+ interface ShippingQuote extends ShippingMethodSummary {
582
+ /** `"fixed"` uses `pricing` rows; `"live_quote"` came from the upstream provider. */
583
+ strategy: "fixed" | "live_quote";
584
+ available: boolean;
585
+ reason: string | null;
586
+ }
461
587
  interface CrossSellItem {
462
588
  productId: string;
463
589
  slug: string | null;
@@ -617,6 +743,7 @@ declare class BehioStorefront {
617
743
  readonly consent: ConsentModule;
618
744
  readonly quotes: QuotesModule;
619
745
  readonly addresses: AddressModule;
746
+ readonly shipping: ShippingModule;
620
747
  /** Get basic shop info */
621
748
  getShopInfo(): Promise<SdkResult<ShopInfo>>;
622
749
  /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
@@ -767,8 +894,28 @@ declare class CartModule {
767
894
  applyGiftCard(code: string): Promise<SdkResult<Cart>>;
768
895
  /** Remove a gift card from the cart */
769
896
  removeGiftCard(): Promise<SdkResult<Cart>>;
770
- /** Add a bundle to the cart (price is locked at the bundle's current price) */
771
- addBundle(bundleId: string, quantity?: number): Promise<SdkResult<Cart>>;
897
+ /**
898
+ * Add a bundle to the cart. Price is snapshotted at the bundle's current
899
+ * price. Pass either the bundle id or its slug — slug is more ergonomic
900
+ * for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
901
+ *
902
+ * Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
903
+ * the request rejects with HTTP 400 if the resulting cart line would
904
+ * violate any of them. The returned error includes the relevant field
905
+ * (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
906
+ * surface a meaningful message.
907
+ *
908
+ * @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
909
+ * convenience, passing a plain string is treated as the
910
+ * bundle id for backwards compatibility.
911
+ * @param quantity How many bundles to add (defaults to 1). Capped by
912
+ * the bundle's `maxQuantity` if set.
913
+ */
914
+ addBundle(identifier: string | {
915
+ id: string;
916
+ } | {
917
+ slug: string;
918
+ }, quantity?: number): Promise<SdkResult<Cart>>;
772
919
  /** Update quantity of a bundle already in the cart */
773
920
  updateBundleQuantity(bundleId: string, quantity: number): Promise<SdkResult<Cart>>;
774
921
  /** Remove a bundle from the cart */
@@ -910,5 +1057,46 @@ declare class AddressModule {
910
1057
  /** Get full structured address from a suggestion's placeId */
911
1058
  getDetail(placeId: string): Promise<SdkResult<AddressDetail>>;
912
1059
  }
1060
+ /**
1061
+ * Storefront shipping module — list configured methods and fetch live
1062
+ * quotes for a destination + cart. Use `listMethods` for the always-on
1063
+ * picker (sidebar, info page) and `quote` once the customer enters a
1064
+ * destination address so live-quote providers (Zaslat etc.) can return
1065
+ * destination-specific prices.
1066
+ */
1067
+ declare class ShippingModule {
1068
+ private client;
1069
+ constructor(client: BehioStorefront);
1070
+ /**
1071
+ * Return the configured shipping methods that pass the current
1072
+ * currency + country filter. Fixed-price methods come back with
1073
+ * their `pricing[]` row resolved; live-quote methods come back with
1074
+ * `price` 0 here — call `quote()` to get the real live price.
1075
+ */
1076
+ listMethods(opts?: {
1077
+ currency?: string;
1078
+ country?: string;
1079
+ cartTotal?: number;
1080
+ cartWeightKg?: number;
1081
+ }): Promise<SdkResult<{
1082
+ items: ShippingMethodSummary[];
1083
+ }>>;
1084
+ /**
1085
+ * Quote shipping for a destination address + cart contents. Each
1086
+ * configured method is evaluated:
1087
+ * - `priceStrategy="fixed"` → resolved from the merchant's per-currency
1088
+ * `pricing[]` rows + free-shipping threshold check.
1089
+ * - `priceStrategy="live_quote"` → dispatched to the upstream
1090
+ * meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
1091
+ * through the merchant's markup/rounding rules.
1092
+ *
1093
+ * Filter `available: true` for the checkout picker; `available: false`
1094
+ * rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
1095
+ * …) you can log but should not display.
1096
+ */
1097
+ quote(input: ShippingQuoteInput): Promise<SdkResult<{
1098
+ items: ShippingQuote[];
1099
+ }>>;
1100
+ }
913
1101
 
914
- export { type ActivePromotion, type AddToCartInput, type AddressDetail, type AddressSuggestion, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CookieConsent, type CookieConsentInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type GiftCardBalance, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, type ProductReview, type ProductReviewsResponse, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type QuoteRequest, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ReturnRequest, type SdkError, type SdkResult, type ShopInfo, type ShopSeo, type SubmitQuoteInput, type SubmitReturnInput, type SubmitReviewInput, type WishlistItem, err, ok, toSdkError };
1102
+ export { type ActivePromotion, type AddToCartInput, type AddressDetail, type AddressSuggestion, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CheckoutPaymentMethod, type CookieConsent, type CookieConsentInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type GiftCardBalance, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, type ProductReview, type ProductReviewsResponse, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type QuoteRequest, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ReturnRequest, type SdkError, type SdkResult, type ShippingMethodSummary, type ShippingQuote, type ShippingQuoteInput, type ShopInfo, type ShopSeo, type SubmitQuoteInput, type SubmitReturnInput, type SubmitReviewInput, type WishlistItem, err, ok, toSdkError };
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
 
13
- var _chunkYLKO3SURjs = require('./chunk-YLKO3SUR.js');
13
+ var _chunkKRGDHGTYjs = require('./chunk-KRGDHGTY.js');
14
14
 
15
15
 
16
16
 
@@ -23,4 +23,4 @@ var _chunkYLKO3SURjs = require('./chunk-YLKO3SUR.js');
23
23
 
24
24
 
25
25
 
26
- exports.AddressTypes = _chunkYLKO3SURjs.AddressTypes; exports.BehioApiError = _chunkYLKO3SURjs.BehioApiError; exports.BehioNetworkError = _chunkYLKO3SURjs.BehioNetworkError; exports.BehioStorefront = _chunkYLKO3SURjs.BehioStorefront; exports.FulfillmentStatuses = _chunkYLKO3SURjs.FulfillmentStatuses; exports.OrderStatuses = _chunkYLKO3SURjs.OrderStatuses; exports.PaymentStatuses = _chunkYLKO3SURjs.PaymentStatuses; exports.ProductSort = _chunkYLKO3SURjs.ProductSort; exports.err = _chunkYLKO3SURjs.err; exports.ok = _chunkYLKO3SURjs.ok; exports.toSdkError = _chunkYLKO3SURjs.toSdkError;
26
+ exports.AddressTypes = _chunkKRGDHGTYjs.AddressTypes; exports.BehioApiError = _chunkKRGDHGTYjs.BehioApiError; exports.BehioNetworkError = _chunkKRGDHGTYjs.BehioNetworkError; exports.BehioStorefront = _chunkKRGDHGTYjs.BehioStorefront; exports.FulfillmentStatuses = _chunkKRGDHGTYjs.FulfillmentStatuses; exports.OrderStatuses = _chunkKRGDHGTYjs.OrderStatuses; exports.PaymentStatuses = _chunkKRGDHGTYjs.PaymentStatuses; exports.ProductSort = _chunkKRGDHGTYjs.ProductSort; exports.err = _chunkKRGDHGTYjs.err; exports.ok = _chunkKRGDHGTYjs.ok; exports.toSdkError = _chunkKRGDHGTYjs.toSdkError;
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  err,
11
11
  ok,
12
12
  toSdkError
13
- } from "./chunk-L5KVNLTD.mjs";
13
+ } from "./chunk-3GOQSB25.mjs";
14
14
  export {
15
15
  AddressTypes,
16
16
  BehioApiError,
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkYLKO3SURjs = require('./chunk-YLKO3SUR.js');
3
+ var _chunkKRGDHGTYjs = require('./chunk-KRGDHGTY.js');
4
4
 
5
5
  // src/react/provider.tsx
6
6
  var _react = require('react');
@@ -114,7 +114,7 @@ function BehioProvider({
114
114
  const storageAdapter = _react.useMemo.call(void 0, () => resolveStorage(storageOption), [storageOption]);
115
115
  const clientRef = _react.useRef.call(void 0, null);
116
116
  if (!clientRef.current) {
117
- clientRef.current = new (0, _chunkYLKO3SURjs.BehioStorefront)({
117
+ clientRef.current = new (0, _chunkKRGDHGTYjs.BehioStorefront)({
118
118
  apiKey,
119
119
  baseUrl,
120
120
  locale,
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BehioStorefront
3
- } from "./chunk-L5KVNLTD.mjs";
3
+ } from "./chunk-3GOQSB25.mjs";
4
4
 
5
5
  // src/react/provider.tsx
6
6
  import { useRef, useEffect, useMemo } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",