@behio/storefront-sdk 0.4.0 → 0.6.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/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;
@@ -89,13 +140,40 @@ interface ProductListItem {
89
140
  labels: ProductLabel[];
90
141
  isFeatured: boolean;
91
142
  }
143
+ /** A single responsive derivative (size + format) of a media image. */
144
+ interface ProductMediaVariant {
145
+ variant: 'thumb' | 'medium' | 'large' | 'xlarge';
146
+ format: 'jpeg' | 'webp' | 'avif';
147
+ url: string;
148
+ width: number;
149
+ height: number;
150
+ }
151
+ /**
152
+ * A product gallery entry (image or video), sourced from the inventory item
153
+ * so it is shared across every storefront listing the product. Images carry
154
+ * responsive `variants` (webp/avif/jpeg in three sizes) for fast LCP; pick
155
+ * the smallest format the client supports.
156
+ */
157
+ interface ProductMedia {
158
+ id: string;
159
+ type: 'IMAGE' | 'VIDEO';
160
+ /** Original uploaded file URL. */
161
+ url: string;
162
+ alt?: string | null;
163
+ isCover: boolean;
164
+ order: number;
165
+ variants: ProductMediaVariant[];
166
+ }
92
167
  interface ProductDetail extends ProductListItem {
93
168
  longDescription?: string;
169
+ /** @deprecated Legacy per-listing images. Prefer `media`. */
94
170
  images: Array<{
95
171
  url: string;
96
172
  alt?: string;
97
173
  order: number;
98
174
  }>;
175
+ /** Product gallery (images + videos) with responsive derivatives. */
176
+ media: ProductMedia[];
99
177
  categories: Array<{
100
178
  id: string;
101
179
  slug: string;
@@ -277,6 +355,20 @@ interface CheckoutInput {
277
355
  email: string;
278
356
  phone?: string;
279
357
  customerNote?: string;
358
+ /**
359
+ * Chosen shipping method. Required whenever the eshop has at least one
360
+ * enabled shipping method — the backend rejects the order without it.
361
+ * Prefer also sending `shippingQuoteId` from `shipping.quote()`; with only
362
+ * the method id the backend re-quotes the cart server-side.
363
+ */
364
+ shippingMethodId?: string;
365
+ /**
366
+ * Quote id from `shipping.quote()` — pins the exact server-computed price
367
+ * the customer saw. Expired quotes are re-quoted automatically; a consumed
368
+ * or foreign quote is rejected.
369
+ */
370
+ shippingQuoteId?: string;
371
+ paymentMethodId?: string;
280
372
  }
281
373
  type OrderStatus = (typeof OrderStatuses)[keyof typeof OrderStatuses];
282
374
  type PaymentStatus = (typeof PaymentStatuses)[keyof typeof PaymentStatuses];
@@ -321,6 +413,13 @@ interface OrderDetail extends OrderListItem {
321
413
  fulfillmentStatus: FulfillmentStatus;
322
414
  statusHistory: OrderStatusHistory[];
323
415
  trackingToken?: string;
416
+ /**
417
+ * For redirect payment gateways (GoPay, ...), the hosted URL the
418
+ * storefront must send the customer to in order to pay. Present only on
419
+ * the order returned by `checkout.createOrder()`. Null/absent for
420
+ * offline methods (bank transfer, COD) and zero-total orders.
421
+ */
422
+ paymentRedirectUrl?: string | null;
324
423
  }
325
424
  interface CustomerProfile {
326
425
  id: string;
@@ -454,10 +553,91 @@ interface Bundle {
454
553
  coverImage: string | null;
455
554
  endsAt: number | null;
456
555
  itemsSum: number;
556
+ /** Absolute saving vs buying the components separately, in `currency`. */
457
557
  savings: number;
558
+ /** Percentage saving, 0–100. 0 when `itemsSum` is zero. */
458
559
  savingsPercent: number;
560
+ /** Minimum bundles per order. Default 1. */
561
+ minQuantity: number;
562
+ /** Maximum bundles per order. `null` = uncapped. */
563
+ maxQuantity: number | null;
564
+ /** Lifetime stock limit. `null` = uncapped. Once exceeded, add-to-cart fails. */
565
+ stockLimit: number | null;
566
+ /** Lifetime units sold (materialized counter). Used for "X sold" badges. */
567
+ soldCount: number;
459
568
  items: BundleItem[];
460
569
  }
570
+ /**
571
+ * Shape returned by `behio.shipping.listMethods()` — the merchant's
572
+ * configured shipping methods filtered by cart currency + destination
573
+ * country. Use this for the "always-on" picker; for live quotes, prefer
574
+ * `behio.shipping.quote()` which can dispatch to the meta-provider
575
+ * (Zaslat, Shippo, …) for a live carrier rate per address.
576
+ */
577
+ interface ShippingMethodSummary {
578
+ id: string;
579
+ name: string;
580
+ description: string | null;
581
+ /** Internal routing id ("zaslat", "ppl_direct", "manual", …). Not for display. */
582
+ provider: string;
583
+ /** "fixed" = price known upfront; "live_quote" = must call shipping.quote() with address. */
584
+ priceStrategy: "fixed" | "live_quote";
585
+ currency: string | null;
586
+ /** Final customer-facing price. Null for live_quote methods (call quote() to resolve). */
587
+ price: number | null;
588
+ /** Per-currency base price from the merchant's config. Null for live_quote. */
589
+ basePrice: number | null;
590
+ isFreeShipping: boolean;
591
+ freeShippingThreshold: number | null;
592
+ /** "address" | "pickup_point" | "in_store" | "digital". */
593
+ deliveryType: string;
594
+ supportsPickupPoints: boolean;
595
+ etaDaysMin: number | null;
596
+ etaDaysMax: number | null;
597
+ allowedCountries: string[];
598
+ /** Customer-safe config fields the merchant filled in (pickup address, instructions). */
599
+ publicConfig: Record<string, unknown>;
600
+ }
601
+ /**
602
+ * Input for `behio.shipping.quote()`. At minimum requires the destination
603
+ * country — passing more (zip, weight per item, cart total) lets the
604
+ * meta-provider return better-fitting carriers and triggers free-shipping
605
+ * thresholds correctly.
606
+ */
607
+ interface ShippingQuoteInput {
608
+ destinationAddress: {
609
+ country: string;
610
+ zip?: string;
611
+ city?: string;
612
+ street?: string;
613
+ };
614
+ items?: Array<{
615
+ whItemId: string;
616
+ quantity: number;
617
+ weightKg?: number;
618
+ }>;
619
+ cartTotal?: number;
620
+ currency?: string;
621
+ }
622
+ /**
623
+ * One option returned by `behio.shipping.quote()`. Methods configured for
624
+ * fixed pricing always come back with `available: true` and the merchant's
625
+ * configured price. Methods configured for live quoting come back available
626
+ * only when the upstream meta-provider returned a rate for the destination
627
+ * — otherwise `available: false` with a `reason` (e.g. `"no_rate_returned"`,
628
+ * `"live_quote_not_implemented"`). Filter `available: true` in your
629
+ * checkout picker.
630
+ */
631
+ interface ShippingQuote extends ShippingMethodSummary {
632
+ /** `"fixed"` uses `pricing` rows; `"live_quote"` came from the upstream provider. */
633
+ strategy: "fixed" | "live_quote";
634
+ available: boolean;
635
+ reason: string | null;
636
+ /** Server-generated quote ID. Null for fixed-price methods. Pass to checkout for tamper-proof pricing. */
637
+ quoteId: string | null;
638
+ /** Quote expiry (epoch ms). Null for fixed-price methods. */
639
+ expiresAt: number | null;
640
+ }
461
641
  interface CrossSellItem {
462
642
  productId: string;
463
643
  slug: string | null;
@@ -526,18 +706,75 @@ interface SubmitReviewInput {
526
706
  authorEmail?: string;
527
707
  imageUrls?: string[];
528
708
  }
709
+ interface ReturnableOrderItem {
710
+ orderItemId: string;
711
+ productName: string;
712
+ /** Quantity ordered */
713
+ quantity: number;
714
+ /** Units still returnable (ordered minus active return claims) */
715
+ returnableQuantity: number;
716
+ }
717
+ /**
718
+ * Result of the guest order lookup used by the EU withdrawal form:
719
+ * the customer enters their order number + email and gets back the
720
+ * internal ids needed to submit a return. No account required.
721
+ */
722
+ interface ReturnableOrder {
723
+ orderId: string;
724
+ orderNumber: string;
725
+ status: string;
726
+ items: ReturnableOrderItem[];
727
+ createdAt: number;
728
+ }
729
+ interface ReturnRequestItem {
730
+ id: string;
731
+ orderItemId: string;
732
+ productName: string;
733
+ quantity: number;
734
+ reason: string | null;
735
+ imageUrls: string[];
736
+ }
737
+ /** Returned by `returns.submit()` — the acknowledged withdrawal request. */
529
738
  interface ReturnRequest {
530
739
  id: string;
740
+ eshopId: string;
531
741
  orderId: string;
742
+ /** REQUESTED | APPROVED | SHIPPED_BACK | RECEIVED | REFUNDED | REJECTED | CLOSED */
743
+ status: string;
744
+ reason: string;
745
+ customerNote: string | null;
746
+ items: ReturnRequestItem[];
747
+ createdAt: number;
748
+ }
749
+ interface ReturnStatusItem {
750
+ id: string;
751
+ productName: string;
752
+ quantity: number;
753
+ reason: string | null;
754
+ }
755
+ /** Returned by `returns.getStatus()` — the full public view of a return. */
756
+ interface ReturnStatus {
757
+ id: string;
758
+ orderNumber: string;
759
+ /** REQUESTED | APPROVED | SHIPPED_BACK | RECEIVED | REFUNDED | REJECTED | CLOSED */
532
760
  status: string;
533
761
  reason: string;
534
762
  customerNote: string | null;
535
- refundAmount: number | null;
536
763
  refundMethod: string | null;
764
+ refundAmount: number | null;
765
+ refundedAt: number | null;
766
+ returnTrackingNumber: string | null;
767
+ items: ReturnStatusItem[];
537
768
  createdAt: number;
769
+ updatedAt: number;
538
770
  }
539
771
  interface SubmitReturnInput {
540
772
  orderId: string;
773
+ /**
774
+ * Email used on the order. Required — it is the ownership gate for guest
775
+ * withdrawals; the backend rejects submissions whose email doesn't match.
776
+ */
777
+ email: string;
541
778
  reason: string;
542
779
  customerNote?: string;
543
780
  items: {
@@ -617,6 +854,7 @@ declare class BehioStorefront {
617
854
  readonly consent: ConsentModule;
618
855
  readonly quotes: QuotesModule;
619
856
  readonly addresses: AddressModule;
857
+ readonly shipping: ShippingModule;
620
858
  /** Get basic shop info */
621
859
  getShopInfo(): Promise<SdkResult<ShopInfo>>;
622
860
  /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
@@ -727,6 +965,12 @@ declare class CatalogModule {
727
965
  }>>;
728
966
  /** Check a gift card code — returns validity and remaining balance */
729
967
  checkGiftCard(code: string): Promise<SdkResult<GiftCardBalance>>;
968
+ /** List configured payment methods (filtered by currency). */
969
+ listPaymentMethods(opts?: {
970
+ currency?: string;
971
+ }): Promise<SdkResult<{
972
+ items: CheckoutPaymentMethod[];
973
+ }>>;
730
974
  }
731
975
  declare class AuthModule {
732
976
  private client;
@@ -767,8 +1011,28 @@ declare class CartModule {
767
1011
  applyGiftCard(code: string): Promise<SdkResult<Cart>>;
768
1012
  /** Remove a gift card from the cart */
769
1013
  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>>;
1014
+ /**
1015
+ * Add a bundle to the cart. Price is snapshotted at the bundle's current
1016
+ * price. Pass either the bundle id or its slug — slug is more ergonomic
1017
+ * for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
1018
+ *
1019
+ * Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
1020
+ * the request rejects with HTTP 400 if the resulting cart line would
1021
+ * violate any of them. The returned error includes the relevant field
1022
+ * (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
1023
+ * surface a meaningful message.
1024
+ *
1025
+ * @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
1026
+ * convenience, passing a plain string is treated as the
1027
+ * bundle id for backwards compatibility.
1028
+ * @param quantity How many bundles to add (defaults to 1). Capped by
1029
+ * the bundle's `maxQuantity` if set.
1030
+ */
1031
+ addBundle(identifier: string | {
1032
+ id: string;
1033
+ } | {
1034
+ slug: string;
1035
+ }, quantity?: number): Promise<SdkResult<Cart>>;
772
1036
  /** Update quantity of a bundle already in the cart */
773
1037
  updateBundleQuantity(bundleId: string, quantity: number): Promise<SdkResult<Cart>>;
774
1038
  /** Remove a bundle from the cart */
@@ -861,8 +1125,14 @@ declare class ReviewsModule {
861
1125
  declare class ReturnsModule {
862
1126
  private client;
863
1127
  constructor(client: BehioStorefront);
1128
+ /**
1129
+ * Guest order lookup for the EU withdrawal form: order number + the email
1130
+ * used on the order resolve to the order id and per-item returnable
1131
+ * quantities. POST so the email never appears in a URL.
1132
+ */
1133
+ lookupOrder(orderNumber: string, email: string): Promise<SdkResult<ReturnableOrder>>;
864
1134
  submit(input: SubmitReturnInput): Promise<SdkResult<ReturnRequest>>;
865
- getStatus(returnId: string, email: string): Promise<SdkResult<ReturnRequest>>;
1135
+ getStatus(returnId: string, email: string): Promise<SdkResult<ReturnStatus>>;
866
1136
  }
867
1137
  declare class ConsentModule {
868
1138
  private client;
@@ -910,5 +1180,46 @@ declare class AddressModule {
910
1180
  /** Get full structured address from a suggestion's placeId */
911
1181
  getDetail(placeId: string): Promise<SdkResult<AddressDetail>>;
912
1182
  }
1183
+ /**
1184
+ * Storefront shipping module — list configured methods and fetch live
1185
+ * quotes for a destination + cart. Use `listMethods` for the always-on
1186
+ * picker (sidebar, info page) and `quote` once the customer enters a
1187
+ * destination address so live-quote providers (Zaslat etc.) can return
1188
+ * destination-specific prices.
1189
+ */
1190
+ declare class ShippingModule {
1191
+ private client;
1192
+ constructor(client: BehioStorefront);
1193
+ /**
1194
+ * Return the configured shipping methods that pass the current
1195
+ * currency + country filter. Fixed-price methods come back with
1196
+ * their `pricing[]` row resolved; live-quote methods come back with
1197
+ * `price` 0 here — call `quote()` to get the real live price.
1198
+ */
1199
+ listMethods(opts?: {
1200
+ currency?: string;
1201
+ country?: string;
1202
+ cartTotal?: number;
1203
+ cartWeightKg?: number;
1204
+ }): Promise<SdkResult<{
1205
+ items: ShippingMethodSummary[];
1206
+ }>>;
1207
+ /**
1208
+ * Quote shipping for a destination address + cart contents. Each
1209
+ * configured method is evaluated:
1210
+ * - `priceStrategy="fixed"` → resolved from the merchant's per-currency
1211
+ * `pricing[]` rows + free-shipping threshold check.
1212
+ * - `priceStrategy="live_quote"` → dispatched to the upstream
1213
+ * meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
1214
+ * through the merchant's markup/rounding rules.
1215
+ *
1216
+ * Filter `available: true` for the checkout picker; `available: false`
1217
+ * rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
1218
+ * …) you can log but should not display.
1219
+ */
1220
+ quote(input: ShippingQuoteInput): Promise<SdkResult<{
1221
+ items: ShippingQuote[];
1222
+ }>>;
1223
+ }
913
1224
 
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 };
1225
+ 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 ProductMedia, type ProductMediaVariant, 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 ReturnRequestItem, type ReturnStatus, type ReturnStatusItem, type ReturnableOrder, type ReturnableOrderItem, 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 };