brainerce 1.27.1 → 1.28.1
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 +228 -4
- package/dist/index.d.mts +458 -14
- package/dist/index.d.ts +458 -14
- package/dist/index.js +1235 -492
- package/dist/index.mjs +1232 -492
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Violating any of these causes production incidents or broken orders. Read them b
|
|
|
93
93
|
|
|
94
94
|
- Customer auth tokens (`result.token` from `loginCustomer`/`registerCustomer`) should be passed to `client.setCustomerToken(token)`. The SDK stores session state internally.
|
|
95
95
|
- NEVER put the admin API key (`brainerce_*`) in client code. It is a server-only secret.
|
|
96
|
-
- OAuth callbacks arrive with
|
|
96
|
+
- OAuth callbacks arrive with a one-time `auth_code` URL param. Call `client.exchangeOAuthCode(authCode)` to swap it for the JWT and apply via `setCustomerToken`. (The legacy `?token=` URL param is still emitted for backward compatibility but will be removed in the next major release.)
|
|
97
97
|
|
|
98
98
|
### i18n
|
|
99
99
|
|
|
@@ -205,11 +205,16 @@ These sequences are non-negotiable. The order of SDK calls matters.
|
|
|
205
205
|
});
|
|
206
206
|
window.location.href = authorizationUrl; // full-page redirect, NOT a popup
|
|
207
207
|
```
|
|
208
|
-
3. On callback, the URL contains `
|
|
208
|
+
3. On callback, the URL contains `auth_code` + `oauth_success` (or `oauth_error`) query params. Exchange the single-use code for the JWT:
|
|
209
209
|
```ts
|
|
210
|
-
const
|
|
211
|
-
|
|
210
|
+
const params = new URLSearchParams(location.search);
|
|
211
|
+
const code = params.get('auth_code');
|
|
212
|
+
if (code) {
|
|
213
|
+
const result = await client.exchangeOAuthCode(code);
|
|
214
|
+
client.setCustomerToken(result.token); // then redirect to account
|
|
215
|
+
}
|
|
212
216
|
```
|
|
217
|
+
The legacy `?token=` URL param is still emitted for backward compatibility but will be removed in the next major release — migrate to `auth_code` now.
|
|
213
218
|
4. On `oauth_error` query param: redirect to login with an error message.
|
|
214
219
|
|
|
215
220
|
> Build the OAuth button region AND the callback handler even when no providers are configured.
|
|
@@ -1936,6 +1941,11 @@ console.log(cart.discountAmount); // Discount applied
|
|
|
1936
1941
|
console.log(cart.couponCode); // 'SAVE20'
|
|
1937
1942
|
```
|
|
1938
1943
|
|
|
1944
|
+
> **Region-restricted coupons:** a coupon may be limited to specific regions via
|
|
1945
|
+
> its `regionIds`. If the buyer's checkout region isn't in that list, redemption is
|
|
1946
|
+
> rejected even when the code is otherwise valid. An empty/omitted `regionIds`
|
|
1947
|
+
> applies in all regions. See [`/docs/concepts/region-coupons`](/docs/concepts/region-coupons).
|
|
1948
|
+
|
|
1939
1949
|
#### Remove Coupon
|
|
1940
1950
|
|
|
1941
1951
|
```typescript
|
|
@@ -1994,6 +2004,52 @@ const checkout = await client.createCheckout({
|
|
|
1994
2004
|
});
|
|
1995
2005
|
```
|
|
1996
2006
|
|
|
2007
|
+
#### Multi-Region Checkout
|
|
2008
|
+
|
|
2009
|
+
If your store uses [regions](#regions), pass a `regionId` to associate the
|
|
2010
|
+
checkout with one (the region must belong to the store). Resolve the buyer's
|
|
2011
|
+
country to a region client-side with `detectRegion`:
|
|
2012
|
+
|
|
2013
|
+
```typescript
|
|
2014
|
+
const { data: regions } = await client.getRegions(); // or a storefront region list
|
|
2015
|
+
const region = client.detectRegion(buyerCountry, regions);
|
|
2016
|
+
|
|
2017
|
+
const checkout = await client.createCheckout({
|
|
2018
|
+
cartId: cart.id,
|
|
2019
|
+
regionId: region?.id, // omit to associate no region
|
|
2020
|
+
});
|
|
2021
|
+
```
|
|
2022
|
+
|
|
2023
|
+
> The region is recorded on the checkout and used for payment-provider scoping.
|
|
2024
|
+
> Currency continues to follow the cart until FX price conversion lands, so
|
|
2025
|
+
> passing a `regionId` does not re-price the cart.
|
|
2026
|
+
|
|
2027
|
+
#### Region display pricing (`getProducts({ regionId })`)
|
|
2028
|
+
|
|
2029
|
+
Product reads accept an optional `regionId`. When set, each product/variant that
|
|
2030
|
+
a region [price list](#price-lists) resolves gains additive fields —
|
|
2031
|
+
`resolvedPrice`, `resolvedCurrency`, `resolvedCompareAtPrice`, and `priceSource`
|
|
2032
|
+
(`'variant-entry'` | `'product-entry'`). Your existing `basePrice` / `salePrice`
|
|
2033
|
+
stay in the store currency; the fields appear **only** when a region entry
|
|
2034
|
+
actually applies, so an omitted/invalid region (or a catalog-price fallback)
|
|
2035
|
+
leaves the response byte-identical.
|
|
2036
|
+
|
|
2037
|
+
```typescript
|
|
2038
|
+
const { data: products } = await client.getProducts({ regionId: 'region_eu' });
|
|
2039
|
+
const p = products[0];
|
|
2040
|
+
const display = p.resolvedPrice
|
|
2041
|
+
? `${p.resolvedPrice} ${p.resolvedCurrency}` // e.g. "27.00 EUR"
|
|
2042
|
+
: p.basePrice; // store-currency catalog price
|
|
2043
|
+
|
|
2044
|
+
// Single product (id or slug) takes the same option:
|
|
2045
|
+
await client.getProduct('prod_tshirt', { regionId: 'region_eu' });
|
|
2046
|
+
```
|
|
2047
|
+
|
|
2048
|
+
> **Display-only** — like checkout, `regionId` here does not charge the region
|
|
2049
|
+
> price; it only changes what you render until currency-lock ships. **Mode note:**
|
|
2050
|
+
> region resolution applies on storefront (`storeId`) and admin (`apiKey`) reads;
|
|
2051
|
+
> it is currently ignored in vibe-coded (`vc_*`/`salesChannelId`) mode.
|
|
2052
|
+
|
|
1997
2053
|
#### Partial Checkout (AliExpress-style)
|
|
1998
2054
|
|
|
1999
2055
|
Allow customers to select which items to checkout from their cart. Only selected items are purchased - remaining items stay in the cart for later.
|
|
@@ -3394,6 +3450,14 @@ const zone = await client.createShippingZone({
|
|
|
3394
3450
|
countries: ['US'],
|
|
3395
3451
|
});
|
|
3396
3452
|
|
|
3453
|
+
// Restrict a zone to specific regions (PRD §25). Empty/omitted = any region;
|
|
3454
|
+
// a non-empty list hides the zone unless the checkout resolved to one of them.
|
|
3455
|
+
const euZone = await client.createShippingZone({
|
|
3456
|
+
name: 'EU Express',
|
|
3457
|
+
countries: ['DE', 'FR', 'IT'],
|
|
3458
|
+
regionIds: ['reg_eu'],
|
|
3459
|
+
});
|
|
3460
|
+
|
|
3397
3461
|
// Shipping Rates
|
|
3398
3462
|
const rates = await client.getZoneShippingRates('zone_id');
|
|
3399
3463
|
await client.createZoneShippingRate('zone_id', {
|
|
@@ -3406,14 +3470,168 @@ await client.createZoneShippingRate('zone_id', {
|
|
|
3406
3470
|
|
|
3407
3471
|
### Tax Configuration
|
|
3408
3472
|
|
|
3473
|
+
`rate` is a **whole percentage** (`7.25` = 7.25%, not `0.0725`). A rate may target
|
|
3474
|
+
a [tax class](#tax-classes) via `taxClassId`; a rate with `taxClassId: null` (the
|
|
3475
|
+
default) is the **Standard fallback** applied to any line whose class has no
|
|
3476
|
+
class-specific rate.
|
|
3477
|
+
|
|
3409
3478
|
```typescript
|
|
3410
3479
|
const rates = await client.getTaxRates();
|
|
3480
|
+
|
|
3481
|
+
// Standard rate (applies to everything without a class-specific rate)
|
|
3411
3482
|
await client.createTaxRate({
|
|
3412
3483
|
name: 'CA Sales Tax',
|
|
3413
3484
|
rate: 7.25,
|
|
3414
3485
|
country: 'US',
|
|
3415
3486
|
region: 'CA',
|
|
3416
3487
|
});
|
|
3488
|
+
|
|
3489
|
+
// Class-specific rate — only lines assigned to this tax class use it.
|
|
3490
|
+
// Create the class in the dashboard (Settings → Tax) to get its id.
|
|
3491
|
+
await client.createTaxRate({
|
|
3492
|
+
name: 'CA Reduced (Food)',
|
|
3493
|
+
rate: 0,
|
|
3494
|
+
country: 'US',
|
|
3495
|
+
region: 'CA',
|
|
3496
|
+
taxClassId: 'taxclass_food_id',
|
|
3497
|
+
});
|
|
3498
|
+
```
|
|
3499
|
+
|
|
3500
|
+
### Tax Classes
|
|
3501
|
+
|
|
3502
|
+
Tax classes let you charge different rates for different product types (e.g.
|
|
3503
|
+
Standard / Reduced / Zero-rated / Food). Assign a class to a product, variant, or
|
|
3504
|
+
category; checkout resolves each line's class **variant → product → category →
|
|
3505
|
+
store default → null** and picks the matching `TaxRate`, falling back to the
|
|
3506
|
+
Standard (null-class) rate. See [`/docs/concepts/tax-classes`](/docs/concepts/tax-classes)
|
|
3507
|
+
for the full resolution order. `storeId` is derived from the API key. Requires
|
|
3508
|
+
the `tax-classes:read` / `tax-classes:write` scopes.
|
|
3509
|
+
|
|
3510
|
+
```typescript
|
|
3511
|
+
// List / inspect
|
|
3512
|
+
const { data: classes } = await client.getTaxClasses();
|
|
3513
|
+
const detail = await client.getTaxClass('taxclass_id');
|
|
3514
|
+
detail.dependents; // { productCount, variantCount, categoryCount, taxRateCount }
|
|
3515
|
+
|
|
3516
|
+
// Create (slug is kebab-case, unique per store)
|
|
3517
|
+
const food = await client.createTaxClass({
|
|
3518
|
+
name: 'Food',
|
|
3519
|
+
slug: 'food',
|
|
3520
|
+
description: 'Zero / reduced-rate groceries',
|
|
3521
|
+
});
|
|
3522
|
+
|
|
3523
|
+
await client.updateTaxClass(food.id, { description: 'Reduced VAT' });
|
|
3524
|
+
await client.setDefaultTaxClass(food.id); // the class auto-applied to unclassified products
|
|
3525
|
+
|
|
3526
|
+
// Bulk-assign a class to products / variants / categories
|
|
3527
|
+
const { updated } = await client.assignTaxClass(food.id, {
|
|
3528
|
+
productIds: ['prod_1', 'prod_2'],
|
|
3529
|
+
categoryIds: ['cat_groceries'],
|
|
3530
|
+
});
|
|
3531
|
+
|
|
3532
|
+
// Merge moves every product/variant/category/rate FK onto the target, then
|
|
3533
|
+
// deletes this class. Delete is blocked (409) while dependents exist — merge first.
|
|
3534
|
+
await client.mergeTaxClasses(food.id, 'taxclass_standard_id');
|
|
3535
|
+
await client.deleteTaxClass(food.id);
|
|
3536
|
+
```
|
|
3537
|
+
|
|
3538
|
+
**Storefront (public, no API key).** A storefront lists classes in `storeId`
|
|
3539
|
+
mode — storefront-safe fields only (for a "9% VAT" transparency badge):
|
|
3540
|
+
|
|
3541
|
+
```typescript
|
|
3542
|
+
const store = new BrainerceClient({ storeId: 'store_123' });
|
|
3543
|
+
const { data: classes } = await store.getStoreTaxClasses();
|
|
3544
|
+
```
|
|
3545
|
+
|
|
3546
|
+
### Regions
|
|
3547
|
+
|
|
3548
|
+
A **region** binds a set of countries to a currency, a tax-display mode
|
|
3549
|
+
(`taxInclusive`), and the payment providers enabled there. `storeId` is derived
|
|
3550
|
+
from the API key. Requires the `regions:read` / `regions:write` scopes. See
|
|
3551
|
+
[`/docs/concepts/regions`](/docs/concepts/regions).
|
|
3552
|
+
|
|
3553
|
+
```typescript
|
|
3554
|
+
// List / inspect
|
|
3555
|
+
const { data: regions } = await client.getRegions();
|
|
3556
|
+
const region = await client.getRegion('region_id');
|
|
3557
|
+
|
|
3558
|
+
// Create — paymentProviderIds are AppInstallation IDs to enable in this region
|
|
3559
|
+
const eu = await client.createRegion({
|
|
3560
|
+
name: 'European Union',
|
|
3561
|
+
currency: 'EUR',
|
|
3562
|
+
countries: ['DE', 'FR', 'IT', 'ES'],
|
|
3563
|
+
taxInclusive: true,
|
|
3564
|
+
paymentProviderIds: ['app_inst_stripe'],
|
|
3565
|
+
});
|
|
3566
|
+
|
|
3567
|
+
await client.updateRegion(eu.id, { isActive: true });
|
|
3568
|
+
await client.setDefaultRegion(eu.id);
|
|
3569
|
+
|
|
3570
|
+
// Manage the country set
|
|
3571
|
+
await client.addRegionCountries(eu.id, ['NL', 'BE']);
|
|
3572
|
+
await client.removeRegionCountry(eu.id, 'BE');
|
|
3573
|
+
|
|
3574
|
+
// Manage payment providers (replaces the full set)
|
|
3575
|
+
await client.updateRegionPaymentProviders(eu.id, ['app_inst_stripe', 'app_inst_paypal']);
|
|
3576
|
+
|
|
3577
|
+
// Which installed providers can serve this region's countries?
|
|
3578
|
+
const compatible = await client.getRegionCompatibleProviders(eu.id);
|
|
3579
|
+
|
|
3580
|
+
// Pure client-side helper (no network) — pick a region for a country code
|
|
3581
|
+
const dest = client.detectRegion('DE', regions); // → the EU region, or the default, or null
|
|
3582
|
+
|
|
3583
|
+
await client.deleteRegion(eu.id);
|
|
3584
|
+
```
|
|
3585
|
+
|
|
3586
|
+
**Storefront (public, no API key).** A storefront fetches regions in `storeId`
|
|
3587
|
+
mode — only active regions, only storefront-safe fields:
|
|
3588
|
+
|
|
3589
|
+
```typescript
|
|
3590
|
+
const store = new BrainerceClient({ storeId: 'store_123' });
|
|
3591
|
+
|
|
3592
|
+
const { data: regions } = await store.getStoreRegions();
|
|
3593
|
+
const region = await store.getStoreRegion(regions[0].id); // + paymentProviders
|
|
3594
|
+
const dest = store.detectRegion('DE', regions); // pick by country, client-side
|
|
3595
|
+
```
|
|
3596
|
+
|
|
3597
|
+
> **Multi-region checkout:** pass a `regionId` to `createCheckout` to associate the
|
|
3598
|
+
> checkout with a region (for reporting + payment-provider scoping). The region
|
|
3599
|
+
> must belong to the store. Currency still follows the cart until FX price
|
|
3600
|
+
> conversion lands. See the Checkout section.
|
|
3601
|
+
|
|
3602
|
+
### Price Lists (per-region pricing)
|
|
3603
|
+
|
|
3604
|
+
Each region has a **price list** — entries that override a product's or variant's
|
|
3605
|
+
catalog price for buyers in that region. Requires `price-lists:read` /
|
|
3606
|
+
`price-lists:write`. Prices are in the region's currency.
|
|
3607
|
+
|
|
3608
|
+
> ⚠️ **Data-entry only for now.** Region prices are **not yet applied at checkout** —
|
|
3609
|
+
> charging a region-currency price through the store-currency cart needs FX
|
|
3610
|
+
> conversion (a later phase). These methods let you populate the price book ahead of
|
|
3611
|
+
> activation; until then checkout uses the catalog price.
|
|
3612
|
+
|
|
3613
|
+
```typescript
|
|
3614
|
+
// One entry per product OR variant (variant wins). price > 0; compareAtPrice optional.
|
|
3615
|
+
const res = await client.upsertPriceListEntries('region_eu', [
|
|
3616
|
+
{ productId: 'prod_tshirt', price: 27, compareAtPrice: 32 },
|
|
3617
|
+
{ variantId: 'var_tshirt_l', price: 29 },
|
|
3618
|
+
]);
|
|
3619
|
+
res.upserted; // 2
|
|
3620
|
+
res.warnings; // e.g. ["PL-004: compareAtPrice not greater than price …"] (non-blocking)
|
|
3621
|
+
|
|
3622
|
+
// Inspect
|
|
3623
|
+
const meta = await client.getPriceList('region_eu'); // { currency, entryCount, … }
|
|
3624
|
+
const { data: entries } = await client.listPriceListEntries('region_eu', {
|
|
3625
|
+
productId: 'prod_tshirt',
|
|
3626
|
+
});
|
|
3627
|
+
|
|
3628
|
+
// Bulk CSV (columns: productSku,variantSku?,price,compareAtPrice?). Unknown SKUs /
|
|
3629
|
+
// bad prices are skipped + reported, not fatal.
|
|
3630
|
+
const imported = await client.importPrices('region_eu', csvString);
|
|
3631
|
+
imported.skipped; // ['row 4: product SKU "NOPE" not found', …]
|
|
3632
|
+
const csv = await client.exportPrices('region_eu'); // round-trips with importPrices
|
|
3633
|
+
|
|
3634
|
+
await client.deletePriceListEntry('region_eu', 'entry_id'); // line falls back to catalog
|
|
3417
3635
|
```
|
|
3418
3636
|
|
|
3419
3637
|
### Metafield Definitions
|
|
@@ -3495,6 +3713,7 @@ const { members, invitations } = await client.getStoreTeam('store_id');
|
|
|
3495
3713
|
const invitation = await client.inviteStoreMember('store_id', {
|
|
3496
3714
|
email: 'newmember@example.com',
|
|
3497
3715
|
role: 'MANAGER', // 'MANAGER' | 'STAFF' | 'VIEWER'
|
|
3716
|
+
salesChannelIds: ['vc_abc123'], // optional: restrict to vibe-coded channels (connectionId); omit/[] = all channels
|
|
3498
3717
|
});
|
|
3499
3718
|
|
|
3500
3719
|
// Update member role or set custom permissions
|
|
@@ -3503,6 +3722,11 @@ await client.updateStoreMember('store_id', 'member_id', {
|
|
|
3503
3722
|
permissions: ['VIEW_PRODUCTS', 'VIEW_ORDERS', 'FULFILL_ORDERS'], // overrides role defaults
|
|
3504
3723
|
});
|
|
3505
3724
|
|
|
3725
|
+
// Replace a member's vibe-coded sales-channel scope ([] clears all restrictions)
|
|
3726
|
+
await client.updateStoreMemberSalesChannels('store_id', 'member_id', {
|
|
3727
|
+
salesChannelIds: ['vc_abc123', 'vc_def456'],
|
|
3728
|
+
});
|
|
3729
|
+
|
|
3506
3730
|
// Remove a member
|
|
3507
3731
|
await client.removeStoreMember('store_id', 'member_id');
|
|
3508
3732
|
|