brainerce 1.23.13 → 1.24.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 +252 -30
- package/dist/index.d.mts +126 -1
- package/dist/index.d.ts +126 -1
- package/dist/index.js +167 -4
- package/dist/index.mjs +167 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,17 @@ Official SDK for building e-commerce storefronts with **Brainerce Platform**.
|
|
|
4
4
|
|
|
5
5
|
This SDK provides a complete solution for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts to connect to Brainerce's unified commerce API.
|
|
6
6
|
|
|
7
|
-
> **AI Agents / Vibe Coders:** Use the MCP server for AI-powered store building: `npx @brainerce/mcp-server`. It provides docs, code templates, and live store capabilities
|
|
7
|
+
> **AI Agents / Vibe Coders (Cursor, Lovable, Claude Code, VS Code):** Use the MCP server for AI-powered store building: `npx @brainerce/mcp-server`. It provides docs, code templates, and live store capabilities directly inside your IDE.
|
|
8
|
+
> Note: the MCP server runs inside your IDE — it is not available in chat-only tools like Google AI Studio or ChatGPT.
|
|
9
|
+
|
|
10
|
+
## Two SDK modes — choose the right one
|
|
11
|
+
|
|
12
|
+
| Mode | Config key | Use for | Where to run |
|
|
13
|
+
| -------------- | ------------------------ | ---------------------------------- | ----------------------------------- |
|
|
14
|
+
| **Storefront** | `salesChannelId: 'vc_*'` | Building the customer-facing store | Browser / client-side |
|
|
15
|
+
| **Admin** | `apiKey: 'brainerce_*'` | Managing products, team, settings | Server only — never in browser code |
|
|
16
|
+
|
|
17
|
+
> **Building a storefront?** You only need your **Sales Channel ID** (`vc_*`) from the Brainerce dashboard under **Sales Channels**. No API key needed. API keys are a server-side admin secret.
|
|
8
18
|
|
|
9
19
|
## Installation
|
|
10
20
|
|
|
@@ -18,27 +28,219 @@ yarn add brainerce
|
|
|
18
28
|
|
|
19
29
|
---
|
|
20
30
|
|
|
31
|
+
## What You Must Build
|
|
32
|
+
|
|
33
|
+
Every Brainerce storefront must include **all mandatory features** below. Features auto-hide when the underlying capability is disabled, so build them all anyway — they'll appear the moment the store owner enables them.
|
|
34
|
+
|
|
35
|
+
| Feature | SDK entry point | Mandatory |
|
|
36
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------- |
|
|
37
|
+
| Product list with search, filter, pagination | `client.getProducts()`, `client.getSearchSuggestions(query)` | ✅ |
|
|
38
|
+
| Product detail with variant picker, stock, price | `client.getProductBySlug()` + helpers | ✅ |
|
|
39
|
+
| Buyer customization fields (engraving, uploads, select) | `product.customizationFields`, `client.uploadCustomizationFile()` | ✅ |
|
|
40
|
+
| Cart (add, update, remove, coupon, totals) | `client.addToCart()`, `getCartTotals(cart)` | ✅ |
|
|
41
|
+
| Inventory reservation countdown | Cart expiry timestamp from `client.getCart()` | ✅ |
|
|
42
|
+
| Full checkout end-to-end with payment | `setShippingAddress → selectShippingMethod → getPaymentProviders → pay → handlePaymentSuccess → waitForOrder` | ✅ |
|
|
43
|
+
| Order confirmation (clear cart + wait for real order) | `client.handlePaymentSuccess()`, `client.waitForOrder()` | ✅ |
|
|
44
|
+
| Register + email verification flow | `client.registerCustomer()`, `client.verifyEmail()` | ✅ |
|
|
45
|
+
| Login + verification branch | `client.loginCustomer()` | ✅ |
|
|
46
|
+
| Forgot / reset password | `client.forgotPassword()`, `client.resetPassword()` | ✅ |
|
|
47
|
+
| OAuth sign-in buttons + callback handler | `client.getAvailableOAuthProviders()` | ✅ |
|
|
48
|
+
| Account area (profile + order history) | `client.getMyProfile()`, `client.getMyOrders()` | ✅ |
|
|
49
|
+
| Global header: cart count + search autocomplete | `client.getCart()`, `client.getSearchSuggestions(query)` | ✅ |
|
|
50
|
+
| Discount banners + product badges | `client.getDiscountBanners()`, `client.getProductDiscountBadge(productId)` | ✅ |
|
|
51
|
+
| Product reviews on PDP + JSON-LD aggregateRating | `client.listProductReviews(id)`, `client.submitProductReview(id, …)` | ✅ |
|
|
52
|
+
| Multi-language + RTL (when i18n enabled) | `client.setLocale()` | conditional |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Critical Rules
|
|
57
|
+
|
|
58
|
+
Violating any of these causes production incidents or broken orders. Read them before writing SDK code.
|
|
59
|
+
|
|
60
|
+
### SDK usage
|
|
61
|
+
|
|
62
|
+
- ALWAYS call SDK client methods. Never reconstruct REST URLs or call `fetch` directly.
|
|
63
|
+
- NEVER invent SDK method names. If it's not in this README or in `get-sdk-docs`, it doesn't exist.
|
|
64
|
+
- NEVER hardcode product data, categories, or store copy — Brainerce is the database.
|
|
65
|
+
- NEVER use `submitGuestOrder()` or `createOrder()` — they bypass payment and produce unpaid orders.
|
|
66
|
+
- ALWAYS use SDK helpers (`getCartTotals`, `formatPrice`, `getProductPriceInfo`, `getCartItemImage`, `getCartItemName`, `getVariantPrice`, `getStockStatus`, `getDescriptionContent`) instead of reading raw fields.
|
|
67
|
+
|
|
68
|
+
### State management
|
|
69
|
+
|
|
70
|
+
- The SDK manages cart, checkout, and session state. Do NOT duplicate it in your own Redux/context.
|
|
71
|
+
- Product lists, categories, and inventory counts are NOT client state — fetch on demand.
|
|
72
|
+
- Discount rules and coupon validity are evaluated server-side. Never re-implement them client-side.
|
|
73
|
+
|
|
74
|
+
### Authentication
|
|
75
|
+
|
|
76
|
+
- ALWAYS handle the `requiresVerification` flag in `registerCustomer` and `loginCustomer` responses. If true, route to the verify-email step BEFORE treating the user as logged in.
|
|
77
|
+
- ALWAYS build the verify-email, forgot-password, and reset-password flows even when the store currently has email verification disabled. They auto-hide when unused.
|
|
78
|
+
- ALWAYS build OAuth button placeholders and a callback handler even when no OAuth provider is configured.
|
|
79
|
+
- NEVER silently swallow auth errors. Render the specific error (invalid credentials, expired token, rate limited).
|
|
80
|
+
|
|
81
|
+
### Checkout & orders
|
|
82
|
+
|
|
83
|
+
- The checkout sequence is strict: `setShippingAddress` → pick a shipping rate → `getPaymentProviders` → provider payment → `handlePaymentSuccess` → `waitForOrder`. Never skip or reorder.
|
|
84
|
+
- ALWAYS call `handlePaymentSuccess(checkoutId)` on the confirmation page — clears the cart so users don't see stale items.
|
|
85
|
+
- ALWAYS call `waitForOrder(checkoutId)` to poll for the real order before showing an order number. The payment callback may return before the order record exists.
|
|
86
|
+
- NEVER use the checkout total as the cart total — they diverge (tax, shipping, discounts). Display `checkout.lineItems` on the summary, not `cart.items`.
|
|
87
|
+
- The reservation timer is a hard guarantee — display the countdown from the cart and let the SDK handle expiry.
|
|
88
|
+
|
|
89
|
+
### Token handling
|
|
90
|
+
|
|
91
|
+
- Customer auth tokens (`result.token` from `loginCustomer`/`registerCustomer`) should be passed to `client.setCustomerToken(token)`. The SDK stores session state internally.
|
|
92
|
+
- NEVER put the admin API key (`brainerce_*`) in client code. It is a server-only secret.
|
|
93
|
+
- OAuth callbacks arrive with the token in URL params. Extract and apply the token before redirecting.
|
|
94
|
+
|
|
95
|
+
### i18n
|
|
96
|
+
|
|
97
|
+
- NEVER hardcode currency, locale, or language strings — read them from `getStoreInfo()`.
|
|
98
|
+
- NEVER format prices with `toFixed(2)` — use `formatPrice()` from the SDK.
|
|
99
|
+
- When i18n is enabled, call `client.setLocale(locale)` at app init and include a language switcher. For RTL locales (`he`, `ar`), set `<html dir="rtl">` — do NOT add `flex-row-reverse` on top.
|
|
100
|
+
|
|
101
|
+
### Type safety
|
|
102
|
+
|
|
103
|
+
- NEVER use `as any` or `as unknown as`. Fix the type, don't hide it.
|
|
104
|
+
- NEVER write your own copies of SDK types (Cart, Product, Order). Import from `'brainerce'`.
|
|
105
|
+
- All prices are **STRINGS** — always `parseFloat()` before math or comparisons.
|
|
106
|
+
- `CartItem` / `CheckoutLineItem` = **NESTED** (`item.product.name`, `item.unitPrice`). `OrderItem` = **FLAT** (`item.name`, `item.price`). Not interchangeable.
|
|
107
|
+
- `Cart` has no `.total` field — call `getCartTotals(cart)`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Business Flows
|
|
112
|
+
|
|
113
|
+
These sequences are non-negotiable. The order of SDK calls matters.
|
|
114
|
+
|
|
115
|
+
### Checkout flow
|
|
116
|
+
|
|
117
|
+
1. Collect customer email, billing address, shipping address (`line1`, `line2`, `city`, `region`, `postalCode`, `country`). `email` is required.
|
|
118
|
+
2. Submit address to get shipping rates:
|
|
119
|
+
```ts
|
|
120
|
+
const { checkout, rates } = await client.setShippingAddress(checkoutId, {
|
|
121
|
+
email,
|
|
122
|
+
firstName,
|
|
123
|
+
lastName,
|
|
124
|
+
line1,
|
|
125
|
+
city,
|
|
126
|
+
region,
|
|
127
|
+
postalCode,
|
|
128
|
+
country,
|
|
129
|
+
});
|
|
130
|
+
// rates = available shipping rates; checkout = updated checkout object
|
|
131
|
+
```
|
|
132
|
+
3. Let the customer pick a rate, then persist it:
|
|
133
|
+
```ts
|
|
134
|
+
await client.selectShippingMethod(checkoutId, rateId);
|
|
135
|
+
```
|
|
136
|
+
4. Fetch available payment providers:
|
|
137
|
+
```ts
|
|
138
|
+
const providers = await client.getPaymentProviders();
|
|
139
|
+
```
|
|
140
|
+
Each provider has a `renderType` — `'sdk-widget'` (Stripe, PayPal, Grow), `'iframe'` (Cardcom), `'redirect'`, `'sandbox'`. Branch on `renderType`, never on provider name.
|
|
141
|
+
5. Confirm payment using the provider's flow (Stripe Elements `stripe.confirmCardPayment`, PayPal button, redirect, etc.).
|
|
142
|
+
6. On the confirmation page, **always call both**:
|
|
143
|
+
```ts
|
|
144
|
+
await client.handlePaymentSuccess(checkoutId); // clears cart
|
|
145
|
+
const order = await client.waitForOrder(checkoutId); // polls until order exists
|
|
146
|
+
```
|
|
147
|
+
7. Display `checkout.lineItems` (not `cart.items`) on the order summary.
|
|
148
|
+
|
|
149
|
+
### Registration flow
|
|
150
|
+
|
|
151
|
+
1. Collect email, password, first name, last name.
|
|
152
|
+
2. Call `registerCustomer`:
|
|
153
|
+
```ts
|
|
154
|
+
const result = await client.registerCustomer({ email, password, firstName, lastName });
|
|
155
|
+
```
|
|
156
|
+
3. Branch on `result.requiresVerification`:
|
|
157
|
+
- `true` → store token temporarily, route to verify-email UI (do NOT set token yet)
|
|
158
|
+
- `false` → `client.setCustomerToken(result.token)`, route to account
|
|
159
|
+
4. On verify-email: collect 6-digit code → `client.verifyEmail(code)`. Offer resend via `client.resendVerificationEmail()`.
|
|
160
|
+
5. After `verifyEmail` resolves: `client.setCustomerToken(result.token)`, route to account.
|
|
161
|
+
|
|
162
|
+
> Build the verify-email step even if verification is currently disabled — it auto-hides.
|
|
163
|
+
|
|
164
|
+
### Login flow
|
|
165
|
+
|
|
166
|
+
1. Collect email + password.
|
|
167
|
+
2. Call `loginCustomer`:
|
|
168
|
+
```ts
|
|
169
|
+
const result = await client.loginCustomer(email, password);
|
|
170
|
+
```
|
|
171
|
+
3. Branch on `result.requiresVerification`:
|
|
172
|
+
- `true` → route to verify-email
|
|
173
|
+
- `false` → `client.setCustomerToken(result.token)`, route to previous page or account
|
|
174
|
+
4. Always offer OAuth buttons from `client.getAvailableOAuthProviders()` — render the region even when empty, it auto-populates when a provider is enabled.
|
|
175
|
+
5. Render specific errors (bad credentials, rate limited, disabled) — never swallow them.
|
|
176
|
+
|
|
177
|
+
### Order confirmation flow
|
|
178
|
+
|
|
179
|
+
1. Read `checkoutId` from URL or session.
|
|
180
|
+
2. `await client.handlePaymentSuccess(checkoutId)` — mandatory, clears cart so purchased items don't show on next visit.
|
|
181
|
+
3. `const order = await client.waitForOrder(checkoutId)` — polls until the webhook writes the order.
|
|
182
|
+
4. Show a spinner during step 3 (webhook may lag). On timeout: show "we're still processing, check your email" with a link to order history — the order WILL appear there.
|
|
183
|
+
5. On success: render order number and line items from the returned `order` object.
|
|
184
|
+
|
|
185
|
+
### Password reset flow
|
|
186
|
+
|
|
187
|
+
**Forgot password step:** collect email → `client.forgotPassword(email)` → always show a generic success message (prevents account enumeration, regardless of whether the account exists).
|
|
188
|
+
|
|
189
|
+
**Reset password step:** read `token` from URL query param → if missing, show error with link back → collect new password → `client.resetPassword(token, newPassword)` → on success route to login → on expired/invalid token show the specific error with link back.
|
|
190
|
+
|
|
191
|
+
### OAuth flow
|
|
192
|
+
|
|
193
|
+
1. Get the list of available provider names:
|
|
194
|
+
```ts
|
|
195
|
+
const { providers } = await client.getAvailableOAuthProviders();
|
|
196
|
+
// providers = ['GOOGLE', 'FACEBOOK', 'GITHUB'] (strings, not objects)
|
|
197
|
+
```
|
|
198
|
+
2. For each provider, get the authorization URL:
|
|
199
|
+
```ts
|
|
200
|
+
const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
|
|
201
|
+
redirectUrl: `${window.location.origin}/auth/callback`,
|
|
202
|
+
});
|
|
203
|
+
window.location.href = authorizationUrl; // full-page redirect, NOT a popup
|
|
204
|
+
```
|
|
205
|
+
3. On callback, the URL contains `token` + `oauth_success` (or `oauth_error`) query params:
|
|
206
|
+
```ts
|
|
207
|
+
const token = new URLSearchParams(location.search).get('token');
|
|
208
|
+
if (token) client.setCustomerToken(token); // then redirect to account
|
|
209
|
+
```
|
|
210
|
+
4. On `oauth_error` query param: redirect to login with an error message.
|
|
211
|
+
|
|
212
|
+
> Build the OAuth button region AND the callback handler even when no providers are configured.
|
|
213
|
+
|
|
214
|
+
### Inventory reservation flow
|
|
215
|
+
|
|
216
|
+
- Display the countdown from `cart.reservation?.expiresAt` — refresh once per second (`reservation` is optional; only present when a reservation strategy is active).
|
|
217
|
+
- On expiry: call `client.getCart()` to refresh. Items whose reservation expired are flagged server-side.
|
|
218
|
+
- On the checkout page: if reservation has expired, block payment and show "your cart has expired" with a link back to cart.
|
|
219
|
+
- Do NOT implement your own timer logic — the SDK is the source of truth.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
21
223
|
## Quick Reference - Helper Functions
|
|
22
224
|
|
|
23
225
|
The SDK exports these utility functions for common UI tasks:
|
|
24
226
|
|
|
25
|
-
| Function | Purpose
|
|
26
|
-
| ---------------------------------------------- |
|
|
27
|
-
| `formatPrice(amount, { currency?, locale? })` | Format prices for display
|
|
28
|
-
| `getPriceDisplay(amount, currency?, locale?)` | Alias for `formatPrice`
|
|
29
|
-
| `getDescriptionContent(product)` | Get product description (HTML or text)
|
|
30
|
-
| `isHtmlDescription(product)` | Check if description is HTML
|
|
31
|
-
| `getStockStatus(inventory)` | Get human-readable stock status
|
|
32
|
-
| `getProductPrice(product)` | Get effective price (handles sales)
|
|
33
|
-
| `getProductPriceInfo(product)` | Get price + sale info + discount %
|
|
34
|
-
| `getVariantPrice(variant, basePrice)` | Get variant price with fallback
|
|
35
|
-
| `getCartTotals(cart, shippingPrice?)` | Calculate cart subtotal/discount/total
|
|
36
|
-
| `getCartItemName(item)` | Get name from nested cart item
|
|
37
|
-
| `getCartItemImage(item)` | Get image URL from cart item
|
|
38
|
-
| `getVariantOptions(variant)` | Get variant attributes as array
|
|
39
|
-
| `isCouponApplicableToProduct(coupon, product)` | Check if coupon applies
|
|
40
|
-
| `isAllowedPaymentUrl(url, options?)` | Validate a payment URL host
|
|
41
|
-
| `safePaymentRedirect(url, options?)` | Validate then `window.location.href`
|
|
227
|
+
| Function | Purpose | Example |
|
|
228
|
+
| ---------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
229
|
+
| `formatPrice(amount, { currency?, locale? })` | Format prices for display | `formatPrice("99.99", { currency: 'USD' })` → `$99.99` |
|
|
230
|
+
| `getPriceDisplay(amount, currency?, locale?)` | Alias for `formatPrice` | Same as above |
|
|
231
|
+
| `getDescriptionContent(product)` | Get product description (HTML or text) | `getDescriptionContent(product)` |
|
|
232
|
+
| `isHtmlDescription(product)` | Check if description is HTML | `isHtmlDescription(product)` → `true/false` |
|
|
233
|
+
| `getStockStatus(inventory)` | Get human-readable stock status | `getStockStatus(inventory)` → `"In Stock"` |
|
|
234
|
+
| `getProductPrice(product)` | Get effective price (handles sales) | `getProductPrice(product)` → `29.99` |
|
|
235
|
+
| `getProductPriceInfo(product)` | Get price + sale info + discount % (falls back to `priceMin` when `basePrice=0` on VARIABLE) | `{ price, isOnSale, discountPercent }` |
|
|
236
|
+
| `getVariantPrice(variant, basePrice)` | Get variant price with fallback | `getVariantPrice(variant, '29.99')` → `34.99` |
|
|
237
|
+
| `getCartTotals(cart, shippingPrice?)` | Calculate cart subtotal/discount/total | `{ subtotal, discount, shipping, total }` |
|
|
238
|
+
| `getCartItemName(item)` | Get name from nested cart item | `getCartItemName(item)` → `"Blue T-Shirt"` |
|
|
239
|
+
| `getCartItemImage(item)` | Get image URL from cart item | `getCartItemImage(item)` → `"https://..."` |
|
|
240
|
+
| `getVariantOptions(variant)` | Get variant attributes as array | `[{ name: "Color", value: "Red" }]` |
|
|
241
|
+
| `isCouponApplicableToProduct(coupon, product)` | Check if coupon applies | `isCouponApplicableToProduct(coupon, product)` |
|
|
242
|
+
| `isAllowedPaymentUrl(url, options?)` | Validate a payment URL host | `isAllowedPaymentUrl(intent.clientSecret)` → `true` |
|
|
243
|
+
| `safePaymentRedirect(url, options?)` | Validate then `window.location.href` | `safePaymentRedirect(intent.clientSecret)` |
|
|
42
244
|
|
|
43
245
|
```typescript
|
|
44
246
|
import {
|
|
@@ -110,9 +312,9 @@ const paypalProvider = providers.find(p => p.provider === 'paypal');
|
|
|
110
312
|
```typescript
|
|
111
313
|
import { BrainerceClient } from 'brainerce';
|
|
112
314
|
|
|
113
|
-
//
|
|
315
|
+
// ✅ salesChannelId (vc_*) is all you need — no API key for storefronts
|
|
114
316
|
const client = new BrainerceClient({
|
|
115
|
-
|
|
317
|
+
salesChannelId: 'vc_YOUR_SALES_CHANNEL_ID', // found in Brainerce dashboard → Sales Channels
|
|
116
318
|
});
|
|
117
319
|
|
|
118
320
|
// Fetch products
|
|
@@ -649,7 +851,7 @@ Create a file `lib/brainerce.ts`:
|
|
|
649
851
|
import { BrainerceClient } from 'brainerce';
|
|
650
852
|
|
|
651
853
|
export const client = new BrainerceClient({
|
|
652
|
-
|
|
854
|
+
salesChannelId: 'vc_YOUR_SALES_CHANNEL_ID', // found in Brainerce dashboard → Sales Channels
|
|
653
855
|
});
|
|
654
856
|
|
|
655
857
|
// ----- Cart Helpers -----
|
|
@@ -1121,6 +1323,9 @@ interface Product {
|
|
|
1121
1323
|
sku: string;
|
|
1122
1324
|
basePrice: number;
|
|
1123
1325
|
salePrice?: number | null;
|
|
1326
|
+
priceMin?: string | null; // Lowest variant price (VARIABLE products only)
|
|
1327
|
+
priceMax?: string | null; // Highest variant price (VARIABLE products only)
|
|
1328
|
+
priceVaries?: boolean; // true when range should be shown ("₪49 – ₪199")
|
|
1124
1329
|
status: 'active' | 'draft';
|
|
1125
1330
|
type: 'SIMPLE' | 'VARIABLE';
|
|
1126
1331
|
images?: ProductImage[];
|
|
@@ -1471,16 +1676,30 @@ console.log(`${count} items in cart`);
|
|
|
1471
1676
|
|
|
1472
1677
|
#### Apply Coupon
|
|
1473
1678
|
|
|
1679
|
+
**On the cart page** (before checkout session is created):
|
|
1680
|
+
|
|
1474
1681
|
```typescript
|
|
1475
1682
|
const cart = await client.smartGetCart();
|
|
1476
1683
|
const updated = await client.applyCoupon(cart.id, 'SAVE20');
|
|
1477
1684
|
console.log(updated.discountAmount); // "10.00"
|
|
1478
1685
|
console.log(updated.couponCode); // "SAVE20"
|
|
1479
1686
|
|
|
1480
|
-
// Remove coupon
|
|
1481
1687
|
await client.removeCoupon(cart.id);
|
|
1482
1688
|
```
|
|
1483
1689
|
|
|
1690
|
+
**On the checkout page** (checkout session already exists — preferred):
|
|
1691
|
+
|
|
1692
|
+
```typescript
|
|
1693
|
+
// applyCheckoutCoupon applies to cart AND updates checkout totals atomically
|
|
1694
|
+
const checkout = await client.applyCheckoutCoupon(checkoutId, 'SAVE20');
|
|
1695
|
+
console.log(checkout.discountAmount); // "10.00"
|
|
1696
|
+
console.log(checkout.total); // updated total
|
|
1697
|
+
|
|
1698
|
+
await client.removeCheckoutCoupon(checkoutId);
|
|
1699
|
+
```
|
|
1700
|
+
|
|
1701
|
+
> **Important:** if a checkout session already exists, always use `applyCheckoutCoupon(checkoutId, code)` — not `applyCoupon(cartId, code)`. Applying to the cart after checkout is created does not update the checkout total, so payment will charge the original amount.
|
|
1702
|
+
|
|
1484
1703
|
#### Cart Totals
|
|
1485
1704
|
|
|
1486
1705
|
```typescript
|
|
@@ -2332,12 +2551,13 @@ function PaymentForm({ checkoutId }: { checkoutId: string }) {
|
|
|
2332
2551
|
}
|
|
2333
2552
|
```
|
|
2334
2553
|
|
|
2335
|
-
#### Complete Order After Payment: `completeGuestCheckout()`
|
|
2554
|
+
#### Complete Order After Payment: `completeGuestCheckout()` (legacy untracked flow only)
|
|
2555
|
+
|
|
2556
|
+
> **Context:** This section describes the **legacy untracked guest checkout** flow where the client must explicitly create the order. In the modern tracked flow (`startGuestCheckout()` → webhook creates the order), you use `handlePaymentSuccess()` + `waitForOrder()` instead (see [Business Flows — Checkout](#checkout-flow) above).
|
|
2336
2557
|
|
|
2337
|
-
**CRITICAL:** After payment succeeds, you MUST call `completeGuestCheckout()` to create the order on the server
|
|
2558
|
+
**CRITICAL (untracked flow only):** After payment succeeds, you MUST call `completeGuestCheckout()` to create the order on the server.
|
|
2338
2559
|
|
|
2339
|
-
> **WARNING:** Do NOT use `handlePaymentSuccess()`
|
|
2340
|
-
> and does NOT send the order to the server. Your customer will pay but no order will be created!
|
|
2560
|
+
> **WARNING (untracked flow only):** Do NOT use `handlePaymentSuccess()` here — it only clears cart state locally and does NOT create the order on the server. The tracked flow uses `handlePaymentSuccess` + `waitForOrder`; the untracked flow uses `completeGuestCheckout` directly.
|
|
2341
2561
|
|
|
2342
2562
|
```typescript
|
|
2343
2563
|
// On your /checkout/success page:
|
|
@@ -3014,12 +3234,14 @@ console.log(store.language); // 'en', 'he', etc.
|
|
|
3014
3234
|
|
|
3015
3235
|
## Admin API Reference
|
|
3016
3236
|
|
|
3017
|
-
|
|
3237
|
+
> ⛔ **Server-side only.** The `apiKey` (`brainerce_*`) is a privileged secret — NEVER put it in browser code, client bundles, or any code that ships to the user's machine. It belongs in an environment variable on your server. For building the customer-facing storefront, use `salesChannelId` instead (see [Quick Start](#quick-start)).
|
|
3238
|
+
|
|
3239
|
+
The Admin API provides full access to store configuration and management features (taxonomy, shipping, team, metafields, etc.) and runs only in server-side code.
|
|
3018
3240
|
|
|
3019
3241
|
```typescript
|
|
3020
|
-
//
|
|
3021
|
-
const
|
|
3022
|
-
apiKey: process.env.BRAINERCE_API_KEY, // 'brainerce_*' prefix
|
|
3242
|
+
// ✅ Only in server-side code (Node.js, Edge functions, API routes — never in the browser)
|
|
3243
|
+
const admin = new BrainerceClient({
|
|
3244
|
+
apiKey: process.env.BRAINERCE_API_KEY, // 'brainerce_*' prefix — keep this secret
|
|
3023
3245
|
});
|
|
3024
3246
|
```
|
|
3025
3247
|
|
package/dist/index.d.mts
CHANGED
|
@@ -218,6 +218,12 @@ interface Product {
|
|
|
218
218
|
salePrice?: string | null;
|
|
219
219
|
/** Cost price as string. Use parseFloat() for calculations. */
|
|
220
220
|
costPrice?: string | null;
|
|
221
|
+
/** Lowest effective variant price (VARIABLE products only). String Decimal, e.g. "19.99". */
|
|
222
|
+
priceMin?: string | null;
|
|
223
|
+
/** Highest effective variant price (VARIABLE products only). String Decimal, e.g. "99.99". */
|
|
224
|
+
priceMax?: string | null;
|
|
225
|
+
/** True when variant prices differ (VARIABLE products only). Use for "From X – Y" display. */
|
|
226
|
+
priceVaries?: boolean;
|
|
221
227
|
/** Product status (active, draft). Always returned by backend. */
|
|
222
228
|
status: string;
|
|
223
229
|
type: 'SIMPLE' | 'VARIABLE';
|
|
@@ -317,9 +323,42 @@ interface Product {
|
|
|
317
323
|
* (`getProductBySlug`, `getProductById`); omitted from list responses.
|
|
318
324
|
*/
|
|
319
325
|
modifierGroups?: ModifierGroup[];
|
|
326
|
+
/** Average rating across visible reviews (0 when reviewCount is 0). */
|
|
327
|
+
avgRating?: number;
|
|
328
|
+
/** Count of visible reviews (non-hidden). */
|
|
329
|
+
reviewCount?: number;
|
|
320
330
|
createdAt: string;
|
|
321
331
|
updatedAt: string;
|
|
322
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Product review submitted by a customer.
|
|
335
|
+
* Reviews publish immediately (no PENDING state). Merchants hide via the admin
|
|
336
|
+
* surface — hiddenAt is null for visible reviews.
|
|
337
|
+
*/
|
|
338
|
+
interface ProductReview {
|
|
339
|
+
id: string;
|
|
340
|
+
productId: string;
|
|
341
|
+
authorName: string;
|
|
342
|
+
rating: number;
|
|
343
|
+
body: string | null;
|
|
344
|
+
verifiedPurchase: boolean;
|
|
345
|
+
/** Only present in admin responses; null on storefront responses. */
|
|
346
|
+
hiddenAt?: string | null;
|
|
347
|
+
createdAt: string;
|
|
348
|
+
}
|
|
349
|
+
/** Admin-mode review with full PII. Returned by `client.adminReviews.*`. */
|
|
350
|
+
interface ProductReviewAdmin extends ProductReview {
|
|
351
|
+
customerId: string | null;
|
|
352
|
+
authorEmail: string | null;
|
|
353
|
+
orderId: string | null;
|
|
354
|
+
updatedAt: string;
|
|
355
|
+
}
|
|
356
|
+
interface SubmitProductReviewInput {
|
|
357
|
+
authorName: string;
|
|
358
|
+
authorEmail?: string;
|
|
359
|
+
rating: number;
|
|
360
|
+
body?: string;
|
|
361
|
+
}
|
|
323
362
|
interface ProductImage {
|
|
324
363
|
url: string;
|
|
325
364
|
position?: number;
|
|
@@ -605,7 +644,7 @@ declare function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice
|
|
|
605
644
|
* }
|
|
606
645
|
* ```
|
|
607
646
|
*/
|
|
608
|
-
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount'> | null | undefined): {
|
|
647
|
+
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'> | null | undefined): {
|
|
609
648
|
price: number;
|
|
610
649
|
originalPrice: number;
|
|
611
650
|
isOnSale: boolean;
|
|
@@ -1134,6 +1173,12 @@ interface Coupon {
|
|
|
1134
1173
|
* Returns objects with id and name from backend.
|
|
1135
1174
|
*/
|
|
1136
1175
|
applicableProducts?: EntityRef[] | null;
|
|
1176
|
+
/** Products explicitly excluded from this coupon. */
|
|
1177
|
+
excludedProducts?: EntityRef[] | null;
|
|
1178
|
+
/** Categories this coupon applies to (inclusion list). */
|
|
1179
|
+
applicableCategories?: EntityRef[] | null;
|
|
1180
|
+
/** Categories explicitly excluded from this coupon. */
|
|
1181
|
+
excludedCategories?: EntityRef[] | null;
|
|
1137
1182
|
combinesWithOther: boolean;
|
|
1138
1183
|
/** Whether coupon needs sync to platforms */
|
|
1139
1184
|
needsSync?: boolean;
|
|
@@ -1200,6 +1245,12 @@ interface CreateCouponDto {
|
|
|
1200
1245
|
conditions?: Record<string, unknown>;
|
|
1201
1246
|
/** Product IDs this coupon applies to */
|
|
1202
1247
|
applicableProducts?: string[];
|
|
1248
|
+
/** Product IDs explicitly excluded from this coupon */
|
|
1249
|
+
excludedProducts?: string[];
|
|
1250
|
+
/** Category IDs this coupon applies to */
|
|
1251
|
+
applicableCategories?: string[];
|
|
1252
|
+
/** Category IDs explicitly excluded from this coupon */
|
|
1253
|
+
excludedCategories?: string[];
|
|
1203
1254
|
combinesWithOther?: boolean;
|
|
1204
1255
|
/** Platforms to publish this coupon to */
|
|
1205
1256
|
platforms?: ConnectorPlatform[];
|
|
@@ -1222,6 +1273,9 @@ interface UpdateCouponDto {
|
|
|
1222
1273
|
maximumDiscount?: number | string | null;
|
|
1223
1274
|
conditions?: Record<string, unknown> | null;
|
|
1224
1275
|
applicableProducts?: string[] | null;
|
|
1276
|
+
excludedProducts?: string[] | null;
|
|
1277
|
+
applicableCategories?: string[] | null;
|
|
1278
|
+
excludedCategories?: string[] | null;
|
|
1225
1279
|
combinesWithOther?: boolean;
|
|
1226
1280
|
}
|
|
1227
1281
|
/**
|
|
@@ -6060,6 +6114,48 @@ declare class BrainerceClient {
|
|
|
6060
6114
|
* ```
|
|
6061
6115
|
*/
|
|
6062
6116
|
getProductRecommendations(productId: string, type?: ProductRelationType): Promise<ProductRecommendationsResponse>;
|
|
6117
|
+
/**
|
|
6118
|
+
* List visible reviews for a product (storefront / sales-channel modes).
|
|
6119
|
+
* Reviews that the merchant has hidden are excluded.
|
|
6120
|
+
*
|
|
6121
|
+
* @example
|
|
6122
|
+
* ```typescript
|
|
6123
|
+
* const { data, meta } = await client.listProductReviews('prod_123', { page: 1, limit: 20 });
|
|
6124
|
+
* data.forEach(r => console.log(r.rating, r.body, r.verifiedPurchase));
|
|
6125
|
+
* ```
|
|
6126
|
+
*/
|
|
6127
|
+
listProductReviews(productId: string, params?: {
|
|
6128
|
+
page?: number;
|
|
6129
|
+
limit?: number;
|
|
6130
|
+
}): Promise<PaginatedResponse<ProductReview>>;
|
|
6131
|
+
/**
|
|
6132
|
+
* Submit a customer review for a product (storefront / sales-channel modes).
|
|
6133
|
+
* Publishes immediately. Duplicate submissions from the same email return 409.
|
|
6134
|
+
*
|
|
6135
|
+
* @example
|
|
6136
|
+
* ```typescript
|
|
6137
|
+
* const review = await client.submitProductReview('prod_123', {
|
|
6138
|
+
* authorName: 'Jane Doe',
|
|
6139
|
+
* authorEmail: 'jane@example.com',
|
|
6140
|
+
* rating: 5,
|
|
6141
|
+
* body: 'Loved it!',
|
|
6142
|
+
* });
|
|
6143
|
+
* ```
|
|
6144
|
+
*/
|
|
6145
|
+
submitProductReview(productId: string, input: SubmitProductReviewInput): Promise<ProductReview>;
|
|
6146
|
+
/**
|
|
6147
|
+
* Admin: list all reviews for a product (incl. hidden). Requires API key with `reviews:read`.
|
|
6148
|
+
*/
|
|
6149
|
+
adminListProductReviews(productId: string, params?: {
|
|
6150
|
+
storeId?: string;
|
|
6151
|
+
page?: number;
|
|
6152
|
+
limit?: number;
|
|
6153
|
+
visibility?: 'visible' | 'hidden' | 'all';
|
|
6154
|
+
}): Promise<PaginatedResponse<ProductReviewAdmin>>;
|
|
6155
|
+
/** Admin: hide a review (sets hiddenAt). */
|
|
6156
|
+
hideProductReview(reviewId: string, storeId?: string): Promise<ProductReviewAdmin>;
|
|
6157
|
+
/** Admin: unhide a previously hidden review. */
|
|
6158
|
+
showProductReview(reviewId: string, storeId?: string): Promise<ProductReviewAdmin>;
|
|
6063
6159
|
/**
|
|
6064
6160
|
* Get cross-sell recommendations based on cart contents.
|
|
6065
6161
|
* Returns products that complement items already in the cart.
|
|
@@ -6397,6 +6493,35 @@ declare class BrainerceClient {
|
|
|
6397
6493
|
* ```
|
|
6398
6494
|
*/
|
|
6399
6495
|
getCheckout(checkoutId: string): Promise<Checkout>;
|
|
6496
|
+
/**
|
|
6497
|
+
* Apply a coupon code to an existing checkout session.
|
|
6498
|
+
*
|
|
6499
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
6500
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
6501
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
6502
|
+
* created — which is the common case when the customer enters a code on the
|
|
6503
|
+
* checkout page.
|
|
6504
|
+
*
|
|
6505
|
+
* @example
|
|
6506
|
+
* ```typescript
|
|
6507
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
6508
|
+
* console.log('New total:', checkout.total);
|
|
6509
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
6510
|
+
* ```
|
|
6511
|
+
*/
|
|
6512
|
+
applyCheckoutCoupon(checkoutId: string, code: string): Promise<Checkout>;
|
|
6513
|
+
/**
|
|
6514
|
+
* Remove a coupon from an existing checkout session.
|
|
6515
|
+
*
|
|
6516
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
6517
|
+
*
|
|
6518
|
+
* @example
|
|
6519
|
+
* ```typescript
|
|
6520
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
6521
|
+
* console.log('New total:', checkout.total);
|
|
6522
|
+
* ```
|
|
6523
|
+
*/
|
|
6524
|
+
removeCheckoutCoupon(checkoutId: string): Promise<Checkout>;
|
|
6400
6525
|
/**
|
|
6401
6526
|
* Set customer information on checkout
|
|
6402
6527
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -218,6 +218,12 @@ interface Product {
|
|
|
218
218
|
salePrice?: string | null;
|
|
219
219
|
/** Cost price as string. Use parseFloat() for calculations. */
|
|
220
220
|
costPrice?: string | null;
|
|
221
|
+
/** Lowest effective variant price (VARIABLE products only). String Decimal, e.g. "19.99". */
|
|
222
|
+
priceMin?: string | null;
|
|
223
|
+
/** Highest effective variant price (VARIABLE products only). String Decimal, e.g. "99.99". */
|
|
224
|
+
priceMax?: string | null;
|
|
225
|
+
/** True when variant prices differ (VARIABLE products only). Use for "From X – Y" display. */
|
|
226
|
+
priceVaries?: boolean;
|
|
221
227
|
/** Product status (active, draft). Always returned by backend. */
|
|
222
228
|
status: string;
|
|
223
229
|
type: 'SIMPLE' | 'VARIABLE';
|
|
@@ -317,9 +323,42 @@ interface Product {
|
|
|
317
323
|
* (`getProductBySlug`, `getProductById`); omitted from list responses.
|
|
318
324
|
*/
|
|
319
325
|
modifierGroups?: ModifierGroup[];
|
|
326
|
+
/** Average rating across visible reviews (0 when reviewCount is 0). */
|
|
327
|
+
avgRating?: number;
|
|
328
|
+
/** Count of visible reviews (non-hidden). */
|
|
329
|
+
reviewCount?: number;
|
|
320
330
|
createdAt: string;
|
|
321
331
|
updatedAt: string;
|
|
322
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Product review submitted by a customer.
|
|
335
|
+
* Reviews publish immediately (no PENDING state). Merchants hide via the admin
|
|
336
|
+
* surface — hiddenAt is null for visible reviews.
|
|
337
|
+
*/
|
|
338
|
+
interface ProductReview {
|
|
339
|
+
id: string;
|
|
340
|
+
productId: string;
|
|
341
|
+
authorName: string;
|
|
342
|
+
rating: number;
|
|
343
|
+
body: string | null;
|
|
344
|
+
verifiedPurchase: boolean;
|
|
345
|
+
/** Only present in admin responses; null on storefront responses. */
|
|
346
|
+
hiddenAt?: string | null;
|
|
347
|
+
createdAt: string;
|
|
348
|
+
}
|
|
349
|
+
/** Admin-mode review with full PII. Returned by `client.adminReviews.*`. */
|
|
350
|
+
interface ProductReviewAdmin extends ProductReview {
|
|
351
|
+
customerId: string | null;
|
|
352
|
+
authorEmail: string | null;
|
|
353
|
+
orderId: string | null;
|
|
354
|
+
updatedAt: string;
|
|
355
|
+
}
|
|
356
|
+
interface SubmitProductReviewInput {
|
|
357
|
+
authorName: string;
|
|
358
|
+
authorEmail?: string;
|
|
359
|
+
rating: number;
|
|
360
|
+
body?: string;
|
|
361
|
+
}
|
|
323
362
|
interface ProductImage {
|
|
324
363
|
url: string;
|
|
325
364
|
position?: number;
|
|
@@ -605,7 +644,7 @@ declare function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice
|
|
|
605
644
|
* }
|
|
606
645
|
* ```
|
|
607
646
|
*/
|
|
608
|
-
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount'> | null | undefined): {
|
|
647
|
+
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'> | null | undefined): {
|
|
609
648
|
price: number;
|
|
610
649
|
originalPrice: number;
|
|
611
650
|
isOnSale: boolean;
|
|
@@ -1134,6 +1173,12 @@ interface Coupon {
|
|
|
1134
1173
|
* Returns objects with id and name from backend.
|
|
1135
1174
|
*/
|
|
1136
1175
|
applicableProducts?: EntityRef[] | null;
|
|
1176
|
+
/** Products explicitly excluded from this coupon. */
|
|
1177
|
+
excludedProducts?: EntityRef[] | null;
|
|
1178
|
+
/** Categories this coupon applies to (inclusion list). */
|
|
1179
|
+
applicableCategories?: EntityRef[] | null;
|
|
1180
|
+
/** Categories explicitly excluded from this coupon. */
|
|
1181
|
+
excludedCategories?: EntityRef[] | null;
|
|
1137
1182
|
combinesWithOther: boolean;
|
|
1138
1183
|
/** Whether coupon needs sync to platforms */
|
|
1139
1184
|
needsSync?: boolean;
|
|
@@ -1200,6 +1245,12 @@ interface CreateCouponDto {
|
|
|
1200
1245
|
conditions?: Record<string, unknown>;
|
|
1201
1246
|
/** Product IDs this coupon applies to */
|
|
1202
1247
|
applicableProducts?: string[];
|
|
1248
|
+
/** Product IDs explicitly excluded from this coupon */
|
|
1249
|
+
excludedProducts?: string[];
|
|
1250
|
+
/** Category IDs this coupon applies to */
|
|
1251
|
+
applicableCategories?: string[];
|
|
1252
|
+
/** Category IDs explicitly excluded from this coupon */
|
|
1253
|
+
excludedCategories?: string[];
|
|
1203
1254
|
combinesWithOther?: boolean;
|
|
1204
1255
|
/** Platforms to publish this coupon to */
|
|
1205
1256
|
platforms?: ConnectorPlatform[];
|
|
@@ -1222,6 +1273,9 @@ interface UpdateCouponDto {
|
|
|
1222
1273
|
maximumDiscount?: number | string | null;
|
|
1223
1274
|
conditions?: Record<string, unknown> | null;
|
|
1224
1275
|
applicableProducts?: string[] | null;
|
|
1276
|
+
excludedProducts?: string[] | null;
|
|
1277
|
+
applicableCategories?: string[] | null;
|
|
1278
|
+
excludedCategories?: string[] | null;
|
|
1225
1279
|
combinesWithOther?: boolean;
|
|
1226
1280
|
}
|
|
1227
1281
|
/**
|
|
@@ -6060,6 +6114,48 @@ declare class BrainerceClient {
|
|
|
6060
6114
|
* ```
|
|
6061
6115
|
*/
|
|
6062
6116
|
getProductRecommendations(productId: string, type?: ProductRelationType): Promise<ProductRecommendationsResponse>;
|
|
6117
|
+
/**
|
|
6118
|
+
* List visible reviews for a product (storefront / sales-channel modes).
|
|
6119
|
+
* Reviews that the merchant has hidden are excluded.
|
|
6120
|
+
*
|
|
6121
|
+
* @example
|
|
6122
|
+
* ```typescript
|
|
6123
|
+
* const { data, meta } = await client.listProductReviews('prod_123', { page: 1, limit: 20 });
|
|
6124
|
+
* data.forEach(r => console.log(r.rating, r.body, r.verifiedPurchase));
|
|
6125
|
+
* ```
|
|
6126
|
+
*/
|
|
6127
|
+
listProductReviews(productId: string, params?: {
|
|
6128
|
+
page?: number;
|
|
6129
|
+
limit?: number;
|
|
6130
|
+
}): Promise<PaginatedResponse<ProductReview>>;
|
|
6131
|
+
/**
|
|
6132
|
+
* Submit a customer review for a product (storefront / sales-channel modes).
|
|
6133
|
+
* Publishes immediately. Duplicate submissions from the same email return 409.
|
|
6134
|
+
*
|
|
6135
|
+
* @example
|
|
6136
|
+
* ```typescript
|
|
6137
|
+
* const review = await client.submitProductReview('prod_123', {
|
|
6138
|
+
* authorName: 'Jane Doe',
|
|
6139
|
+
* authorEmail: 'jane@example.com',
|
|
6140
|
+
* rating: 5,
|
|
6141
|
+
* body: 'Loved it!',
|
|
6142
|
+
* });
|
|
6143
|
+
* ```
|
|
6144
|
+
*/
|
|
6145
|
+
submitProductReview(productId: string, input: SubmitProductReviewInput): Promise<ProductReview>;
|
|
6146
|
+
/**
|
|
6147
|
+
* Admin: list all reviews for a product (incl. hidden). Requires API key with `reviews:read`.
|
|
6148
|
+
*/
|
|
6149
|
+
adminListProductReviews(productId: string, params?: {
|
|
6150
|
+
storeId?: string;
|
|
6151
|
+
page?: number;
|
|
6152
|
+
limit?: number;
|
|
6153
|
+
visibility?: 'visible' | 'hidden' | 'all';
|
|
6154
|
+
}): Promise<PaginatedResponse<ProductReviewAdmin>>;
|
|
6155
|
+
/** Admin: hide a review (sets hiddenAt). */
|
|
6156
|
+
hideProductReview(reviewId: string, storeId?: string): Promise<ProductReviewAdmin>;
|
|
6157
|
+
/** Admin: unhide a previously hidden review. */
|
|
6158
|
+
showProductReview(reviewId: string, storeId?: string): Promise<ProductReviewAdmin>;
|
|
6063
6159
|
/**
|
|
6064
6160
|
* Get cross-sell recommendations based on cart contents.
|
|
6065
6161
|
* Returns products that complement items already in the cart.
|
|
@@ -6397,6 +6493,35 @@ declare class BrainerceClient {
|
|
|
6397
6493
|
* ```
|
|
6398
6494
|
*/
|
|
6399
6495
|
getCheckout(checkoutId: string): Promise<Checkout>;
|
|
6496
|
+
/**
|
|
6497
|
+
* Apply a coupon code to an existing checkout session.
|
|
6498
|
+
*
|
|
6499
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
6500
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
6501
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
6502
|
+
* created — which is the common case when the customer enters a code on the
|
|
6503
|
+
* checkout page.
|
|
6504
|
+
*
|
|
6505
|
+
* @example
|
|
6506
|
+
* ```typescript
|
|
6507
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
6508
|
+
* console.log('New total:', checkout.total);
|
|
6509
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
6510
|
+
* ```
|
|
6511
|
+
*/
|
|
6512
|
+
applyCheckoutCoupon(checkoutId: string, code: string): Promise<Checkout>;
|
|
6513
|
+
/**
|
|
6514
|
+
* Remove a coupon from an existing checkout session.
|
|
6515
|
+
*
|
|
6516
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
6517
|
+
*
|
|
6518
|
+
* @example
|
|
6519
|
+
* ```typescript
|
|
6520
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
6521
|
+
* console.log('New total:', checkout.total);
|
|
6522
|
+
* ```
|
|
6523
|
+
*/
|
|
6524
|
+
removeCheckoutCoupon(checkoutId: string): Promise<Checkout>;
|
|
6400
6525
|
/**
|
|
6401
6526
|
* Set customer information on checkout
|
|
6402
6527
|
*
|
package/dist/index.js
CHANGED
|
@@ -262,9 +262,7 @@ var BrainerceClient = class {
|
|
|
262
262
|
this.LOCAL_CART_KEY = "brainerce_cart";
|
|
263
263
|
const resolvedSalesChannelId = options.salesChannelId ?? options.connectionId;
|
|
264
264
|
if (!options.apiKey && !options.storeId && !resolvedSalesChannelId) {
|
|
265
|
-
throw new Error(
|
|
266
|
-
"BrainerceClient: either salesChannelId, apiKey, or storeId is required"
|
|
267
|
-
);
|
|
265
|
+
throw new Error("BrainerceClient: either salesChannelId, apiKey, or storeId is required");
|
|
268
266
|
}
|
|
269
267
|
if (options.apiKey && !options.apiKey.startsWith("brainerce_")) {
|
|
270
268
|
console.warn('BrainerceClient: apiKey should start with "brainerce_"');
|
|
@@ -2792,6 +2790,107 @@ var BrainerceClient = class {
|
|
|
2792
2790
|
400
|
|
2793
2791
|
);
|
|
2794
2792
|
}
|
|
2793
|
+
// ===================================================
|
|
2794
|
+
// Product Reviews
|
|
2795
|
+
// ===================================================
|
|
2796
|
+
/**
|
|
2797
|
+
* List visible reviews for a product (storefront / sales-channel modes).
|
|
2798
|
+
* Reviews that the merchant has hidden are excluded.
|
|
2799
|
+
*
|
|
2800
|
+
* @example
|
|
2801
|
+
* ```typescript
|
|
2802
|
+
* const { data, meta } = await client.listProductReviews('prod_123', { page: 1, limit: 20 });
|
|
2803
|
+
* data.forEach(r => console.log(r.rating, r.body, r.verifiedPurchase));
|
|
2804
|
+
* ```
|
|
2805
|
+
*/
|
|
2806
|
+
async listProductReviews(productId, params) {
|
|
2807
|
+
const queryParams = {};
|
|
2808
|
+
if (params?.page) queryParams.page = params.page;
|
|
2809
|
+
if (params?.limit) queryParams.limit = params.limit;
|
|
2810
|
+
if (this.isVibeCodedMode()) {
|
|
2811
|
+
return this.vibeCodedRequest(
|
|
2812
|
+
"GET",
|
|
2813
|
+
`/products/${productId}/reviews`,
|
|
2814
|
+
void 0,
|
|
2815
|
+
queryParams
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
if (this.storeId && !this.apiKey) {
|
|
2819
|
+
return this.storefrontRequest(
|
|
2820
|
+
"GET",
|
|
2821
|
+
`/products/${productId}/reviews`,
|
|
2822
|
+
void 0,
|
|
2823
|
+
queryParams
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
throw new BrainerceError("listProductReviews() requires vibe-coded or storefront mode", 400);
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Submit a customer review for a product (storefront / sales-channel modes).
|
|
2830
|
+
* Publishes immediately. Duplicate submissions from the same email return 409.
|
|
2831
|
+
*
|
|
2832
|
+
* @example
|
|
2833
|
+
* ```typescript
|
|
2834
|
+
* const review = await client.submitProductReview('prod_123', {
|
|
2835
|
+
* authorName: 'Jane Doe',
|
|
2836
|
+
* authorEmail: 'jane@example.com',
|
|
2837
|
+
* rating: 5,
|
|
2838
|
+
* body: 'Loved it!',
|
|
2839
|
+
* });
|
|
2840
|
+
* ```
|
|
2841
|
+
*/
|
|
2842
|
+
async submitProductReview(productId, input) {
|
|
2843
|
+
if (this.isVibeCodedMode()) {
|
|
2844
|
+
return this.vibeCodedRequest("POST", `/products/${productId}/reviews`, input);
|
|
2845
|
+
}
|
|
2846
|
+
if (this.storeId && !this.apiKey) {
|
|
2847
|
+
return this.storefrontRequest("POST", `/products/${productId}/reviews`, input);
|
|
2848
|
+
}
|
|
2849
|
+
throw new BrainerceError("submitProductReview() requires vibe-coded or storefront mode", 400);
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Admin: list all reviews for a product (incl. hidden). Requires API key with `reviews:read`.
|
|
2853
|
+
*/
|
|
2854
|
+
async adminListProductReviews(productId, params) {
|
|
2855
|
+
if (!this.apiKey) {
|
|
2856
|
+
throw new BrainerceError("adminListProductReviews() requires admin (API key) mode", 400);
|
|
2857
|
+
}
|
|
2858
|
+
const queryParams = {};
|
|
2859
|
+
if (params?.storeId) queryParams.storeId = params.storeId;
|
|
2860
|
+
if (params?.page) queryParams.page = params.page;
|
|
2861
|
+
if (params?.limit) queryParams.limit = params.limit;
|
|
2862
|
+
if (params?.visibility) queryParams.visibility = params.visibility;
|
|
2863
|
+
return this.adminRequest(
|
|
2864
|
+
"GET",
|
|
2865
|
+
`/api/v1/products/${productId}/reviews`,
|
|
2866
|
+
void 0,
|
|
2867
|
+
queryParams
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
/** Admin: hide a review (sets hiddenAt). */
|
|
2871
|
+
async hideProductReview(reviewId, storeId) {
|
|
2872
|
+
if (!this.apiKey) {
|
|
2873
|
+
throw new BrainerceError("hideProductReview() requires admin (API key) mode", 400);
|
|
2874
|
+
}
|
|
2875
|
+
return this.adminRequest(
|
|
2876
|
+
"PATCH",
|
|
2877
|
+
`/api/v1/reviews/${reviewId}/hide`,
|
|
2878
|
+
void 0,
|
|
2879
|
+
storeId ? { storeId } : void 0
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
/** Admin: unhide a previously hidden review. */
|
|
2883
|
+
async showProductReview(reviewId, storeId) {
|
|
2884
|
+
if (!this.apiKey) {
|
|
2885
|
+
throw new BrainerceError("showProductReview() requires admin (API key) mode", 400);
|
|
2886
|
+
}
|
|
2887
|
+
return this.adminRequest(
|
|
2888
|
+
"PATCH",
|
|
2889
|
+
`/api/v1/reviews/${reviewId}/show`,
|
|
2890
|
+
void 0,
|
|
2891
|
+
storeId ? { storeId } : void 0
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2795
2894
|
/**
|
|
2796
2895
|
* Get cross-sell recommendations based on cart contents.
|
|
2797
2896
|
* Returns products that complement items already in the cart.
|
|
@@ -3575,6 +3674,69 @@ var BrainerceClient = class {
|
|
|
3575
3674
|
"checkout"
|
|
3576
3675
|
);
|
|
3577
3676
|
}
|
|
3677
|
+
/**
|
|
3678
|
+
* Apply a coupon code to an existing checkout session.
|
|
3679
|
+
*
|
|
3680
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
3681
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
3682
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
3683
|
+
* created — which is the common case when the customer enters a code on the
|
|
3684
|
+
* checkout page.
|
|
3685
|
+
*
|
|
3686
|
+
* @example
|
|
3687
|
+
* ```typescript
|
|
3688
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
3689
|
+
* console.log('New total:', checkout.total);
|
|
3690
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
3691
|
+
* ```
|
|
3692
|
+
*/
|
|
3693
|
+
async applyCheckoutCoupon(checkoutId, code) {
|
|
3694
|
+
if (this.isVibeCodedMode()) {
|
|
3695
|
+
return this.withGuards(
|
|
3696
|
+
this.vibeCodedRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3697
|
+
"checkout"
|
|
3698
|
+
);
|
|
3699
|
+
}
|
|
3700
|
+
if (this.storeId && !this.apiKey) {
|
|
3701
|
+
return this.withGuards(
|
|
3702
|
+
this.storefrontRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3703
|
+
"checkout"
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
return this.withGuards(
|
|
3707
|
+
this.adminRequest("POST", `/api/v1/checkout/${checkoutId}/coupon`, { code }),
|
|
3708
|
+
"checkout"
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
/**
|
|
3712
|
+
* Remove a coupon from an existing checkout session.
|
|
3713
|
+
*
|
|
3714
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
3715
|
+
*
|
|
3716
|
+
* @example
|
|
3717
|
+
* ```typescript
|
|
3718
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
3719
|
+
* console.log('New total:', checkout.total);
|
|
3720
|
+
* ```
|
|
3721
|
+
*/
|
|
3722
|
+
async removeCheckoutCoupon(checkoutId) {
|
|
3723
|
+
if (this.isVibeCodedMode()) {
|
|
3724
|
+
return this.withGuards(
|
|
3725
|
+
this.vibeCodedRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3726
|
+
"checkout"
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
if (this.storeId && !this.apiKey) {
|
|
3730
|
+
return this.withGuards(
|
|
3731
|
+
this.storefrontRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3732
|
+
"checkout"
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3735
|
+
return this.withGuards(
|
|
3736
|
+
this.adminRequest("DELETE", `/api/v1/checkout/${checkoutId}/coupon`),
|
|
3737
|
+
"checkout"
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3578
3740
|
/**
|
|
3579
3741
|
* Set customer information on checkout
|
|
3580
3742
|
*
|
|
@@ -6972,6 +7134,7 @@ function getProductPriceInfo(product) {
|
|
|
6972
7134
|
if (!product) {
|
|
6973
7135
|
return { price: 0, originalPrice: 0, isOnSale: false, discountAmount: 0, discountPercent: 0 };
|
|
6974
7136
|
}
|
|
7137
|
+
const resolvedBasePrice = parseFloat(product.basePrice) === 0 && product.priceMin ? product.priceMin : product.basePrice;
|
|
6975
7138
|
if (product.discount) {
|
|
6976
7139
|
const ruleOriginal = parseFloat(product.discount.originalPrice) || 0;
|
|
6977
7140
|
const ruleDiscounted = parseFloat(product.discount.discountedPrice) || 0;
|
|
@@ -6985,7 +7148,7 @@ function getProductPriceInfo(product) {
|
|
|
6985
7148
|
discountPercent: rulePercent
|
|
6986
7149
|
};
|
|
6987
7150
|
}
|
|
6988
|
-
const basePrice = parseFloat(
|
|
7151
|
+
const basePrice = parseFloat(resolvedBasePrice) || 0;
|
|
6989
7152
|
const salePrice = product.salePrice ? parseFloat(product.salePrice) : null;
|
|
6990
7153
|
const isOnSale = salePrice !== null && salePrice < basePrice;
|
|
6991
7154
|
const effectivePrice = isOnSale ? salePrice : basePrice;
|
package/dist/index.mjs
CHANGED
|
@@ -199,9 +199,7 @@ var BrainerceClient = class {
|
|
|
199
199
|
this.LOCAL_CART_KEY = "brainerce_cart";
|
|
200
200
|
const resolvedSalesChannelId = options.salesChannelId ?? options.connectionId;
|
|
201
201
|
if (!options.apiKey && !options.storeId && !resolvedSalesChannelId) {
|
|
202
|
-
throw new Error(
|
|
203
|
-
"BrainerceClient: either salesChannelId, apiKey, or storeId is required"
|
|
204
|
-
);
|
|
202
|
+
throw new Error("BrainerceClient: either salesChannelId, apiKey, or storeId is required");
|
|
205
203
|
}
|
|
206
204
|
if (options.apiKey && !options.apiKey.startsWith("brainerce_")) {
|
|
207
205
|
console.warn('BrainerceClient: apiKey should start with "brainerce_"');
|
|
@@ -2729,6 +2727,107 @@ var BrainerceClient = class {
|
|
|
2729
2727
|
400
|
|
2730
2728
|
);
|
|
2731
2729
|
}
|
|
2730
|
+
// ===================================================
|
|
2731
|
+
// Product Reviews
|
|
2732
|
+
// ===================================================
|
|
2733
|
+
/**
|
|
2734
|
+
* List visible reviews for a product (storefront / sales-channel modes).
|
|
2735
|
+
* Reviews that the merchant has hidden are excluded.
|
|
2736
|
+
*
|
|
2737
|
+
* @example
|
|
2738
|
+
* ```typescript
|
|
2739
|
+
* const { data, meta } = await client.listProductReviews('prod_123', { page: 1, limit: 20 });
|
|
2740
|
+
* data.forEach(r => console.log(r.rating, r.body, r.verifiedPurchase));
|
|
2741
|
+
* ```
|
|
2742
|
+
*/
|
|
2743
|
+
async listProductReviews(productId, params) {
|
|
2744
|
+
const queryParams = {};
|
|
2745
|
+
if (params?.page) queryParams.page = params.page;
|
|
2746
|
+
if (params?.limit) queryParams.limit = params.limit;
|
|
2747
|
+
if (this.isVibeCodedMode()) {
|
|
2748
|
+
return this.vibeCodedRequest(
|
|
2749
|
+
"GET",
|
|
2750
|
+
`/products/${productId}/reviews`,
|
|
2751
|
+
void 0,
|
|
2752
|
+
queryParams
|
|
2753
|
+
);
|
|
2754
|
+
}
|
|
2755
|
+
if (this.storeId && !this.apiKey) {
|
|
2756
|
+
return this.storefrontRequest(
|
|
2757
|
+
"GET",
|
|
2758
|
+
`/products/${productId}/reviews`,
|
|
2759
|
+
void 0,
|
|
2760
|
+
queryParams
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
throw new BrainerceError("listProductReviews() requires vibe-coded or storefront mode", 400);
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Submit a customer review for a product (storefront / sales-channel modes).
|
|
2767
|
+
* Publishes immediately. Duplicate submissions from the same email return 409.
|
|
2768
|
+
*
|
|
2769
|
+
* @example
|
|
2770
|
+
* ```typescript
|
|
2771
|
+
* const review = await client.submitProductReview('prod_123', {
|
|
2772
|
+
* authorName: 'Jane Doe',
|
|
2773
|
+
* authorEmail: 'jane@example.com',
|
|
2774
|
+
* rating: 5,
|
|
2775
|
+
* body: 'Loved it!',
|
|
2776
|
+
* });
|
|
2777
|
+
* ```
|
|
2778
|
+
*/
|
|
2779
|
+
async submitProductReview(productId, input) {
|
|
2780
|
+
if (this.isVibeCodedMode()) {
|
|
2781
|
+
return this.vibeCodedRequest("POST", `/products/${productId}/reviews`, input);
|
|
2782
|
+
}
|
|
2783
|
+
if (this.storeId && !this.apiKey) {
|
|
2784
|
+
return this.storefrontRequest("POST", `/products/${productId}/reviews`, input);
|
|
2785
|
+
}
|
|
2786
|
+
throw new BrainerceError("submitProductReview() requires vibe-coded or storefront mode", 400);
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Admin: list all reviews for a product (incl. hidden). Requires API key with `reviews:read`.
|
|
2790
|
+
*/
|
|
2791
|
+
async adminListProductReviews(productId, params) {
|
|
2792
|
+
if (!this.apiKey) {
|
|
2793
|
+
throw new BrainerceError("adminListProductReviews() requires admin (API key) mode", 400);
|
|
2794
|
+
}
|
|
2795
|
+
const queryParams = {};
|
|
2796
|
+
if (params?.storeId) queryParams.storeId = params.storeId;
|
|
2797
|
+
if (params?.page) queryParams.page = params.page;
|
|
2798
|
+
if (params?.limit) queryParams.limit = params.limit;
|
|
2799
|
+
if (params?.visibility) queryParams.visibility = params.visibility;
|
|
2800
|
+
return this.adminRequest(
|
|
2801
|
+
"GET",
|
|
2802
|
+
`/api/v1/products/${productId}/reviews`,
|
|
2803
|
+
void 0,
|
|
2804
|
+
queryParams
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
/** Admin: hide a review (sets hiddenAt). */
|
|
2808
|
+
async hideProductReview(reviewId, storeId) {
|
|
2809
|
+
if (!this.apiKey) {
|
|
2810
|
+
throw new BrainerceError("hideProductReview() requires admin (API key) mode", 400);
|
|
2811
|
+
}
|
|
2812
|
+
return this.adminRequest(
|
|
2813
|
+
"PATCH",
|
|
2814
|
+
`/api/v1/reviews/${reviewId}/hide`,
|
|
2815
|
+
void 0,
|
|
2816
|
+
storeId ? { storeId } : void 0
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
/** Admin: unhide a previously hidden review. */
|
|
2820
|
+
async showProductReview(reviewId, storeId) {
|
|
2821
|
+
if (!this.apiKey) {
|
|
2822
|
+
throw new BrainerceError("showProductReview() requires admin (API key) mode", 400);
|
|
2823
|
+
}
|
|
2824
|
+
return this.adminRequest(
|
|
2825
|
+
"PATCH",
|
|
2826
|
+
`/api/v1/reviews/${reviewId}/show`,
|
|
2827
|
+
void 0,
|
|
2828
|
+
storeId ? { storeId } : void 0
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2732
2831
|
/**
|
|
2733
2832
|
* Get cross-sell recommendations based on cart contents.
|
|
2734
2833
|
* Returns products that complement items already in the cart.
|
|
@@ -3512,6 +3611,69 @@ var BrainerceClient = class {
|
|
|
3512
3611
|
"checkout"
|
|
3513
3612
|
);
|
|
3514
3613
|
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Apply a coupon code to an existing checkout session.
|
|
3616
|
+
*
|
|
3617
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
3618
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
3619
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
3620
|
+
* created — which is the common case when the customer enters a code on the
|
|
3621
|
+
* checkout page.
|
|
3622
|
+
*
|
|
3623
|
+
* @example
|
|
3624
|
+
* ```typescript
|
|
3625
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
3626
|
+
* console.log('New total:', checkout.total);
|
|
3627
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
3628
|
+
* ```
|
|
3629
|
+
*/
|
|
3630
|
+
async applyCheckoutCoupon(checkoutId, code) {
|
|
3631
|
+
if (this.isVibeCodedMode()) {
|
|
3632
|
+
return this.withGuards(
|
|
3633
|
+
this.vibeCodedRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3634
|
+
"checkout"
|
|
3635
|
+
);
|
|
3636
|
+
}
|
|
3637
|
+
if (this.storeId && !this.apiKey) {
|
|
3638
|
+
return this.withGuards(
|
|
3639
|
+
this.storefrontRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3640
|
+
"checkout"
|
|
3641
|
+
);
|
|
3642
|
+
}
|
|
3643
|
+
return this.withGuards(
|
|
3644
|
+
this.adminRequest("POST", `/api/v1/checkout/${checkoutId}/coupon`, { code }),
|
|
3645
|
+
"checkout"
|
|
3646
|
+
);
|
|
3647
|
+
}
|
|
3648
|
+
/**
|
|
3649
|
+
* Remove a coupon from an existing checkout session.
|
|
3650
|
+
*
|
|
3651
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
3652
|
+
*
|
|
3653
|
+
* @example
|
|
3654
|
+
* ```typescript
|
|
3655
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
3656
|
+
* console.log('New total:', checkout.total);
|
|
3657
|
+
* ```
|
|
3658
|
+
*/
|
|
3659
|
+
async removeCheckoutCoupon(checkoutId) {
|
|
3660
|
+
if (this.isVibeCodedMode()) {
|
|
3661
|
+
return this.withGuards(
|
|
3662
|
+
this.vibeCodedRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3663
|
+
"checkout"
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
if (this.storeId && !this.apiKey) {
|
|
3667
|
+
return this.withGuards(
|
|
3668
|
+
this.storefrontRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3669
|
+
"checkout"
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
return this.withGuards(
|
|
3673
|
+
this.adminRequest("DELETE", `/api/v1/checkout/${checkoutId}/coupon`),
|
|
3674
|
+
"checkout"
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3515
3677
|
/**
|
|
3516
3678
|
* Set customer information on checkout
|
|
3517
3679
|
*
|
|
@@ -6909,6 +7071,7 @@ function getProductPriceInfo(product) {
|
|
|
6909
7071
|
if (!product) {
|
|
6910
7072
|
return { price: 0, originalPrice: 0, isOnSale: false, discountAmount: 0, discountPercent: 0 };
|
|
6911
7073
|
}
|
|
7074
|
+
const resolvedBasePrice = parseFloat(product.basePrice) === 0 && product.priceMin ? product.priceMin : product.basePrice;
|
|
6912
7075
|
if (product.discount) {
|
|
6913
7076
|
const ruleOriginal = parseFloat(product.discount.originalPrice) || 0;
|
|
6914
7077
|
const ruleDiscounted = parseFloat(product.discount.discountedPrice) || 0;
|
|
@@ -6922,7 +7085,7 @@ function getProductPriceInfo(product) {
|
|
|
6922
7085
|
discountPercent: rulePercent
|
|
6923
7086
|
};
|
|
6924
7087
|
}
|
|
6925
|
-
const basePrice = parseFloat(
|
|
7088
|
+
const basePrice = parseFloat(resolvedBasePrice) || 0;
|
|
6926
7089
|
const salePrice = product.salePrice ? parseFloat(product.salePrice) : null;
|
|
6927
7090
|
const isOnSale = salePrice !== null && salePrice < basePrice;
|
|
6928
7091
|
const effectivePrice = isOnSale ? salePrice : basePrice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainerce",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|