@behio/storefront-sdk 1.9.0 → 1.17.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 +11 -9
- package/dist/{chunk-CY6D2YEI.js → chunk-FMMFIZHA.js} +173 -26
- package/dist/{chunk-XMKY4RI5.mjs → chunk-PU4QV6JS.mjs} +148 -1
- package/dist/{client-C635vSzU.d.mts → client-yo90ANGT.d.mts} +395 -4
- package/dist/{client-C635vSzU.d.ts → client-yo90ANGT.d.ts} +395 -4
- package/dist/index.d.mts +44 -3
- package/dist/index.d.ts +44 -3
- package/dist/index.js +743 -2
- package/dist/index.mjs +742 -1
- package/dist/next.d.mts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +2 -2
- package/dist/next.mjs +1 -1
- package/dist/react.d.mts +430 -10
- package/dist/react.d.ts +430 -10
- package/dist/react.js +333 -52
- package/dist/react.mjs +320 -44
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless e-commerce SDK for building custom storefronts.**
|
|
4
4
|
|
|
5
|
-
Behio gives you a complete e-commerce backend
|
|
5
|
+
Behio gives you a complete e-commerce backend (products, inventory, orders, customers, discounts, multi-currency, multi-language) and lets you design the storefront however you want. Merchants run on it worldwide, with local tax rules, carriers and payment gateways handled natively. No themes, no templates, no vendor lock-in.
|
|
6
6
|
|
|
7
7
|
## Why Behio?
|
|
8
8
|
|
|
9
|
-
- **You own the frontend.** Next.js, React, Vue, Nuxt, Astro, or plain JS
|
|
9
|
+
- **You own the frontend.** Next.js, React, Vue, Nuxt, Astro, or plain JS, the backend doesn't care.
|
|
10
10
|
- **Production-ready in minutes.** Catalog, cart, checkout, customer accounts, orders, CMS, discount codes, gift cards, loyalty programs, and more.
|
|
11
11
|
- **Built for developers.** Full TypeScript types, auto-completing, modern React hooks with TanStack Query.
|
|
12
12
|
- **Scale-ready.** Redis caching, rate limiting, webhooks, atomic checkout (no double-spend, no overselling).
|
|
@@ -60,7 +60,7 @@ function ProductList() {
|
|
|
60
60
|
|
|
61
61
|
return data.items.map(p => (
|
|
62
62
|
<div key={p.id}>
|
|
63
|
-
<h3>{p.name}
|
|
63
|
+
<h3>{p.name}, {p.price} {p.currency}</h3>
|
|
64
64
|
<button onClick={() => addToCart.mutateAsync({ productId: p.id, quantity: 1 })}>
|
|
65
65
|
Add to Cart
|
|
66
66
|
</button>
|
|
@@ -89,18 +89,20 @@ function ProductList() {
|
|
|
89
89
|
| `consent` | Cookie consent (GDPR) |
|
|
90
90
|
| `quotes` | B2B quote requests |
|
|
91
91
|
| `pages` | CMS pages |
|
|
92
|
+
| `blog` | Blogs and published posts (web + e-shop) |
|
|
93
|
+
| `forms` | Merchant-defined forms: definition + validated submit with per-field errors (web + e-shop) |
|
|
92
94
|
|
|
93
95
|
### React Hooks (30+)
|
|
94
96
|
|
|
95
|
-
`useProducts` · `useProduct` · `useCategories` · `useCategoryProducts` · `useFeaturedProducts` · `useLabels` · `useProductSearch` · `useFilters` · `useBundles` · `useBundle` · `useCrossSell` · `useProductPromotions` · `useGiftCardBalance` · `useCart` · `useAddToCart` · `useUpdateCartItem` · `useRemoveCartItem` · `useCheckout` · `useOrders` · `useOrder` · `useOrderTracking` · `useCustomerProfile` · `useAddresses` · `useAddressAutocomplete` · `useWishlist` · `useProductReviews` · `useSubmitReview` · `useShopInfo` · `useShopSeo` · `useCartCount`
|
|
97
|
+
`useProducts` · `useProduct` · `useCategories` · `useCategoryProducts` · `useFeaturedProducts` · `useLabels` · `useProductSearch` · `useFilters` · `useBundles` · `useBundle` · `useCrossSell` · `useProductPromotions` · `useGiftCardBalance` · `useCart` · `useAddToCart` · `useUpdateCartItem` · `useRemoveCartItem` · `useCheckout` · `useOrders` · `useOrder` · `useOrderTracking` · `useCustomerProfile` · `useAddresses` · `useAddressAutocomplete` · `useWishlist` · `useProductReviews` · `useSubmitReview` · `useShopInfo` · `useShopSeo` · `useCartCount` · `useBlogs` · `useBlogPosts` · `useBlogPost` · `useSiteForm` · `useSiteFormSubmit`
|
|
96
98
|
|
|
97
99
|
### Framework Support
|
|
98
100
|
|
|
99
|
-
- **Next.js
|
|
100
|
-
- **React + Vite
|
|
101
|
-
- **Nuxt 3
|
|
102
|
-
- **Vue + Vite
|
|
103
|
-
- **Vanilla JS
|
|
101
|
+
- **Next.js**: Server components + client hooks, SSR ready
|
|
102
|
+
- **React + Vite**: Standard SPA setup
|
|
103
|
+
- **Nuxt 3**: Composables with SSR
|
|
104
|
+
- **Vue + Vite**: Provide/inject pattern
|
|
105
|
+
- **Vanilla JS**: Works in any runtime (Node.js, Deno, Bun, Cloudflare Workers)
|
|
104
106
|
|
|
105
107
|
### Built-in Features
|
|
106
108
|
|
|
@@ -33,6 +33,38 @@ var AddressTypes = {
|
|
|
33
33
|
SHIPPING: "SHIPPING",
|
|
34
34
|
BILLING: "BILLING"
|
|
35
35
|
};
|
|
36
|
+
var BE_CODE_TO_ERROR = {
|
|
37
|
+
"be.storefront.cartIsEmpty": "CART_EMPTY",
|
|
38
|
+
"be.storefront.productNotFound": "PRODUCT_NOT_FOUND",
|
|
39
|
+
"be.storefront.productNotAvailable": "PRODUCT_NOT_FOUND",
|
|
40
|
+
"be.storefront.invalidEmailOrPassword": "INVALID_CREDENTIALS",
|
|
41
|
+
"be.storefront.currentPasswordIncorrect": "INVALID_CREDENTIALS",
|
|
42
|
+
"be.storefront.discountExpired": "DISCOUNT_EXPIRED",
|
|
43
|
+
"be.storefront.discountExpiredOrLimit": "DISCOUNT_EXPIRED",
|
|
44
|
+
"be.storefront.invalidDiscountCode": "INVALID_DISCOUNT",
|
|
45
|
+
"be.storefront.discountInvalid": "INVALID_DISCOUNT",
|
|
46
|
+
"be.storefront.discountInactive": "INVALID_DISCOUNT",
|
|
47
|
+
"be.storefront.discountWrongCurrency": "INVALID_DISCOUNT",
|
|
48
|
+
"be.storefront.discountNotApplicable": "INVALID_DISCOUNT",
|
|
49
|
+
"be.storefront.discountNotYetValid": "INVALID_DISCOUNT",
|
|
50
|
+
"be.storefront.discountUsageLimitReached": "INVALID_DISCOUNT",
|
|
51
|
+
"be.storefront.discountLimitedToGroups": "INVALID_DISCOUNT",
|
|
52
|
+
"be.storefront.tokenInvalid": "TOKEN_INVALID",
|
|
53
|
+
"be.storefront.orderAccessTokenInvalid": "TOKEN_INVALID",
|
|
54
|
+
"be.storefront.resetTokenInvalid": "TOKEN_INVALID",
|
|
55
|
+
"be.storefront.invalidVerificationToken": "TOKEN_INVALID",
|
|
56
|
+
"be.storefront.invalidRefreshToken": "TOKEN_INVALID",
|
|
57
|
+
"be.storefront.refreshTokenExpired": "TOKEN_EXPIRED",
|
|
58
|
+
"be.storefront.apiKeyExpired": "TOKEN_EXPIRED",
|
|
59
|
+
"be.storefront.customerEmailTaken": "EMAIL_ALREADY_EXISTS",
|
|
60
|
+
"be.storefront.orderNotCancellable": "ORDER_NOT_CANCELLABLE",
|
|
61
|
+
"be.storefront.orderCancelledNotReturnable": "ORDER_NOT_CANCELLABLE",
|
|
62
|
+
"be.validation.failed": "VALIDATION_ERROR",
|
|
63
|
+
"be.forms.formNotFound": "NOT_FOUND",
|
|
64
|
+
"be.forms.validationFailed": "VALIDATION_ERROR",
|
|
65
|
+
"be.forms.consentRequired": "VALIDATION_ERROR",
|
|
66
|
+
"be.forms.tooLarge": "VALIDATION_ERROR"
|
|
67
|
+
};
|
|
36
68
|
var BehioApiError = class _BehioApiError extends Error {
|
|
37
69
|
constructor(status, body, message) {
|
|
38
70
|
super(message || `API Error ${status}`);
|
|
@@ -49,7 +81,12 @@ var BehioApiError = class _BehioApiError extends Error {
|
|
|
49
81
|
if (status === 409) return "EMAIL_ALREADY_EXISTS";
|
|
50
82
|
if (status === 429) return "RATE_LIMITED";
|
|
51
83
|
if (status >= 500) return "INTERNAL_ERROR";
|
|
52
|
-
const
|
|
84
|
+
const rawCode = _optionalChain([body, 'optionalAccess', _ => _.code]);
|
|
85
|
+
if (typeof rawCode === "string") {
|
|
86
|
+
const mapped = BE_CODE_TO_ERROR[rawCode];
|
|
87
|
+
if (mapped) return mapped;
|
|
88
|
+
}
|
|
89
|
+
const msg = (_optionalChain([body, 'optionalAccess', _2 => _2.message]) || "").toLowerCase();
|
|
53
90
|
if (msg.includes("invalid") && msg.includes("password"))
|
|
54
91
|
return "INVALID_CREDENTIALS";
|
|
55
92
|
if (msg.includes("invalid") && msg.includes("email"))
|
|
@@ -157,6 +194,8 @@ var BehioStorefront = class {
|
|
|
157
194
|
this.orders = new OrdersModule(this);
|
|
158
195
|
this.customer = new CustomerModule(this);
|
|
159
196
|
this.pages = new PagesModule(this);
|
|
197
|
+
this.blog = new BlogModule(this);
|
|
198
|
+
this.forms = new FormsModule(this);
|
|
160
199
|
this.wishlist = new WishlistModule(this);
|
|
161
200
|
this.reviews = new ReviewsModule(this);
|
|
162
201
|
this.returns = new ReturnsModule(this);
|
|
@@ -317,12 +356,12 @@ var BehioStorefront = class {
|
|
|
317
356
|
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
318
357
|
this.listeners.get(event).add(handler);
|
|
319
358
|
return () => {
|
|
320
|
-
_optionalChain([this, 'access',
|
|
359
|
+
_optionalChain([this, 'access', _3 => _3.listeners, 'access', _4 => _4.get, 'call', _5 => _5(event), 'optionalAccess', _6 => _6.delete, 'call', _7 => _7(handler)]);
|
|
321
360
|
};
|
|
322
361
|
}
|
|
323
362
|
/** @internal Emit an event (fire-and-forget, handler errors are swallowed) */
|
|
324
363
|
emit(event, data) {
|
|
325
|
-
_optionalChain([this, 'access',
|
|
364
|
+
_optionalChain([this, 'access', _8 => _8.listeners, 'access', _9 => _9.get, 'call', _10 => _10(event), 'optionalAccess', _11 => _11.forEach, 'call', _12 => _12((fn) => {
|
|
326
365
|
try {
|
|
327
366
|
fn(data);
|
|
328
367
|
} catch (e2) {
|
|
@@ -438,7 +477,7 @@ var BehioStorefront = class {
|
|
|
438
477
|
*/
|
|
439
478
|
async rawRequest(method, path, options) {
|
|
440
479
|
const params = new URLSearchParams();
|
|
441
|
-
if (_optionalChain([options, 'optionalAccess',
|
|
480
|
+
if (_optionalChain([options, 'optionalAccess', _13 => _13.query])) {
|
|
442
481
|
for (const [key, value] of Object.entries(options.query)) {
|
|
443
482
|
if (value === void 0 || value === null || value === "") continue;
|
|
444
483
|
if (Array.isArray(value)) {
|
|
@@ -467,7 +506,7 @@ var BehioStorefront = class {
|
|
|
467
506
|
if (this.shopDomain) {
|
|
468
507
|
headers["X-Shop-Domain"] = this.shopDomain;
|
|
469
508
|
}
|
|
470
|
-
if (this.accessToken && _optionalChain([options, 'optionalAccess',
|
|
509
|
+
if (this.accessToken && _optionalChain([options, 'optionalAccess', _14 => _14.auth]) !== false) {
|
|
471
510
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
472
511
|
}
|
|
473
512
|
if (this.cartSession) {
|
|
@@ -483,10 +522,10 @@ var BehioStorefront = class {
|
|
|
483
522
|
} catch (e4) {
|
|
484
523
|
}
|
|
485
524
|
}
|
|
486
|
-
if (_optionalChain([options, 'optionalAccess',
|
|
525
|
+
if (_optionalChain([options, 'optionalAccess', _15 => _15.headers])) {
|
|
487
526
|
Object.assign(headers, options.headers);
|
|
488
527
|
}
|
|
489
|
-
const bodyStr = _optionalChain([options, 'optionalAccess',
|
|
528
|
+
const bodyStr = _optionalChain([options, 'optionalAccess', _16 => _16.body]) ? JSON.stringify(options.body) : void 0;
|
|
490
529
|
let interceptedConfig = {
|
|
491
530
|
url,
|
|
492
531
|
method,
|
|
@@ -500,7 +539,7 @@ var BehioStorefront = class {
|
|
|
500
539
|
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
501
540
|
const controller = new AbortController();
|
|
502
541
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
503
|
-
if (_optionalChain([options, 'optionalAccess',
|
|
542
|
+
if (_optionalChain([options, 'optionalAccess', _17 => _17.signal])) {
|
|
504
543
|
if (options.signal.aborted) {
|
|
505
544
|
clearTimeout(timeoutId);
|
|
506
545
|
throw new BehioNetworkError("Request aborted", false);
|
|
@@ -551,9 +590,9 @@ var BehioStorefront = class {
|
|
|
551
590
|
const apiError = new BehioApiError(
|
|
552
591
|
res.status,
|
|
553
592
|
body,
|
|
554
|
-
_optionalChain([body, 'optionalAccess',
|
|
593
|
+
_optionalChain([body, 'optionalAccess', _18 => _18.message]) || `API Error ${res.status}`
|
|
555
594
|
);
|
|
556
|
-
if (res.status === 401 && this.refreshToken && _optionalChain([options, 'optionalAccess',
|
|
595
|
+
if (res.status === 401 && this.refreshToken && _optionalChain([options, 'optionalAccess', _19 => _19.auth]) !== false && !_optionalChain([options, 'optionalAccess', _20 => _20._isRetryAfterRefresh])) {
|
|
557
596
|
try {
|
|
558
597
|
await this.handleTokenRefresh();
|
|
559
598
|
return this.rawRequest(method, path, {
|
|
@@ -637,7 +676,7 @@ var CatalogModule = class {
|
|
|
637
676
|
"GET",
|
|
638
677
|
`/catalog/products/${slug}`,
|
|
639
678
|
{
|
|
640
|
-
query: { locale: _optionalChain([options, 'optionalAccess',
|
|
679
|
+
query: { locale: _optionalChain([options, 'optionalAccess', _21 => _21.locale]), currency: _optionalChain([options, 'optionalAccess', _22 => _22.currency]) }
|
|
641
680
|
}
|
|
642
681
|
);
|
|
643
682
|
}
|
|
@@ -691,7 +730,7 @@ var CatalogModule = class {
|
|
|
691
730
|
"GET",
|
|
692
731
|
`/catalog/menu/${encodeURIComponent(handle)}`,
|
|
693
732
|
{
|
|
694
|
-
query: { locale: _optionalChain([options, 'optionalAccess',
|
|
733
|
+
query: { locale: _optionalChain([options, 'optionalAccess', _23 => _23.locale]) }
|
|
695
734
|
}
|
|
696
735
|
);
|
|
697
736
|
}
|
|
@@ -707,7 +746,7 @@ var CatalogModule = class {
|
|
|
707
746
|
"GET",
|
|
708
747
|
"/catalog/featured",
|
|
709
748
|
{
|
|
710
|
-
query: { locale: _optionalChain([options, 'optionalAccess',
|
|
749
|
+
query: { locale: _optionalChain([options, 'optionalAccess', _24 => _24.locale]), currency: _optionalChain([options, 'optionalAccess', _25 => _25.currency]) }
|
|
711
750
|
}
|
|
712
751
|
);
|
|
713
752
|
}
|
|
@@ -722,7 +761,7 @@ var CatalogModule = class {
|
|
|
722
761
|
return this.client.request(
|
|
723
762
|
"GET",
|
|
724
763
|
"/catalog/filters",
|
|
725
|
-
{ query: { locale: _optionalChain([options, 'optionalAccess',
|
|
764
|
+
{ query: { locale: _optionalChain([options, 'optionalAccess', _26 => _26.locale]) } }
|
|
726
765
|
);
|
|
727
766
|
}
|
|
728
767
|
/**
|
|
@@ -737,7 +776,7 @@ var CatalogModule = class {
|
|
|
737
776
|
return this.client.request(
|
|
738
777
|
"GET",
|
|
739
778
|
`/catalog/products/${slug}/parameters`,
|
|
740
|
-
{ query: { locale: _optionalChain([options, 'optionalAccess',
|
|
779
|
+
{ query: { locale: _optionalChain([options, 'optionalAccess', _27 => _27.locale]) } }
|
|
741
780
|
);
|
|
742
781
|
}
|
|
743
782
|
/**
|
|
@@ -749,7 +788,7 @@ var CatalogModule = class {
|
|
|
749
788
|
return this.client.request(
|
|
750
789
|
"GET",
|
|
751
790
|
`/catalog/products/${slug}/parameters/${groupSlug}`,
|
|
752
|
-
{ query: { locale: _optionalChain([options, 'optionalAccess',
|
|
791
|
+
{ query: { locale: _optionalChain([options, 'optionalAccess', _28 => _28.locale]) } }
|
|
753
792
|
);
|
|
754
793
|
}
|
|
755
794
|
/**
|
|
@@ -807,7 +846,7 @@ var CatalogModule = class {
|
|
|
807
846
|
"GET",
|
|
808
847
|
`/catalog/product-groups/${encodeURIComponent(slug)}`,
|
|
809
848
|
{
|
|
810
|
-
query: { locale: _optionalChain([options, 'optionalAccess',
|
|
849
|
+
query: { locale: _optionalChain([options, 'optionalAccess', _29 => _29.locale]), currency: _optionalChain([options, 'optionalAccess', _30 => _30.currency]) }
|
|
811
850
|
}
|
|
812
851
|
);
|
|
813
852
|
}
|
|
@@ -822,7 +861,7 @@ var CatalogModule = class {
|
|
|
822
861
|
return this.client.request(
|
|
823
862
|
"GET",
|
|
824
863
|
`/catalog/products/${encodeURIComponent(productSlug)}/cross-sell`,
|
|
825
|
-
{ query: { locale: _optionalChain([options, 'optionalAccess',
|
|
864
|
+
{ query: { locale: _optionalChain([options, 'optionalAccess', _31 => _31.locale]), currency: _optionalChain([options, 'optionalAccess', _32 => _32.currency]) } }
|
|
826
865
|
);
|
|
827
866
|
}
|
|
828
867
|
/** Active promotions applicable to a product (with countdown end time) */
|
|
@@ -859,16 +898,27 @@ var CatalogModule = class {
|
|
|
859
898
|
{ body: input }
|
|
860
899
|
);
|
|
861
900
|
}
|
|
862
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* List configured payment methods (filtered by currency). `locale` picks the
|
|
903
|
+
* language of `instruments[].label` / `swifts[].label` (SDK 1.14.0); pass the
|
|
904
|
+
* locale of the page so the instrument tiles read in the shopper's language.
|
|
905
|
+
*/
|
|
863
906
|
async listPaymentMethods(opts) {
|
|
864
907
|
const query = {};
|
|
865
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
908
|
+
if (_optionalChain([opts, 'optionalAccess', _33 => _33.currency])) query.currency = opts.currency;
|
|
909
|
+
if (_optionalChain([opts, 'optionalAccess', _34 => _34.locale])) query.locale = opts.locale;
|
|
910
|
+
if (_optionalChain([opts, 'optionalAccess', _35 => _35.country])) query.country = opts.country;
|
|
911
|
+
if (_optionalChain([opts, 'optionalAccess', _36 => _36.shippingMethodId])) query.shippingMethodId = opts.shippingMethodId;
|
|
866
912
|
return this.client.request(
|
|
867
913
|
"GET",
|
|
868
914
|
"/catalog/payment-methods",
|
|
869
915
|
{ query }
|
|
870
916
|
);
|
|
871
917
|
}
|
|
918
|
+
/** Alias of `listPaymentMethods` (SDK 1.14.0). */
|
|
919
|
+
paymentMethods(opts) {
|
|
920
|
+
return this.listPaymentMethods(opts);
|
|
921
|
+
}
|
|
872
922
|
};
|
|
873
923
|
var AuthModule = class {
|
|
874
924
|
constructor(client) {
|
|
@@ -1022,6 +1072,26 @@ var CartModule = class {
|
|
|
1022
1072
|
this.client.emit("cart:updated", res.data);
|
|
1023
1073
|
return res;
|
|
1024
1074
|
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Tell the cart where the order will ship (and, for B2B, the buyer's VAT
|
|
1077
|
+
* ID) so the VAT breakdown matches the checkout before the address form:
|
|
1078
|
+
* destination-country rate (OSS), 0 % export outside the EU, or reverse
|
|
1079
|
+
* charge for an EU business with a VIES-valid VAT ID. `cart.vatMode` says
|
|
1080
|
+
* which rule applied. SDK 1.17.0.
|
|
1081
|
+
*
|
|
1082
|
+
* ```ts
|
|
1083
|
+
* await client.cart.setDestination({ country: "SK" });
|
|
1084
|
+
* await client.cart.setDestination({ vatId: "SK2020000001" }); // "" clears
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
async setDestination(input) {
|
|
1088
|
+
const res = await this.client.request("PUT", "/cart/destination", {
|
|
1089
|
+
body: input
|
|
1090
|
+
});
|
|
1091
|
+
if (res.error) return res;
|
|
1092
|
+
this.client.emit("cart:updated", res.data);
|
|
1093
|
+
return res;
|
|
1094
|
+
}
|
|
1025
1095
|
/** Update item quantity */
|
|
1026
1096
|
async updateQuantity(itemId, quantity) {
|
|
1027
1097
|
const res = await this.client.request(
|
|
@@ -1173,13 +1243,37 @@ var OrdersModule = class {
|
|
|
1173
1243
|
constructor(client) {
|
|
1174
1244
|
this.client = client;
|
|
1175
1245
|
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Ask the backend to re-check this order's payment with the gateway.
|
|
1248
|
+
*
|
|
1249
|
+
* Call it on the thank-you page the customer lands on after paying, BEFORE
|
|
1250
|
+
* you read the order. Some gateways (Tatrapay+) have no server-to-server
|
|
1251
|
+
* notification at all, so the customer's return is the only fast way the
|
|
1252
|
+
* payment gets confirmed; for the others it is a safety net for a lost
|
|
1253
|
+
* notification.
|
|
1254
|
+
*
|
|
1255
|
+
* The response is deliberately opaque (`{ok: true}` every time, even for an
|
|
1256
|
+
* order number that does not exist): order numbers are sequential, so
|
|
1257
|
+
* anything else would turn this into a probe for other people's orders.
|
|
1258
|
+
* Read the actual state afterwards through a path that proves entitlement
|
|
1259
|
+
* ({@link get}, {@link track} or the guest access-code flow).
|
|
1260
|
+
*
|
|
1261
|
+
* Never throws for a missing order and never blocks the page: treat a
|
|
1262
|
+
* failure as "not confirmed yet", the backend poller catches up on its own.
|
|
1263
|
+
*/
|
|
1264
|
+
async syncPaymentOnReturn(orderNumber) {
|
|
1265
|
+
return this.client.request(
|
|
1266
|
+
"POST",
|
|
1267
|
+
`/payments/return/${orderNumber}`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1176
1270
|
/** List customer orders (requires auth) */
|
|
1177
1271
|
async list(options) {
|
|
1178
1272
|
return this.client.request(
|
|
1179
1273
|
"GET",
|
|
1180
1274
|
"/orders",
|
|
1181
1275
|
{
|
|
1182
|
-
query: { page: _optionalChain([options, 'optionalAccess',
|
|
1276
|
+
query: { page: _optionalChain([options, 'optionalAccess', _37 => _37.page]), limit: _optionalChain([options, 'optionalAccess', _38 => _38.limit]) }
|
|
1183
1277
|
}
|
|
1184
1278
|
);
|
|
1185
1279
|
}
|
|
@@ -1525,6 +1619,59 @@ var PagesModule = class {
|
|
|
1525
1619
|
});
|
|
1526
1620
|
}
|
|
1527
1621
|
};
|
|
1622
|
+
var BlogModule = class {
|
|
1623
|
+
constructor(client) {
|
|
1624
|
+
this.client = client;
|
|
1625
|
+
}
|
|
1626
|
+
/** List the site's blogs (active only). */
|
|
1627
|
+
async list(locale) {
|
|
1628
|
+
return this.client.request("GET", "/blogs", {
|
|
1629
|
+
query: { locale }
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
/** Published posts of one blog, newest first (featured first), paginated. */
|
|
1633
|
+
async posts(handle, query = {}) {
|
|
1634
|
+
return this.client.request("GET", `/blogs/${handle}/posts`, {
|
|
1635
|
+
query: {
|
|
1636
|
+
locale: query.locale,
|
|
1637
|
+
page: query.page,
|
|
1638
|
+
limit: query.limit,
|
|
1639
|
+
tag: query.tag
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/** One published post with sanitised HTML content and related posts. */
|
|
1644
|
+
async post(handle, slug, locale) {
|
|
1645
|
+
return this.client.request("GET", `/blogs/${handle}/posts/${slug}`, {
|
|
1646
|
+
query: { locale }
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
var FormsModule = class {
|
|
1651
|
+
constructor(client) {
|
|
1652
|
+
this.client = client;
|
|
1653
|
+
}
|
|
1654
|
+
/** Public definition of one form (fields + settings). 404 for an unknown or inactive slug. */
|
|
1655
|
+
async get(slug) {
|
|
1656
|
+
return this.client.request(
|
|
1657
|
+
"GET",
|
|
1658
|
+
`/forms/${encodeURIComponent(slug)}`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Submit a response. On `be.forms.validationFailed` the returned error
|
|
1663
|
+
* carries per-field codes: read them with `formFieldErrors(error)`. Other
|
|
1664
|
+
* rejections: `be.forms.consentRequired`, `be.forms.tooLarge`,
|
|
1665
|
+
* `be.forms.formNotFound`; rate limit 10 submits per minute per visitor.
|
|
1666
|
+
*/
|
|
1667
|
+
async submit(slug, input) {
|
|
1668
|
+
return this.client.request(
|
|
1669
|
+
"POST",
|
|
1670
|
+
`/forms/${encodeURIComponent(slug)}/submit`,
|
|
1671
|
+
{ body: input, auth: false }
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1528
1675
|
var WishlistModule = class {
|
|
1529
1676
|
constructor(client) {
|
|
1530
1677
|
this.client = client;
|
|
@@ -1674,7 +1821,7 @@ var ConsentModule = class {
|
|
|
1674
1821
|
}
|
|
1675
1822
|
if (result.error) return { data: null, error: result.error };
|
|
1676
1823
|
return {
|
|
1677
|
-
data: _optionalChain([result, 'access',
|
|
1824
|
+
data: _optionalChain([result, 'access', _39 => _39.data, 'optionalAccess', _40 => _40.consented]) ? result.data.consent : null,
|
|
1678
1825
|
error: null
|
|
1679
1826
|
};
|
|
1680
1827
|
}
|
|
@@ -1761,10 +1908,10 @@ var ShippingModule = class {
|
|
|
1761
1908
|
*/
|
|
1762
1909
|
async listMethods(opts) {
|
|
1763
1910
|
const query = {};
|
|
1764
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1765
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1766
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1767
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1911
|
+
if (_optionalChain([opts, 'optionalAccess', _41 => _41.currency])) query.currency = opts.currency;
|
|
1912
|
+
if (_optionalChain([opts, 'optionalAccess', _42 => _42.country])) query.country = opts.country;
|
|
1913
|
+
if (_optionalChain([opts, 'optionalAccess', _43 => _43.cartTotal]) != null) query.cartTotal = String(opts.cartTotal);
|
|
1914
|
+
if (_optionalChain([opts, 'optionalAccess', _44 => _44.cartWeightKg]) != null)
|
|
1768
1915
|
query.cartWeightKg = String(opts.cartWeightKg);
|
|
1769
1916
|
return this.client.request(
|
|
1770
1917
|
"GET",
|
|
@@ -33,6 +33,38 @@ var AddressTypes = {
|
|
|
33
33
|
SHIPPING: "SHIPPING",
|
|
34
34
|
BILLING: "BILLING"
|
|
35
35
|
};
|
|
36
|
+
var BE_CODE_TO_ERROR = {
|
|
37
|
+
"be.storefront.cartIsEmpty": "CART_EMPTY",
|
|
38
|
+
"be.storefront.productNotFound": "PRODUCT_NOT_FOUND",
|
|
39
|
+
"be.storefront.productNotAvailable": "PRODUCT_NOT_FOUND",
|
|
40
|
+
"be.storefront.invalidEmailOrPassword": "INVALID_CREDENTIALS",
|
|
41
|
+
"be.storefront.currentPasswordIncorrect": "INVALID_CREDENTIALS",
|
|
42
|
+
"be.storefront.discountExpired": "DISCOUNT_EXPIRED",
|
|
43
|
+
"be.storefront.discountExpiredOrLimit": "DISCOUNT_EXPIRED",
|
|
44
|
+
"be.storefront.invalidDiscountCode": "INVALID_DISCOUNT",
|
|
45
|
+
"be.storefront.discountInvalid": "INVALID_DISCOUNT",
|
|
46
|
+
"be.storefront.discountInactive": "INVALID_DISCOUNT",
|
|
47
|
+
"be.storefront.discountWrongCurrency": "INVALID_DISCOUNT",
|
|
48
|
+
"be.storefront.discountNotApplicable": "INVALID_DISCOUNT",
|
|
49
|
+
"be.storefront.discountNotYetValid": "INVALID_DISCOUNT",
|
|
50
|
+
"be.storefront.discountUsageLimitReached": "INVALID_DISCOUNT",
|
|
51
|
+
"be.storefront.discountLimitedToGroups": "INVALID_DISCOUNT",
|
|
52
|
+
"be.storefront.tokenInvalid": "TOKEN_INVALID",
|
|
53
|
+
"be.storefront.orderAccessTokenInvalid": "TOKEN_INVALID",
|
|
54
|
+
"be.storefront.resetTokenInvalid": "TOKEN_INVALID",
|
|
55
|
+
"be.storefront.invalidVerificationToken": "TOKEN_INVALID",
|
|
56
|
+
"be.storefront.invalidRefreshToken": "TOKEN_INVALID",
|
|
57
|
+
"be.storefront.refreshTokenExpired": "TOKEN_EXPIRED",
|
|
58
|
+
"be.storefront.apiKeyExpired": "TOKEN_EXPIRED",
|
|
59
|
+
"be.storefront.customerEmailTaken": "EMAIL_ALREADY_EXISTS",
|
|
60
|
+
"be.storefront.orderNotCancellable": "ORDER_NOT_CANCELLABLE",
|
|
61
|
+
"be.storefront.orderCancelledNotReturnable": "ORDER_NOT_CANCELLABLE",
|
|
62
|
+
"be.validation.failed": "VALIDATION_ERROR",
|
|
63
|
+
"be.forms.formNotFound": "NOT_FOUND",
|
|
64
|
+
"be.forms.validationFailed": "VALIDATION_ERROR",
|
|
65
|
+
"be.forms.consentRequired": "VALIDATION_ERROR",
|
|
66
|
+
"be.forms.tooLarge": "VALIDATION_ERROR"
|
|
67
|
+
};
|
|
36
68
|
var BehioApiError = class _BehioApiError extends Error {
|
|
37
69
|
constructor(status, body, message) {
|
|
38
70
|
super(message || `API Error ${status}`);
|
|
@@ -49,6 +81,11 @@ var BehioApiError = class _BehioApiError extends Error {
|
|
|
49
81
|
if (status === 409) return "EMAIL_ALREADY_EXISTS";
|
|
50
82
|
if (status === 429) return "RATE_LIMITED";
|
|
51
83
|
if (status >= 500) return "INTERNAL_ERROR";
|
|
84
|
+
const rawCode = body?.code;
|
|
85
|
+
if (typeof rawCode === "string") {
|
|
86
|
+
const mapped = BE_CODE_TO_ERROR[rawCode];
|
|
87
|
+
if (mapped) return mapped;
|
|
88
|
+
}
|
|
52
89
|
const msg = (body?.message || "").toLowerCase();
|
|
53
90
|
if (msg.includes("invalid") && msg.includes("password"))
|
|
54
91
|
return "INVALID_CREDENTIALS";
|
|
@@ -157,6 +194,8 @@ var BehioStorefront = class {
|
|
|
157
194
|
this.orders = new OrdersModule(this);
|
|
158
195
|
this.customer = new CustomerModule(this);
|
|
159
196
|
this.pages = new PagesModule(this);
|
|
197
|
+
this.blog = new BlogModule(this);
|
|
198
|
+
this.forms = new FormsModule(this);
|
|
160
199
|
this.wishlist = new WishlistModule(this);
|
|
161
200
|
this.reviews = new ReviewsModule(this);
|
|
162
201
|
this.returns = new ReturnsModule(this);
|
|
@@ -859,16 +898,27 @@ var CatalogModule = class {
|
|
|
859
898
|
{ body: input }
|
|
860
899
|
);
|
|
861
900
|
}
|
|
862
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* List configured payment methods (filtered by currency). `locale` picks the
|
|
903
|
+
* language of `instruments[].label` / `swifts[].label` (SDK 1.14.0); pass the
|
|
904
|
+
* locale of the page so the instrument tiles read in the shopper's language.
|
|
905
|
+
*/
|
|
863
906
|
async listPaymentMethods(opts) {
|
|
864
907
|
const query = {};
|
|
865
908
|
if (opts?.currency) query.currency = opts.currency;
|
|
909
|
+
if (opts?.locale) query.locale = opts.locale;
|
|
910
|
+
if (opts?.country) query.country = opts.country;
|
|
911
|
+
if (opts?.shippingMethodId) query.shippingMethodId = opts.shippingMethodId;
|
|
866
912
|
return this.client.request(
|
|
867
913
|
"GET",
|
|
868
914
|
"/catalog/payment-methods",
|
|
869
915
|
{ query }
|
|
870
916
|
);
|
|
871
917
|
}
|
|
918
|
+
/** Alias of `listPaymentMethods` (SDK 1.14.0). */
|
|
919
|
+
paymentMethods(opts) {
|
|
920
|
+
return this.listPaymentMethods(opts);
|
|
921
|
+
}
|
|
872
922
|
};
|
|
873
923
|
var AuthModule = class {
|
|
874
924
|
constructor(client) {
|
|
@@ -1022,6 +1072,26 @@ var CartModule = class {
|
|
|
1022
1072
|
this.client.emit("cart:updated", res.data);
|
|
1023
1073
|
return res;
|
|
1024
1074
|
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Tell the cart where the order will ship (and, for B2B, the buyer's VAT
|
|
1077
|
+
* ID) so the VAT breakdown matches the checkout before the address form:
|
|
1078
|
+
* destination-country rate (OSS), 0 % export outside the EU, or reverse
|
|
1079
|
+
* charge for an EU business with a VIES-valid VAT ID. `cart.vatMode` says
|
|
1080
|
+
* which rule applied. SDK 1.17.0.
|
|
1081
|
+
*
|
|
1082
|
+
* ```ts
|
|
1083
|
+
* await client.cart.setDestination({ country: "SK" });
|
|
1084
|
+
* await client.cart.setDestination({ vatId: "SK2020000001" }); // "" clears
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
async setDestination(input) {
|
|
1088
|
+
const res = await this.client.request("PUT", "/cart/destination", {
|
|
1089
|
+
body: input
|
|
1090
|
+
});
|
|
1091
|
+
if (res.error) return res;
|
|
1092
|
+
this.client.emit("cart:updated", res.data);
|
|
1093
|
+
return res;
|
|
1094
|
+
}
|
|
1025
1095
|
/** Update item quantity */
|
|
1026
1096
|
async updateQuantity(itemId, quantity) {
|
|
1027
1097
|
const res = await this.client.request(
|
|
@@ -1173,6 +1243,30 @@ var OrdersModule = class {
|
|
|
1173
1243
|
constructor(client) {
|
|
1174
1244
|
this.client = client;
|
|
1175
1245
|
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Ask the backend to re-check this order's payment with the gateway.
|
|
1248
|
+
*
|
|
1249
|
+
* Call it on the thank-you page the customer lands on after paying, BEFORE
|
|
1250
|
+
* you read the order. Some gateways (Tatrapay+) have no server-to-server
|
|
1251
|
+
* notification at all, so the customer's return is the only fast way the
|
|
1252
|
+
* payment gets confirmed; for the others it is a safety net for a lost
|
|
1253
|
+
* notification.
|
|
1254
|
+
*
|
|
1255
|
+
* The response is deliberately opaque (`{ok: true}` every time, even for an
|
|
1256
|
+
* order number that does not exist): order numbers are sequential, so
|
|
1257
|
+
* anything else would turn this into a probe for other people's orders.
|
|
1258
|
+
* Read the actual state afterwards through a path that proves entitlement
|
|
1259
|
+
* ({@link get}, {@link track} or the guest access-code flow).
|
|
1260
|
+
*
|
|
1261
|
+
* Never throws for a missing order and never blocks the page: treat a
|
|
1262
|
+
* failure as "not confirmed yet", the backend poller catches up on its own.
|
|
1263
|
+
*/
|
|
1264
|
+
async syncPaymentOnReturn(orderNumber) {
|
|
1265
|
+
return this.client.request(
|
|
1266
|
+
"POST",
|
|
1267
|
+
`/payments/return/${orderNumber}`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1176
1270
|
/** List customer orders (requires auth) */
|
|
1177
1271
|
async list(options) {
|
|
1178
1272
|
return this.client.request(
|
|
@@ -1525,6 +1619,59 @@ var PagesModule = class {
|
|
|
1525
1619
|
});
|
|
1526
1620
|
}
|
|
1527
1621
|
};
|
|
1622
|
+
var BlogModule = class {
|
|
1623
|
+
constructor(client) {
|
|
1624
|
+
this.client = client;
|
|
1625
|
+
}
|
|
1626
|
+
/** List the site's blogs (active only). */
|
|
1627
|
+
async list(locale) {
|
|
1628
|
+
return this.client.request("GET", "/blogs", {
|
|
1629
|
+
query: { locale }
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
/** Published posts of one blog, newest first (featured first), paginated. */
|
|
1633
|
+
async posts(handle, query = {}) {
|
|
1634
|
+
return this.client.request("GET", `/blogs/${handle}/posts`, {
|
|
1635
|
+
query: {
|
|
1636
|
+
locale: query.locale,
|
|
1637
|
+
page: query.page,
|
|
1638
|
+
limit: query.limit,
|
|
1639
|
+
tag: query.tag
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/** One published post with sanitised HTML content and related posts. */
|
|
1644
|
+
async post(handle, slug, locale) {
|
|
1645
|
+
return this.client.request("GET", `/blogs/${handle}/posts/${slug}`, {
|
|
1646
|
+
query: { locale }
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
var FormsModule = class {
|
|
1651
|
+
constructor(client) {
|
|
1652
|
+
this.client = client;
|
|
1653
|
+
}
|
|
1654
|
+
/** Public definition of one form (fields + settings). 404 for an unknown or inactive slug. */
|
|
1655
|
+
async get(slug) {
|
|
1656
|
+
return this.client.request(
|
|
1657
|
+
"GET",
|
|
1658
|
+
`/forms/${encodeURIComponent(slug)}`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Submit a response. On `be.forms.validationFailed` the returned error
|
|
1663
|
+
* carries per-field codes: read them with `formFieldErrors(error)`. Other
|
|
1664
|
+
* rejections: `be.forms.consentRequired`, `be.forms.tooLarge`,
|
|
1665
|
+
* `be.forms.formNotFound`; rate limit 10 submits per minute per visitor.
|
|
1666
|
+
*/
|
|
1667
|
+
async submit(slug, input) {
|
|
1668
|
+
return this.client.request(
|
|
1669
|
+
"POST",
|
|
1670
|
+
`/forms/${encodeURIComponent(slug)}/submit`,
|
|
1671
|
+
{ body: input, auth: false }
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1528
1675
|
var WishlistModule = class {
|
|
1529
1676
|
constructor(client) {
|
|
1530
1677
|
this.client = client;
|