brainerce 1.23.13 → 1.23.14
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 +251 -30
- package/dist/index.d.mts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +66 -4
- package/dist/index.mjs +66 -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,218 @@ 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
|
+
| Multi-language + RTL (when i18n enabled) | `client.setLocale()` | conditional |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Critical Rules
|
|
56
|
+
|
|
57
|
+
Violating any of these causes production incidents or broken orders. Read them before writing SDK code.
|
|
58
|
+
|
|
59
|
+
### SDK usage
|
|
60
|
+
|
|
61
|
+
- ALWAYS call SDK client methods. Never reconstruct REST URLs or call `fetch` directly.
|
|
62
|
+
- NEVER invent SDK method names. If it's not in this README or in `get-sdk-docs`, it doesn't exist.
|
|
63
|
+
- NEVER hardcode product data, categories, or store copy — Brainerce is the database.
|
|
64
|
+
- NEVER use `submitGuestOrder()` or `createOrder()` — they bypass payment and produce unpaid orders.
|
|
65
|
+
- ALWAYS use SDK helpers (`getCartTotals`, `formatPrice`, `getProductPriceInfo`, `getCartItemImage`, `getCartItemName`, `getVariantPrice`, `getStockStatus`, `getDescriptionContent`) instead of reading raw fields.
|
|
66
|
+
|
|
67
|
+
### State management
|
|
68
|
+
|
|
69
|
+
- The SDK manages cart, checkout, and session state. Do NOT duplicate it in your own Redux/context.
|
|
70
|
+
- Product lists, categories, and inventory counts are NOT client state — fetch on demand.
|
|
71
|
+
- Discount rules and coupon validity are evaluated server-side. Never re-implement them client-side.
|
|
72
|
+
|
|
73
|
+
### Authentication
|
|
74
|
+
|
|
75
|
+
- 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.
|
|
76
|
+
- 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.
|
|
77
|
+
- ALWAYS build OAuth button placeholders and a callback handler even when no OAuth provider is configured.
|
|
78
|
+
- NEVER silently swallow auth errors. Render the specific error (invalid credentials, expired token, rate limited).
|
|
79
|
+
|
|
80
|
+
### Checkout & orders
|
|
81
|
+
|
|
82
|
+
- The checkout sequence is strict: `setShippingAddress` → pick a shipping rate → `getPaymentProviders` → provider payment → `handlePaymentSuccess` → `waitForOrder`. Never skip or reorder.
|
|
83
|
+
- ALWAYS call `handlePaymentSuccess(checkoutId)` on the confirmation page — clears the cart so users don't see stale items.
|
|
84
|
+
- 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.
|
|
85
|
+
- NEVER use the checkout total as the cart total — they diverge (tax, shipping, discounts). Display `checkout.lineItems` on the summary, not `cart.items`.
|
|
86
|
+
- The reservation timer is a hard guarantee — display the countdown from the cart and let the SDK handle expiry.
|
|
87
|
+
|
|
88
|
+
### Token handling
|
|
89
|
+
|
|
90
|
+
- Customer auth tokens (`result.token` from `loginCustomer`/`registerCustomer`) should be passed to `client.setCustomerToken(token)`. The SDK stores session state internally.
|
|
91
|
+
- NEVER put the admin API key (`brainerce_*`) in client code. It is a server-only secret.
|
|
92
|
+
- OAuth callbacks arrive with the token in URL params. Extract and apply the token before redirecting.
|
|
93
|
+
|
|
94
|
+
### i18n
|
|
95
|
+
|
|
96
|
+
- NEVER hardcode currency, locale, or language strings — read them from `getStoreInfo()`.
|
|
97
|
+
- NEVER format prices with `toFixed(2)` — use `formatPrice()` from the SDK.
|
|
98
|
+
- 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.
|
|
99
|
+
|
|
100
|
+
### Type safety
|
|
101
|
+
|
|
102
|
+
- NEVER use `as any` or `as unknown as`. Fix the type, don't hide it.
|
|
103
|
+
- NEVER write your own copies of SDK types (Cart, Product, Order). Import from `'brainerce'`.
|
|
104
|
+
- All prices are **STRINGS** — always `parseFloat()` before math or comparisons.
|
|
105
|
+
- `CartItem` / `CheckoutLineItem` = **NESTED** (`item.product.name`, `item.unitPrice`). `OrderItem` = **FLAT** (`item.name`, `item.price`). Not interchangeable.
|
|
106
|
+
- `Cart` has no `.total` field — call `getCartTotals(cart)`.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Business Flows
|
|
111
|
+
|
|
112
|
+
These sequences are non-negotiable. The order of SDK calls matters.
|
|
113
|
+
|
|
114
|
+
### Checkout flow
|
|
115
|
+
|
|
116
|
+
1. Collect customer email, billing address, shipping address (`line1`, `line2`, `city`, `region`, `postalCode`, `country`). `email` is required.
|
|
117
|
+
2. Submit address to get shipping rates:
|
|
118
|
+
```ts
|
|
119
|
+
const { checkout, rates } = await client.setShippingAddress(checkoutId, {
|
|
120
|
+
email,
|
|
121
|
+
firstName,
|
|
122
|
+
lastName,
|
|
123
|
+
line1,
|
|
124
|
+
city,
|
|
125
|
+
region,
|
|
126
|
+
postalCode,
|
|
127
|
+
country,
|
|
128
|
+
});
|
|
129
|
+
// rates = available shipping rates; checkout = updated checkout object
|
|
130
|
+
```
|
|
131
|
+
3. Let the customer pick a rate, then persist it:
|
|
132
|
+
```ts
|
|
133
|
+
await client.selectShippingMethod(checkoutId, rateId);
|
|
134
|
+
```
|
|
135
|
+
4. Fetch available payment providers:
|
|
136
|
+
```ts
|
|
137
|
+
const providers = await client.getPaymentProviders();
|
|
138
|
+
```
|
|
139
|
+
Each provider has a `renderType` — `'sdk-widget'` (Stripe, PayPal, Grow), `'iframe'` (Cardcom), `'redirect'`, `'sandbox'`. Branch on `renderType`, never on provider name.
|
|
140
|
+
5. Confirm payment using the provider's flow (Stripe Elements `stripe.confirmCardPayment`, PayPal button, redirect, etc.).
|
|
141
|
+
6. On the confirmation page, **always call both**:
|
|
142
|
+
```ts
|
|
143
|
+
await client.handlePaymentSuccess(checkoutId); // clears cart
|
|
144
|
+
const order = await client.waitForOrder(checkoutId); // polls until order exists
|
|
145
|
+
```
|
|
146
|
+
7. Display `checkout.lineItems` (not `cart.items`) on the order summary.
|
|
147
|
+
|
|
148
|
+
### Registration flow
|
|
149
|
+
|
|
150
|
+
1. Collect email, password, first name, last name.
|
|
151
|
+
2. Call `registerCustomer`:
|
|
152
|
+
```ts
|
|
153
|
+
const result = await client.registerCustomer({ email, password, firstName, lastName });
|
|
154
|
+
```
|
|
155
|
+
3. Branch on `result.requiresVerification`:
|
|
156
|
+
- `true` → store token temporarily, route to verify-email UI (do NOT set token yet)
|
|
157
|
+
- `false` → `client.setCustomerToken(result.token)`, route to account
|
|
158
|
+
4. On verify-email: collect 6-digit code → `client.verifyEmail(code)`. Offer resend via `client.resendVerificationEmail()`.
|
|
159
|
+
5. After `verifyEmail` resolves: `client.setCustomerToken(result.token)`, route to account.
|
|
160
|
+
|
|
161
|
+
> Build the verify-email step even if verification is currently disabled — it auto-hides.
|
|
162
|
+
|
|
163
|
+
### Login flow
|
|
164
|
+
|
|
165
|
+
1. Collect email + password.
|
|
166
|
+
2. Call `loginCustomer`:
|
|
167
|
+
```ts
|
|
168
|
+
const result = await client.loginCustomer(email, password);
|
|
169
|
+
```
|
|
170
|
+
3. Branch on `result.requiresVerification`:
|
|
171
|
+
- `true` → route to verify-email
|
|
172
|
+
- `false` → `client.setCustomerToken(result.token)`, route to previous page or account
|
|
173
|
+
4. Always offer OAuth buttons from `client.getAvailableOAuthProviders()` — render the region even when empty, it auto-populates when a provider is enabled.
|
|
174
|
+
5. Render specific errors (bad credentials, rate limited, disabled) — never swallow them.
|
|
175
|
+
|
|
176
|
+
### Order confirmation flow
|
|
177
|
+
|
|
178
|
+
1. Read `checkoutId` from URL or session.
|
|
179
|
+
2. `await client.handlePaymentSuccess(checkoutId)` — mandatory, clears cart so purchased items don't show on next visit.
|
|
180
|
+
3. `const order = await client.waitForOrder(checkoutId)` — polls until the webhook writes the order.
|
|
181
|
+
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.
|
|
182
|
+
5. On success: render order number and line items from the returned `order` object.
|
|
183
|
+
|
|
184
|
+
### Password reset flow
|
|
185
|
+
|
|
186
|
+
**Forgot password step:** collect email → `client.forgotPassword(email)` → always show a generic success message (prevents account enumeration, regardless of whether the account exists).
|
|
187
|
+
|
|
188
|
+
**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.
|
|
189
|
+
|
|
190
|
+
### OAuth flow
|
|
191
|
+
|
|
192
|
+
1. Get the list of available provider names:
|
|
193
|
+
```ts
|
|
194
|
+
const { providers } = await client.getAvailableOAuthProviders();
|
|
195
|
+
// providers = ['GOOGLE', 'FACEBOOK', 'GITHUB'] (strings, not objects)
|
|
196
|
+
```
|
|
197
|
+
2. For each provider, get the authorization URL:
|
|
198
|
+
```ts
|
|
199
|
+
const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
|
|
200
|
+
redirectUrl: `${window.location.origin}/auth/callback`,
|
|
201
|
+
});
|
|
202
|
+
window.location.href = authorizationUrl; // full-page redirect, NOT a popup
|
|
203
|
+
```
|
|
204
|
+
3. On callback, the URL contains `token` + `oauth_success` (or `oauth_error`) query params:
|
|
205
|
+
```ts
|
|
206
|
+
const token = new URLSearchParams(location.search).get('token');
|
|
207
|
+
if (token) client.setCustomerToken(token); // then redirect to account
|
|
208
|
+
```
|
|
209
|
+
4. On `oauth_error` query param: redirect to login with an error message.
|
|
210
|
+
|
|
211
|
+
> Build the OAuth button region AND the callback handler even when no providers are configured.
|
|
212
|
+
|
|
213
|
+
### Inventory reservation flow
|
|
214
|
+
|
|
215
|
+
- Display the countdown from `cart.reservation?.expiresAt` — refresh once per second (`reservation` is optional; only present when a reservation strategy is active).
|
|
216
|
+
- On expiry: call `client.getCart()` to refresh. Items whose reservation expired are flagged server-side.
|
|
217
|
+
- On the checkout page: if reservation has expired, block payment and show "your cart has expired" with a link back to cart.
|
|
218
|
+
- Do NOT implement your own timer logic — the SDK is the source of truth.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
21
222
|
## Quick Reference - Helper Functions
|
|
22
223
|
|
|
23
224
|
The SDK exports these utility functions for common UI tasks:
|
|
24
225
|
|
|
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`
|
|
226
|
+
| Function | Purpose | Example |
|
|
227
|
+
| ---------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
228
|
+
| `formatPrice(amount, { currency?, locale? })` | Format prices for display | `formatPrice("99.99", { currency: 'USD' })` → `$99.99` |
|
|
229
|
+
| `getPriceDisplay(amount, currency?, locale?)` | Alias for `formatPrice` | Same as above |
|
|
230
|
+
| `getDescriptionContent(product)` | Get product description (HTML or text) | `getDescriptionContent(product)` |
|
|
231
|
+
| `isHtmlDescription(product)` | Check if description is HTML | `isHtmlDescription(product)` → `true/false` |
|
|
232
|
+
| `getStockStatus(inventory)` | Get human-readable stock status | `getStockStatus(inventory)` → `"In Stock"` |
|
|
233
|
+
| `getProductPrice(product)` | Get effective price (handles sales) | `getProductPrice(product)` → `29.99` |
|
|
234
|
+
| `getProductPriceInfo(product)` | Get price + sale info + discount % (falls back to `priceMin` when `basePrice=0` on VARIABLE) | `{ price, isOnSale, discountPercent }` |
|
|
235
|
+
| `getVariantPrice(variant, basePrice)` | Get variant price with fallback | `getVariantPrice(variant, '29.99')` → `34.99` |
|
|
236
|
+
| `getCartTotals(cart, shippingPrice?)` | Calculate cart subtotal/discount/total | `{ subtotal, discount, shipping, total }` |
|
|
237
|
+
| `getCartItemName(item)` | Get name from nested cart item | `getCartItemName(item)` → `"Blue T-Shirt"` |
|
|
238
|
+
| `getCartItemImage(item)` | Get image URL from cart item | `getCartItemImage(item)` → `"https://..."` |
|
|
239
|
+
| `getVariantOptions(variant)` | Get variant attributes as array | `[{ name: "Color", value: "Red" }]` |
|
|
240
|
+
| `isCouponApplicableToProduct(coupon, product)` | Check if coupon applies | `isCouponApplicableToProduct(coupon, product)` |
|
|
241
|
+
| `isAllowedPaymentUrl(url, options?)` | Validate a payment URL host | `isAllowedPaymentUrl(intent.clientSecret)` → `true` |
|
|
242
|
+
| `safePaymentRedirect(url, options?)` | Validate then `window.location.href` | `safePaymentRedirect(intent.clientSecret)` |
|
|
42
243
|
|
|
43
244
|
```typescript
|
|
44
245
|
import {
|
|
@@ -110,9 +311,9 @@ const paypalProvider = providers.find(p => p.provider === 'paypal');
|
|
|
110
311
|
```typescript
|
|
111
312
|
import { BrainerceClient } from 'brainerce';
|
|
112
313
|
|
|
113
|
-
//
|
|
314
|
+
// ✅ salesChannelId (vc_*) is all you need — no API key for storefronts
|
|
114
315
|
const client = new BrainerceClient({
|
|
115
|
-
|
|
316
|
+
salesChannelId: 'vc_YOUR_SALES_CHANNEL_ID', // found in Brainerce dashboard → Sales Channels
|
|
116
317
|
});
|
|
117
318
|
|
|
118
319
|
// Fetch products
|
|
@@ -649,7 +850,7 @@ Create a file `lib/brainerce.ts`:
|
|
|
649
850
|
import { BrainerceClient } from 'brainerce';
|
|
650
851
|
|
|
651
852
|
export const client = new BrainerceClient({
|
|
652
|
-
|
|
853
|
+
salesChannelId: 'vc_YOUR_SALES_CHANNEL_ID', // found in Brainerce dashboard → Sales Channels
|
|
653
854
|
});
|
|
654
855
|
|
|
655
856
|
// ----- Cart Helpers -----
|
|
@@ -1121,6 +1322,9 @@ interface Product {
|
|
|
1121
1322
|
sku: string;
|
|
1122
1323
|
basePrice: number;
|
|
1123
1324
|
salePrice?: number | null;
|
|
1325
|
+
priceMin?: string | null; // Lowest variant price (VARIABLE products only)
|
|
1326
|
+
priceMax?: string | null; // Highest variant price (VARIABLE products only)
|
|
1327
|
+
priceVaries?: boolean; // true when range should be shown ("₪49 – ₪199")
|
|
1124
1328
|
status: 'active' | 'draft';
|
|
1125
1329
|
type: 'SIMPLE' | 'VARIABLE';
|
|
1126
1330
|
images?: ProductImage[];
|
|
@@ -1471,16 +1675,30 @@ console.log(`${count} items in cart`);
|
|
|
1471
1675
|
|
|
1472
1676
|
#### Apply Coupon
|
|
1473
1677
|
|
|
1678
|
+
**On the cart page** (before checkout session is created):
|
|
1679
|
+
|
|
1474
1680
|
```typescript
|
|
1475
1681
|
const cart = await client.smartGetCart();
|
|
1476
1682
|
const updated = await client.applyCoupon(cart.id, 'SAVE20');
|
|
1477
1683
|
console.log(updated.discountAmount); // "10.00"
|
|
1478
1684
|
console.log(updated.couponCode); // "SAVE20"
|
|
1479
1685
|
|
|
1480
|
-
// Remove coupon
|
|
1481
1686
|
await client.removeCoupon(cart.id);
|
|
1482
1687
|
```
|
|
1483
1688
|
|
|
1689
|
+
**On the checkout page** (checkout session already exists — preferred):
|
|
1690
|
+
|
|
1691
|
+
```typescript
|
|
1692
|
+
// applyCheckoutCoupon applies to cart AND updates checkout totals atomically
|
|
1693
|
+
const checkout = await client.applyCheckoutCoupon(checkoutId, 'SAVE20');
|
|
1694
|
+
console.log(checkout.discountAmount); // "10.00"
|
|
1695
|
+
console.log(checkout.total); // updated total
|
|
1696
|
+
|
|
1697
|
+
await client.removeCheckoutCoupon(checkoutId);
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
> **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.
|
|
1701
|
+
|
|
1484
1702
|
#### Cart Totals
|
|
1485
1703
|
|
|
1486
1704
|
```typescript
|
|
@@ -2332,12 +2550,13 @@ function PaymentForm({ checkoutId }: { checkoutId: string }) {
|
|
|
2332
2550
|
}
|
|
2333
2551
|
```
|
|
2334
2552
|
|
|
2335
|
-
#### Complete Order After Payment: `completeGuestCheckout()`
|
|
2553
|
+
#### Complete Order After Payment: `completeGuestCheckout()` (legacy untracked flow only)
|
|
2554
|
+
|
|
2555
|
+
> **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
2556
|
|
|
2337
|
-
**CRITICAL:** After payment succeeds, you MUST call `completeGuestCheckout()` to create the order on the server
|
|
2557
|
+
**CRITICAL (untracked flow only):** After payment succeeds, you MUST call `completeGuestCheckout()` to create the order on the server.
|
|
2338
2558
|
|
|
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!
|
|
2559
|
+
> **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
2560
|
|
|
2342
2561
|
```typescript
|
|
2343
2562
|
// On your /checkout/success page:
|
|
@@ -3014,12 +3233,14 @@ console.log(store.language); // 'en', 'he', etc.
|
|
|
3014
3233
|
|
|
3015
3234
|
## Admin API Reference
|
|
3016
3235
|
|
|
3017
|
-
|
|
3236
|
+
> ⛔ **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)).
|
|
3237
|
+
|
|
3238
|
+
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
3239
|
|
|
3019
3240
|
```typescript
|
|
3020
|
-
//
|
|
3021
|
-
const
|
|
3022
|
-
apiKey: process.env.BRAINERCE_API_KEY, // 'brainerce_*' prefix
|
|
3241
|
+
// ✅ Only in server-side code (Node.js, Edge functions, API routes — never in the browser)
|
|
3242
|
+
const admin = new BrainerceClient({
|
|
3243
|
+
apiKey: process.env.BRAINERCE_API_KEY, // 'brainerce_*' prefix — keep this secret
|
|
3023
3244
|
});
|
|
3024
3245
|
```
|
|
3025
3246
|
|
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';
|
|
@@ -605,7 +611,7 @@ declare function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice
|
|
|
605
611
|
* }
|
|
606
612
|
* ```
|
|
607
613
|
*/
|
|
608
|
-
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount'> | null | undefined): {
|
|
614
|
+
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'> | null | undefined): {
|
|
609
615
|
price: number;
|
|
610
616
|
originalPrice: number;
|
|
611
617
|
isOnSale: boolean;
|
|
@@ -1134,6 +1140,12 @@ interface Coupon {
|
|
|
1134
1140
|
* Returns objects with id and name from backend.
|
|
1135
1141
|
*/
|
|
1136
1142
|
applicableProducts?: EntityRef[] | null;
|
|
1143
|
+
/** Products explicitly excluded from this coupon. */
|
|
1144
|
+
excludedProducts?: EntityRef[] | null;
|
|
1145
|
+
/** Categories this coupon applies to (inclusion list). */
|
|
1146
|
+
applicableCategories?: EntityRef[] | null;
|
|
1147
|
+
/** Categories explicitly excluded from this coupon. */
|
|
1148
|
+
excludedCategories?: EntityRef[] | null;
|
|
1137
1149
|
combinesWithOther: boolean;
|
|
1138
1150
|
/** Whether coupon needs sync to platforms */
|
|
1139
1151
|
needsSync?: boolean;
|
|
@@ -1200,6 +1212,12 @@ interface CreateCouponDto {
|
|
|
1200
1212
|
conditions?: Record<string, unknown>;
|
|
1201
1213
|
/** Product IDs this coupon applies to */
|
|
1202
1214
|
applicableProducts?: string[];
|
|
1215
|
+
/** Product IDs explicitly excluded from this coupon */
|
|
1216
|
+
excludedProducts?: string[];
|
|
1217
|
+
/** Category IDs this coupon applies to */
|
|
1218
|
+
applicableCategories?: string[];
|
|
1219
|
+
/** Category IDs explicitly excluded from this coupon */
|
|
1220
|
+
excludedCategories?: string[];
|
|
1203
1221
|
combinesWithOther?: boolean;
|
|
1204
1222
|
/** Platforms to publish this coupon to */
|
|
1205
1223
|
platforms?: ConnectorPlatform[];
|
|
@@ -1222,6 +1240,9 @@ interface UpdateCouponDto {
|
|
|
1222
1240
|
maximumDiscount?: number | string | null;
|
|
1223
1241
|
conditions?: Record<string, unknown> | null;
|
|
1224
1242
|
applicableProducts?: string[] | null;
|
|
1243
|
+
excludedProducts?: string[] | null;
|
|
1244
|
+
applicableCategories?: string[] | null;
|
|
1245
|
+
excludedCategories?: string[] | null;
|
|
1225
1246
|
combinesWithOther?: boolean;
|
|
1226
1247
|
}
|
|
1227
1248
|
/**
|
|
@@ -6397,6 +6418,35 @@ declare class BrainerceClient {
|
|
|
6397
6418
|
* ```
|
|
6398
6419
|
*/
|
|
6399
6420
|
getCheckout(checkoutId: string): Promise<Checkout>;
|
|
6421
|
+
/**
|
|
6422
|
+
* Apply a coupon code to an existing checkout session.
|
|
6423
|
+
*
|
|
6424
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
6425
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
6426
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
6427
|
+
* created — which is the common case when the customer enters a code on the
|
|
6428
|
+
* checkout page.
|
|
6429
|
+
*
|
|
6430
|
+
* @example
|
|
6431
|
+
* ```typescript
|
|
6432
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
6433
|
+
* console.log('New total:', checkout.total);
|
|
6434
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
6435
|
+
* ```
|
|
6436
|
+
*/
|
|
6437
|
+
applyCheckoutCoupon(checkoutId: string, code: string): Promise<Checkout>;
|
|
6438
|
+
/**
|
|
6439
|
+
* Remove a coupon from an existing checkout session.
|
|
6440
|
+
*
|
|
6441
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
6442
|
+
*
|
|
6443
|
+
* @example
|
|
6444
|
+
* ```typescript
|
|
6445
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
6446
|
+
* console.log('New total:', checkout.total);
|
|
6447
|
+
* ```
|
|
6448
|
+
*/
|
|
6449
|
+
removeCheckoutCoupon(checkoutId: string): Promise<Checkout>;
|
|
6400
6450
|
/**
|
|
6401
6451
|
* Set customer information on checkout
|
|
6402
6452
|
*
|
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';
|
|
@@ -605,7 +611,7 @@ declare function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice
|
|
|
605
611
|
* }
|
|
606
612
|
* ```
|
|
607
613
|
*/
|
|
608
|
-
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount'> | null | undefined): {
|
|
614
|
+
declare function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'> | null | undefined): {
|
|
609
615
|
price: number;
|
|
610
616
|
originalPrice: number;
|
|
611
617
|
isOnSale: boolean;
|
|
@@ -1134,6 +1140,12 @@ interface Coupon {
|
|
|
1134
1140
|
* Returns objects with id and name from backend.
|
|
1135
1141
|
*/
|
|
1136
1142
|
applicableProducts?: EntityRef[] | null;
|
|
1143
|
+
/** Products explicitly excluded from this coupon. */
|
|
1144
|
+
excludedProducts?: EntityRef[] | null;
|
|
1145
|
+
/** Categories this coupon applies to (inclusion list). */
|
|
1146
|
+
applicableCategories?: EntityRef[] | null;
|
|
1147
|
+
/** Categories explicitly excluded from this coupon. */
|
|
1148
|
+
excludedCategories?: EntityRef[] | null;
|
|
1137
1149
|
combinesWithOther: boolean;
|
|
1138
1150
|
/** Whether coupon needs sync to platforms */
|
|
1139
1151
|
needsSync?: boolean;
|
|
@@ -1200,6 +1212,12 @@ interface CreateCouponDto {
|
|
|
1200
1212
|
conditions?: Record<string, unknown>;
|
|
1201
1213
|
/** Product IDs this coupon applies to */
|
|
1202
1214
|
applicableProducts?: string[];
|
|
1215
|
+
/** Product IDs explicitly excluded from this coupon */
|
|
1216
|
+
excludedProducts?: string[];
|
|
1217
|
+
/** Category IDs this coupon applies to */
|
|
1218
|
+
applicableCategories?: string[];
|
|
1219
|
+
/** Category IDs explicitly excluded from this coupon */
|
|
1220
|
+
excludedCategories?: string[];
|
|
1203
1221
|
combinesWithOther?: boolean;
|
|
1204
1222
|
/** Platforms to publish this coupon to */
|
|
1205
1223
|
platforms?: ConnectorPlatform[];
|
|
@@ -1222,6 +1240,9 @@ interface UpdateCouponDto {
|
|
|
1222
1240
|
maximumDiscount?: number | string | null;
|
|
1223
1241
|
conditions?: Record<string, unknown> | null;
|
|
1224
1242
|
applicableProducts?: string[] | null;
|
|
1243
|
+
excludedProducts?: string[] | null;
|
|
1244
|
+
applicableCategories?: string[] | null;
|
|
1245
|
+
excludedCategories?: string[] | null;
|
|
1225
1246
|
combinesWithOther?: boolean;
|
|
1226
1247
|
}
|
|
1227
1248
|
/**
|
|
@@ -6397,6 +6418,35 @@ declare class BrainerceClient {
|
|
|
6397
6418
|
* ```
|
|
6398
6419
|
*/
|
|
6399
6420
|
getCheckout(checkoutId: string): Promise<Checkout>;
|
|
6421
|
+
/**
|
|
6422
|
+
* Apply a coupon code to an existing checkout session.
|
|
6423
|
+
*
|
|
6424
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
6425
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
6426
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
6427
|
+
* created — which is the common case when the customer enters a code on the
|
|
6428
|
+
* checkout page.
|
|
6429
|
+
*
|
|
6430
|
+
* @example
|
|
6431
|
+
* ```typescript
|
|
6432
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
6433
|
+
* console.log('New total:', checkout.total);
|
|
6434
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
6435
|
+
* ```
|
|
6436
|
+
*/
|
|
6437
|
+
applyCheckoutCoupon(checkoutId: string, code: string): Promise<Checkout>;
|
|
6438
|
+
/**
|
|
6439
|
+
* Remove a coupon from an existing checkout session.
|
|
6440
|
+
*
|
|
6441
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
6442
|
+
*
|
|
6443
|
+
* @example
|
|
6444
|
+
* ```typescript
|
|
6445
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
6446
|
+
* console.log('New total:', checkout.total);
|
|
6447
|
+
* ```
|
|
6448
|
+
*/
|
|
6449
|
+
removeCheckoutCoupon(checkoutId: string): Promise<Checkout>;
|
|
6400
6450
|
/**
|
|
6401
6451
|
* Set customer information on checkout
|
|
6402
6452
|
*
|
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_"');
|
|
@@ -3575,6 +3573,69 @@ var BrainerceClient = class {
|
|
|
3575
3573
|
"checkout"
|
|
3576
3574
|
);
|
|
3577
3575
|
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Apply a coupon code to an existing checkout session.
|
|
3578
|
+
*
|
|
3579
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
3580
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
3581
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
3582
|
+
* created — which is the common case when the customer enters a code on the
|
|
3583
|
+
* checkout page.
|
|
3584
|
+
*
|
|
3585
|
+
* @example
|
|
3586
|
+
* ```typescript
|
|
3587
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
3588
|
+
* console.log('New total:', checkout.total);
|
|
3589
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
3590
|
+
* ```
|
|
3591
|
+
*/
|
|
3592
|
+
async applyCheckoutCoupon(checkoutId, code) {
|
|
3593
|
+
if (this.isVibeCodedMode()) {
|
|
3594
|
+
return this.withGuards(
|
|
3595
|
+
this.vibeCodedRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3596
|
+
"checkout"
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
if (this.storeId && !this.apiKey) {
|
|
3600
|
+
return this.withGuards(
|
|
3601
|
+
this.storefrontRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3602
|
+
"checkout"
|
|
3603
|
+
);
|
|
3604
|
+
}
|
|
3605
|
+
return this.withGuards(
|
|
3606
|
+
this.adminRequest("POST", `/api/v1/checkout/${checkoutId}/coupon`, { code }),
|
|
3607
|
+
"checkout"
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Remove a coupon from an existing checkout session.
|
|
3612
|
+
*
|
|
3613
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
3614
|
+
*
|
|
3615
|
+
* @example
|
|
3616
|
+
* ```typescript
|
|
3617
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
3618
|
+
* console.log('New total:', checkout.total);
|
|
3619
|
+
* ```
|
|
3620
|
+
*/
|
|
3621
|
+
async removeCheckoutCoupon(checkoutId) {
|
|
3622
|
+
if (this.isVibeCodedMode()) {
|
|
3623
|
+
return this.withGuards(
|
|
3624
|
+
this.vibeCodedRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3625
|
+
"checkout"
|
|
3626
|
+
);
|
|
3627
|
+
}
|
|
3628
|
+
if (this.storeId && !this.apiKey) {
|
|
3629
|
+
return this.withGuards(
|
|
3630
|
+
this.storefrontRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3631
|
+
"checkout"
|
|
3632
|
+
);
|
|
3633
|
+
}
|
|
3634
|
+
return this.withGuards(
|
|
3635
|
+
this.adminRequest("DELETE", `/api/v1/checkout/${checkoutId}/coupon`),
|
|
3636
|
+
"checkout"
|
|
3637
|
+
);
|
|
3638
|
+
}
|
|
3578
3639
|
/**
|
|
3579
3640
|
* Set customer information on checkout
|
|
3580
3641
|
*
|
|
@@ -6972,6 +7033,7 @@ function getProductPriceInfo(product) {
|
|
|
6972
7033
|
if (!product) {
|
|
6973
7034
|
return { price: 0, originalPrice: 0, isOnSale: false, discountAmount: 0, discountPercent: 0 };
|
|
6974
7035
|
}
|
|
7036
|
+
const resolvedBasePrice = parseFloat(product.basePrice) === 0 && product.priceMin ? product.priceMin : product.basePrice;
|
|
6975
7037
|
if (product.discount) {
|
|
6976
7038
|
const ruleOriginal = parseFloat(product.discount.originalPrice) || 0;
|
|
6977
7039
|
const ruleDiscounted = parseFloat(product.discount.discountedPrice) || 0;
|
|
@@ -6985,7 +7047,7 @@ function getProductPriceInfo(product) {
|
|
|
6985
7047
|
discountPercent: rulePercent
|
|
6986
7048
|
};
|
|
6987
7049
|
}
|
|
6988
|
-
const basePrice = parseFloat(
|
|
7050
|
+
const basePrice = parseFloat(resolvedBasePrice) || 0;
|
|
6989
7051
|
const salePrice = product.salePrice ? parseFloat(product.salePrice) : null;
|
|
6990
7052
|
const isOnSale = salePrice !== null && salePrice < basePrice;
|
|
6991
7053
|
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_"');
|
|
@@ -3512,6 +3510,69 @@ var BrainerceClient = class {
|
|
|
3512
3510
|
"checkout"
|
|
3513
3511
|
);
|
|
3514
3512
|
}
|
|
3513
|
+
/**
|
|
3514
|
+
* Apply a coupon code to an existing checkout session.
|
|
3515
|
+
*
|
|
3516
|
+
* The coupon is validated, applied to the linked cart, and the checkout
|
|
3517
|
+
* totals (discountAmount, totalAmount) are updated atomically.
|
|
3518
|
+
* Use this instead of `applyCoupon()` when the checkout session is already
|
|
3519
|
+
* created — which is the common case when the customer enters a code on the
|
|
3520
|
+
* checkout page.
|
|
3521
|
+
*
|
|
3522
|
+
* @example
|
|
3523
|
+
* ```typescript
|
|
3524
|
+
* const checkout = await client.applyCheckoutCoupon('checkout_123', 'SAVE20');
|
|
3525
|
+
* console.log('New total:', checkout.total);
|
|
3526
|
+
* console.log('Discount:', checkout.discountAmount);
|
|
3527
|
+
* ```
|
|
3528
|
+
*/
|
|
3529
|
+
async applyCheckoutCoupon(checkoutId, code) {
|
|
3530
|
+
if (this.isVibeCodedMode()) {
|
|
3531
|
+
return this.withGuards(
|
|
3532
|
+
this.vibeCodedRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3533
|
+
"checkout"
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
if (this.storeId && !this.apiKey) {
|
|
3537
|
+
return this.withGuards(
|
|
3538
|
+
this.storefrontRequest("POST", `/checkout/${checkoutId}/coupon`, { code }),
|
|
3539
|
+
"checkout"
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3542
|
+
return this.withGuards(
|
|
3543
|
+
this.adminRequest("POST", `/api/v1/checkout/${checkoutId}/coupon`, { code }),
|
|
3544
|
+
"checkout"
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* Remove a coupon from an existing checkout session.
|
|
3549
|
+
*
|
|
3550
|
+
* Clears the coupon from the linked cart and recalculates the checkout totals.
|
|
3551
|
+
*
|
|
3552
|
+
* @example
|
|
3553
|
+
* ```typescript
|
|
3554
|
+
* const checkout = await client.removeCheckoutCoupon('checkout_123');
|
|
3555
|
+
* console.log('New total:', checkout.total);
|
|
3556
|
+
* ```
|
|
3557
|
+
*/
|
|
3558
|
+
async removeCheckoutCoupon(checkoutId) {
|
|
3559
|
+
if (this.isVibeCodedMode()) {
|
|
3560
|
+
return this.withGuards(
|
|
3561
|
+
this.vibeCodedRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3562
|
+
"checkout"
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
if (this.storeId && !this.apiKey) {
|
|
3566
|
+
return this.withGuards(
|
|
3567
|
+
this.storefrontRequest("DELETE", `/checkout/${checkoutId}/coupon`),
|
|
3568
|
+
"checkout"
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
return this.withGuards(
|
|
3572
|
+
this.adminRequest("DELETE", `/api/v1/checkout/${checkoutId}/coupon`),
|
|
3573
|
+
"checkout"
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3515
3576
|
/**
|
|
3516
3577
|
* Set customer information on checkout
|
|
3517
3578
|
*
|
|
@@ -6909,6 +6970,7 @@ function getProductPriceInfo(product) {
|
|
|
6909
6970
|
if (!product) {
|
|
6910
6971
|
return { price: 0, originalPrice: 0, isOnSale: false, discountAmount: 0, discountPercent: 0 };
|
|
6911
6972
|
}
|
|
6973
|
+
const resolvedBasePrice = parseFloat(product.basePrice) === 0 && product.priceMin ? product.priceMin : product.basePrice;
|
|
6912
6974
|
if (product.discount) {
|
|
6913
6975
|
const ruleOriginal = parseFloat(product.discount.originalPrice) || 0;
|
|
6914
6976
|
const ruleDiscounted = parseFloat(product.discount.discountedPrice) || 0;
|
|
@@ -6922,7 +6984,7 @@ function getProductPriceInfo(product) {
|
|
|
6922
6984
|
discountPercent: rulePercent
|
|
6923
6985
|
};
|
|
6924
6986
|
}
|
|
6925
|
-
const basePrice = parseFloat(
|
|
6987
|
+
const basePrice = parseFloat(resolvedBasePrice) || 0;
|
|
6926
6988
|
const salePrice = product.salePrice ? parseFloat(product.salePrice) : null;
|
|
6927
6989
|
const isOnSale = salePrice !== null && salePrice < basePrice;
|
|
6928
6990
|
const effectivePrice = isOnSale ? salePrice : basePrice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainerce",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.14",
|
|
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",
|