brainerce 2.0.0 → 2.1.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 +757 -295
- package/dist/index.d.mts +956 -186
- package/dist/index.d.ts +956 -186
- package/dist/index.js +454 -130
- package/dist/index.mjs +454 -130
- package/package.json +84 -84
package/dist/index.mjs
CHANGED
|
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// src/version.ts
|
|
118
|
-
var SDK_VERSION = "2.0.
|
|
118
|
+
var SDK_VERSION = "2.0.3";
|
|
119
119
|
|
|
120
120
|
// src/client.ts
|
|
121
121
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -674,7 +674,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
674
674
|
}
|
|
675
675
|
if (!options.salesChannelId && options.connectionId) {
|
|
676
676
|
console.warn(
|
|
677
|
-
"BrainerceClient: `connectionId` is deprecated \u2014 use `salesChannelId` instead. `connectionId`
|
|
677
|
+
"BrainerceClient: `connectionId` is deprecated \u2014 use `salesChannelId` instead. `connectionId` is a permanent backward-compat alias and is not scheduled for removal."
|
|
678
678
|
);
|
|
679
679
|
}
|
|
680
680
|
if (options.apiKey && typeof window !== "undefined") {
|
|
@@ -1132,6 +1132,45 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
1132
1132
|
getStoreDirection(locale) {
|
|
1133
1133
|
return getDirectionForLocale(locale ?? this.locale);
|
|
1134
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Get what the merchant actually configured on this sales channel: store
|
|
1137
|
+
* identity and multi-language setup, the channel's own settings (low-stock
|
|
1138
|
+
* warning and threshold, reservation strategy and timeout, birthday and
|
|
1139
|
+
* email-verification requirements, granted scopes) and which optional
|
|
1140
|
+
* features are switched on (payment providers, social login, coupons,
|
|
1141
|
+
* discount rules, shipping zones, downloadables, content, loyalty).
|
|
1142
|
+
*
|
|
1143
|
+
* Read this instead of hardcoding. A storefront with a hardcoded low-stock
|
|
1144
|
+
* threshold shows the wrong badge on every store whose merchant chose a
|
|
1145
|
+
* different one, and a storefront that renders a feature nobody enabled
|
|
1146
|
+
* ships a control that silently does nothing.
|
|
1147
|
+
*
|
|
1148
|
+
* Only available in vibe-coded mode (`salesChannelId`). The payload belongs
|
|
1149
|
+
* to one sales channel: `storeId` mode has no channel to read it from, and
|
|
1150
|
+
* an `apiKey` addresses the store rather than any single channel.
|
|
1151
|
+
*
|
|
1152
|
+
* Call it once for the whole app and share the result. It is per channel,
|
|
1153
|
+
* not per product, so fetching it on every page is wasted work.
|
|
1154
|
+
*
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* const caps = await client.getStoreCapabilities();
|
|
1158
|
+
*
|
|
1159
|
+
* // Low stock: honour the switch before the number.
|
|
1160
|
+
* const threshold = caps.connection.lowStockWarning
|
|
1161
|
+
* ? caps.connection.lowStockThreshold
|
|
1162
|
+
* : 0;
|
|
1163
|
+
*
|
|
1164
|
+
* if (caps.features.hasCoupons) renderCouponInput();
|
|
1165
|
+
* if (caps.store.i18n?.enabled) renderLocaleSwitcher(caps.store.i18n.supportedLocales);
|
|
1166
|
+
* ```
|
|
1167
|
+
*/
|
|
1168
|
+
async getStoreCapabilities() {
|
|
1169
|
+
if (this.isVibeCodedMode()) {
|
|
1170
|
+
return this.vibeCodedRequest("GET", "/capabilities");
|
|
1171
|
+
}
|
|
1172
|
+
throw new BrainerceError("getStoreCapabilities is only available in vibe-coded mode", 400);
|
|
1173
|
+
}
|
|
1135
1174
|
// -------------------- Analytics --------------------
|
|
1136
1175
|
/**
|
|
1137
1176
|
* Send a storefront analytics beacon (pageview or engagement).
|
|
@@ -2550,6 +2589,36 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
2550
2589
|
async getOrderShipments(orderId) {
|
|
2551
2590
|
return this.request("GET", `/api/v1/orders/${encodePathSegment(orderId)}/shipments`);
|
|
2552
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Buy a return label the merchant sends to their customer to print.
|
|
2594
|
+
*
|
|
2595
|
+
* Requires admin mode (`apiKey`) with `FULFILL_ORDERS` permission — it
|
|
2596
|
+
* spends the store's carrier balance, same as {@link createShippingLabel}.
|
|
2597
|
+
*
|
|
2598
|
+
* Unlike {@link createShippingLabel}, this is **not** on the API-key `/v1`
|
|
2599
|
+
* surface — it calls the internal `/api/orders/:id/shipments/return-label`
|
|
2600
|
+
* route, which takes `storeId` explicitly rather than resolving it from the
|
|
2601
|
+
* key. There is no `rateId` in the body: a return is quoted and bought in
|
|
2602
|
+
* one call at the shipping app, because the carrier fixes a shipment as a
|
|
2603
|
+
* return when it is created and will not amend it afterwards.
|
|
2604
|
+
*
|
|
2605
|
+
* @example
|
|
2606
|
+
* ```typescript
|
|
2607
|
+
* const label = await client.createReturnLabel('store_abc', 'order_abc', {
|
|
2608
|
+
* reason: 'Wrong size',
|
|
2609
|
+
* returnForShipmentId: 'shp_original123',
|
|
2610
|
+
* });
|
|
2611
|
+
* console.log('Return label URL:', label.labelUrl);
|
|
2612
|
+
* ```
|
|
2613
|
+
*/
|
|
2614
|
+
async createReturnLabel(storeId, orderId, data) {
|
|
2615
|
+
return this.adminRequest(
|
|
2616
|
+
"POST",
|
|
2617
|
+
`/api/orders/${encodePathSegment(orderId)}/shipments/return-label`,
|
|
2618
|
+
data,
|
|
2619
|
+
{ storeId }
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2553
2622
|
/**
|
|
2554
2623
|
* Cancel an order.
|
|
2555
2624
|
*
|
|
@@ -2929,14 +2998,20 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
2929
2998
|
});
|
|
2930
2999
|
}
|
|
2931
3000
|
/**
|
|
2932
|
-
* Get platform capabilities for coupon features.
|
|
2933
|
-
*
|
|
3001
|
+
* Get platform capabilities for coupon features, keyed by platform.
|
|
3002
|
+
*
|
|
3003
|
+
* ⛔ **Returns an empty object today.** Platform coupon capabilities moved
|
|
3004
|
+
* into the standalone connector apps and are not re-exposed here yet, so the
|
|
3005
|
+
* endpoint answers `{}` for every store. Treat a missing key as "unknown",
|
|
3006
|
+
* never as "the platform lacks the feature", and do not index into a platform
|
|
3007
|
+
* key without checking it exists first — there are none to find.
|
|
2934
3008
|
*
|
|
2935
3009
|
* @example
|
|
2936
3010
|
* ```typescript
|
|
2937
3011
|
* const capabilities = await client.getCouponPlatformCapabilities();
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
3012
|
+
* const meta = capabilities['GOOGLE'];
|
|
3013
|
+
* if (meta && !meta.supportsProducts) {
|
|
3014
|
+
* console.log('This platform cannot target individual products');
|
|
2940
3015
|
* }
|
|
2941
3016
|
* ```
|
|
2942
3017
|
*/
|
|
@@ -3062,7 +3137,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
3062
3137
|
);
|
|
3063
3138
|
}
|
|
3064
3139
|
console.warn(
|
|
3065
|
-
`BrainerceClient.${methodName}: passing \`storeId\` is deprecated \u2014 it is now derived from the SDK config. Remove the leading argument; this overload will be removed in
|
|
3140
|
+
`BrainerceClient.${methodName}: passing \`storeId\` is deprecated \u2014 it is now derived from the SDK config. Remove the leading argument; this overload will be removed in a future major version.`
|
|
3066
3141
|
);
|
|
3067
3142
|
}
|
|
3068
3143
|
/**
|
|
@@ -5557,6 +5632,164 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
5557
5632
|
"checkout"
|
|
5558
5633
|
);
|
|
5559
5634
|
}
|
|
5635
|
+
/**
|
|
5636
|
+
* Apply a gift card to a checkout.
|
|
5637
|
+
*
|
|
5638
|
+
* Merchants issue cards from the dashboard, so live codes exist and this field
|
|
5639
|
+
* is worth building. There is still no way for a shopper to BUY a gift card —
|
|
5640
|
+
* no product type, no purchase flow — so every card in circulation was issued
|
|
5641
|
+
* by hand.
|
|
5642
|
+
*
|
|
5643
|
+
* Redemption is deliberately NOT gated on the store's gift-card switch: a
|
|
5644
|
+
* store that turned the feature off still owes every card already in a
|
|
5645
|
+
* customer's hand, so the field keeps working. Build it unconditionally.
|
|
5646
|
+
*
|
|
5647
|
+
* A gift card is a **means of payment, not a discount**. The order total does
|
|
5648
|
+
* not change and tax stays calculated on the full value; what changes is
|
|
5649
|
+
* `providerAmountDue`, the amount the payment provider will be charged.
|
|
5650
|
+
*
|
|
5651
|
+
* Render it as its own line — "Gift card −₪54.50" beside the total — and NOT
|
|
5652
|
+
* by adding it to `discountAmount`. A shopper who sees stored value folded
|
|
5653
|
+
* into a discount is being shown the wrong thing, and so is their receipt.
|
|
5654
|
+
*
|
|
5655
|
+
* Only as much of the card as the order still owes is applied, so a card
|
|
5656
|
+
* larger than the basket leaves a balance on it for next time, and a smaller
|
|
5657
|
+
* one leaves an amount for the provider to charge.
|
|
5658
|
+
*
|
|
5659
|
+
* @example
|
|
5660
|
+
* const { amountApplied, providerAmountDue, tenderId } =
|
|
5661
|
+
* await client.applyGiftCard('checkout_123', 'A1B2-C3D4-E5F6-G7H8-J9K0');
|
|
5662
|
+
* // total is unchanged; charge the provider providerAmountDue
|
|
5663
|
+
*/
|
|
5664
|
+
async applyGiftCard(checkoutId, code) {
|
|
5665
|
+
if (this.isVibeCodedMode()) {
|
|
5666
|
+
return this.vibeCodedRequest(
|
|
5667
|
+
"POST",
|
|
5668
|
+
`/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5669
|
+
{ code }
|
|
5670
|
+
);
|
|
5671
|
+
}
|
|
5672
|
+
if (this.storeId && !this.apiKey) {
|
|
5673
|
+
return this.storefrontRequest(
|
|
5674
|
+
"POST",
|
|
5675
|
+
`/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5676
|
+
{ code }
|
|
5677
|
+
);
|
|
5678
|
+
}
|
|
5679
|
+
return this.adminRequest(
|
|
5680
|
+
"POST",
|
|
5681
|
+
`/api/v1/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5682
|
+
{ code }
|
|
5683
|
+
);
|
|
5684
|
+
}
|
|
5685
|
+
/**
|
|
5686
|
+
* Remove a previously applied gift card from a checkout.
|
|
5687
|
+
*
|
|
5688
|
+
* Takes the `tenderId` returned by {@link applyGiftCard}, not the code — a
|
|
5689
|
+
* checkout can carry more than one card, and the code is never echoed back.
|
|
5690
|
+
*
|
|
5691
|
+
* The held value goes straight back to the card. Nothing was ever debited
|
|
5692
|
+
* while it was applied, so removing costs the shopper nothing.
|
|
5693
|
+
*/
|
|
5694
|
+
async removeGiftCard(checkoutId, tenderId) {
|
|
5695
|
+
const path = `/checkout/${encodePathSegment(checkoutId)}/gift-card/${encodePathSegment(tenderId)}`;
|
|
5696
|
+
if (this.isVibeCodedMode()) {
|
|
5697
|
+
return this.vibeCodedRequest("DELETE", path);
|
|
5698
|
+
}
|
|
5699
|
+
if (this.storeId && !this.apiKey) {
|
|
5700
|
+
return this.storefrontRequest("DELETE", path);
|
|
5701
|
+
}
|
|
5702
|
+
return this.adminRequest("DELETE", `/api/v1${path}`);
|
|
5703
|
+
}
|
|
5704
|
+
/**
|
|
5705
|
+
* Check what is left on a gift card.
|
|
5706
|
+
*
|
|
5707
|
+
* Rate limited, and deliberately uninformative: a code that does not exist,
|
|
5708
|
+
* one that has been disabled and one that has expired all return the SAME
|
|
5709
|
+
* response — `{ balance: '0.00', usable: false }` — and take the same time to
|
|
5710
|
+
* do it. Do not build UI that tries to tell those apart, because the API will
|
|
5711
|
+
* not tell you, by design: a gift-card code is bearer value, and an endpoint
|
|
5712
|
+
* that confirmed which codes were real would be a free way to find them.
|
|
5713
|
+
*
|
|
5714
|
+
* Show "we cannot use this code" and let the shopper re-enter it.
|
|
5715
|
+
*/
|
|
5716
|
+
async checkGiftCardBalance(code) {
|
|
5717
|
+
if (this.isVibeCodedMode()) {
|
|
5718
|
+
return this.vibeCodedRequest("POST", "/gift-cards/balance", { code });
|
|
5719
|
+
}
|
|
5720
|
+
if (this.storeId && !this.apiKey) {
|
|
5721
|
+
return this.storefrontRequest("POST", "/gift-cards/balance", { code });
|
|
5722
|
+
}
|
|
5723
|
+
return this.adminRequest("POST", "/api/v1/gift-cards/balance", { code });
|
|
5724
|
+
}
|
|
5725
|
+
// ==========================================================================
|
|
5726
|
+
// Donations
|
|
5727
|
+
// ==========================================================================
|
|
5728
|
+
/**
|
|
5729
|
+
* Start a donation.
|
|
5730
|
+
*
|
|
5731
|
+
* A donation does not go through the cart. There is no line item, no
|
|
5732
|
+
* quantity, no shipping and no order — a donor names an amount and pays it,
|
|
5733
|
+
* which is a different shape of transaction from a purchase. Do not model a
|
|
5734
|
+
* donation as a product; if you already have, the amount is the giveaway:
|
|
5735
|
+
* you cannot let a donor type one.
|
|
5736
|
+
*
|
|
5737
|
+
* ⛔ A successful return is NOT a completed gift. You get back a PENDING
|
|
5738
|
+
* donation and a provider intent to complete, exactly as with a checkout.
|
|
5739
|
+
* The gift is only real once the provider's webhook confirms it, which is
|
|
5740
|
+
* when the donation reads back as `PAID`. Show a thank-you that reflects
|
|
5741
|
+
* that, and never send a receipt off the back of this call.
|
|
5742
|
+
*
|
|
5743
|
+
* Requires donations to be switched on for the store; a store that has not
|
|
5744
|
+
* turned them on is rejected rather than silently accepting money.
|
|
5745
|
+
*
|
|
5746
|
+
* ```ts
|
|
5747
|
+
* const donation = await brainerce.createDonation({
|
|
5748
|
+
* amount: 180,
|
|
5749
|
+
* feeCoverAmount: 6.3, // offered as a checkbox; most donors accept
|
|
5750
|
+
* donorEmail: 'sarah@example.com',
|
|
5751
|
+
* donorName: 'Sarah Cohen',
|
|
5752
|
+
* tributeType: 'IN_MEMORY',
|
|
5753
|
+
* tributeName: 'Avraham Cohen',
|
|
5754
|
+
* returnPath: '/thank-you',
|
|
5755
|
+
* });
|
|
5756
|
+
* // → complete donation.payment with your provider, then poll getDonation()
|
|
5757
|
+
* ```
|
|
5758
|
+
*/
|
|
5759
|
+
async createDonation(input) {
|
|
5760
|
+
if (this.isVibeCodedMode()) {
|
|
5761
|
+
return this.vibeCodedRequest("POST", "/donations", input);
|
|
5762
|
+
}
|
|
5763
|
+
if (this.storeId && !this.apiKey) {
|
|
5764
|
+
return this.storefrontRequest("POST", "/donations", input);
|
|
5765
|
+
}
|
|
5766
|
+
throw new Error(
|
|
5767
|
+
"createDonation is a storefront call. Initialise the SDK with a salesChannelId or storeId \u2014 an admin apiKey cannot start a donation."
|
|
5768
|
+
);
|
|
5769
|
+
}
|
|
5770
|
+
/**
|
|
5771
|
+
* Read a donation back — for a thank-you page, and to see whether it is paid.
|
|
5772
|
+
*
|
|
5773
|
+
* Rate limited to 5 requests a minute, because an id that either resolves or
|
|
5774
|
+
* 404s is a way to enumerate them. Poll it a handful of times after the donor
|
|
5775
|
+
* returns from the provider; do not put it behind a 1-second interval.
|
|
5776
|
+
*
|
|
5777
|
+
* The payload is narrow on purpose: no failure reason, and `donorName` comes
|
|
5778
|
+
* back `null` whenever the gift was marked anonymous — so you can render this
|
|
5779
|
+
* straight onto a public page without leaking anything.
|
|
5780
|
+
*/
|
|
5781
|
+
async getDonation(donationId) {
|
|
5782
|
+
const path = `/donations/${encodePathSegment(donationId)}`;
|
|
5783
|
+
if (this.isVibeCodedMode()) {
|
|
5784
|
+
return this.vibeCodedRequest("GET", path);
|
|
5785
|
+
}
|
|
5786
|
+
if (this.storeId && !this.apiKey) {
|
|
5787
|
+
return this.storefrontRequest("GET", path);
|
|
5788
|
+
}
|
|
5789
|
+
throw new Error(
|
|
5790
|
+
"getDonation is a storefront call. Initialise the SDK with a salesChannelId or storeId."
|
|
5791
|
+
);
|
|
5792
|
+
}
|
|
5560
5793
|
/**
|
|
5561
5794
|
* Set customer information on checkout
|
|
5562
5795
|
*
|
|
@@ -7908,119 +8141,6 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
7908
8141
|
}
|
|
7909
8142
|
return this.storefrontRequest("GET", "/customers/me/cart");
|
|
7910
8143
|
}
|
|
7911
|
-
// -------------------- Custom API Integrations --------------------
|
|
7912
|
-
// These methods require Admin mode (apiKey)
|
|
7913
|
-
/**
|
|
7914
|
-
* Get all Custom API integrations for a store
|
|
7915
|
-
* Requires Admin mode (apiKey)
|
|
7916
|
-
*
|
|
7917
|
-
* @example
|
|
7918
|
-
* ```typescript
|
|
7919
|
-
* const integrations = await client.getCustomApiIntegrations();
|
|
7920
|
-
* integrations.forEach(api => {
|
|
7921
|
-
* console.log(`${api.name}: ${api.status}`);
|
|
7922
|
-
* });
|
|
7923
|
-
* ```
|
|
7924
|
-
*/
|
|
7925
|
-
async getCustomApiIntegrations() {
|
|
7926
|
-
return this.adminRequest("GET", "/api/v1/custom-api");
|
|
7927
|
-
}
|
|
7928
|
-
/**
|
|
7929
|
-
* Get a single Custom API integration by ID
|
|
7930
|
-
* Requires Admin mode (apiKey)
|
|
7931
|
-
*
|
|
7932
|
-
* @example
|
|
7933
|
-
* ```typescript
|
|
7934
|
-
* const api = await client.getCustomApiIntegration('api_123');
|
|
7935
|
-
* console.log(`API: ${api.name}, URL: ${api.baseUrl}`);
|
|
7936
|
-
* ```
|
|
7937
|
-
*/
|
|
7938
|
-
async getCustomApiIntegration(integrationId) {
|
|
7939
|
-
return this.adminRequest(
|
|
7940
|
-
"GET",
|
|
7941
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`
|
|
7942
|
-
);
|
|
7943
|
-
}
|
|
7944
|
-
/**
|
|
7945
|
-
* Create a new Custom API integration
|
|
7946
|
-
* Requires Admin mode (apiKey)
|
|
7947
|
-
*
|
|
7948
|
-
* @example
|
|
7949
|
-
* ```typescript
|
|
7950
|
-
* const api = await client.createCustomApiIntegration({
|
|
7951
|
-
* name: 'My External API',
|
|
7952
|
-
* baseUrl: 'https://api.example.com',
|
|
7953
|
-
* authType: 'api_key',
|
|
7954
|
-
* credentials: {
|
|
7955
|
-
* apiKey: 'sk_123...',
|
|
7956
|
-
* headerName: 'X-API-Key',
|
|
7957
|
-
* },
|
|
7958
|
-
* syncDirection: 'bidirectional',
|
|
7959
|
-
* syncConfig: {
|
|
7960
|
-
* products: true,
|
|
7961
|
-
* orders: true,
|
|
7962
|
-
* inventory: true,
|
|
7963
|
-
* },
|
|
7964
|
-
* });
|
|
7965
|
-
* ```
|
|
7966
|
-
*/
|
|
7967
|
-
async createCustomApiIntegration(data) {
|
|
7968
|
-
return this.adminRequest("POST", "/api/v1/custom-api", data);
|
|
7969
|
-
}
|
|
7970
|
-
/**
|
|
7971
|
-
* Update a Custom API integration
|
|
7972
|
-
* Requires Admin mode (apiKey)
|
|
7973
|
-
*
|
|
7974
|
-
* @example
|
|
7975
|
-
* ```typescript
|
|
7976
|
-
* const api = await client.updateCustomApiIntegration('api_123', {
|
|
7977
|
-
* enabled: false,
|
|
7978
|
-
* syncConfig: { products: true, orders: false, inventory: true },
|
|
7979
|
-
* });
|
|
7980
|
-
* ```
|
|
7981
|
-
*/
|
|
7982
|
-
async updateCustomApiIntegration(integrationId, data) {
|
|
7983
|
-
return this.adminRequest(
|
|
7984
|
-
"PATCH",
|
|
7985
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`,
|
|
7986
|
-
data
|
|
7987
|
-
);
|
|
7988
|
-
}
|
|
7989
|
-
/**
|
|
7990
|
-
* Delete a Custom API integration
|
|
7991
|
-
* Requires Admin mode (apiKey)
|
|
7992
|
-
*
|
|
7993
|
-
* @example
|
|
7994
|
-
* ```typescript
|
|
7995
|
-
* await client.deleteCustomApiIntegration('api_123');
|
|
7996
|
-
* ```
|
|
7997
|
-
*/
|
|
7998
|
-
async deleteCustomApiIntegration(integrationId) {
|
|
7999
|
-
await this.adminRequest(
|
|
8000
|
-
"DELETE",
|
|
8001
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`
|
|
8002
|
-
);
|
|
8003
|
-
}
|
|
8004
|
-
/**
|
|
8005
|
-
* Test connection to a Custom API
|
|
8006
|
-
* Requires Admin mode (apiKey)
|
|
8007
|
-
*
|
|
8008
|
-
* @example
|
|
8009
|
-
* ```typescript
|
|
8010
|
-
* const result = await client.testCustomApiConnection('api_123');
|
|
8011
|
-
* if (result.success) {
|
|
8012
|
-
* console.log(`Connection OK, latency: ${result.latency}ms`);
|
|
8013
|
-
* } else {
|
|
8014
|
-
* console.error(`Connection failed: ${result.error}`);
|
|
8015
|
-
* }
|
|
8016
|
-
* ```
|
|
8017
|
-
*/
|
|
8018
|
-
async testCustomApiConnection(integrationId) {
|
|
8019
|
-
return this.adminRequest(
|
|
8020
|
-
"POST",
|
|
8021
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}/test`
|
|
8022
|
-
);
|
|
8023
|
-
}
|
|
8024
8144
|
// -------------------- Inventory Reservations --------------------
|
|
8025
8145
|
// These methods are only available in vibe-coded mode when reservation strategy
|
|
8026
8146
|
// is set to ON_CART or ON_CHECKOUT
|
|
@@ -8660,12 +8780,18 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8660
8780
|
* ```typescript
|
|
8661
8781
|
* const rate = await client.createZoneShippingRate('zone_123', {
|
|
8662
8782
|
* name: 'Standard Shipping',
|
|
8663
|
-
* type: '
|
|
8664
|
-
*
|
|
8665
|
-
*
|
|
8666
|
-
*
|
|
8783
|
+
* type: 'FLAT_RATE',
|
|
8784
|
+
* rateConfig: { amount: 5.99 },
|
|
8785
|
+
* minDeliveryDays: 3,
|
|
8786
|
+
* maxDeliveryDays: 5,
|
|
8667
8787
|
* });
|
|
8668
8788
|
* ```
|
|
8789
|
+
*
|
|
8790
|
+
* The price lives in `rateConfig`, whose shape follows `type` — `FLAT_RATE`
|
|
8791
|
+
* takes `{ amount }`, `WEIGHT_BASED` and `PRICE_BASED` take tier arrays, and
|
|
8792
|
+
* `FREE` and `LOCAL_PICKUP` take none. Unknown top-level properties are
|
|
8793
|
+
* rejected outright rather than ignored, so a stray `price` or
|
|
8794
|
+
* `estimatedDays` fails the whole call with 400.
|
|
8669
8795
|
*/
|
|
8670
8796
|
async createZoneShippingRate(zoneId, data) {
|
|
8671
8797
|
return this.adminRequest(
|
|
@@ -8866,6 +8992,10 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8866
8992
|
*
|
|
8867
8993
|
* Returns `appliesTax=false` when tax is disabled, the country is missing,
|
|
8868
8994
|
* or no active rate covers it.
|
|
8995
|
+
*
|
|
8996
|
+
* The preview has no province, so it only sees country-level rates: a
|
|
8997
|
+
* Canadian estimate shows the federal GST alone and the provincial PST/QST
|
|
8998
|
+
* joins it at checkout. Render `note` so the buyer is not surprised.
|
|
8869
8999
|
*/
|
|
8870
9000
|
async estimateTax(params) {
|
|
8871
9001
|
const query = params.country ? { country: params.country, subtotal: params.subtotal } : { subtotal: params.subtotal };
|
|
@@ -8936,10 +9066,46 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8936
9066
|
/**
|
|
8937
9067
|
* Get all tax rates for the store
|
|
8938
9068
|
* Requires Admin mode (apiKey)
|
|
9069
|
+
*
|
|
9070
|
+
* A jurisdiction can need more than one rate. Canada is the common case: a
|
|
9071
|
+
* country-level `GST` row plus a province-level `PST`/`QST` row, both with
|
|
9072
|
+
* `stackable: true`, which the checkout charges together. Provinces on HST
|
|
9073
|
+
* carry a single combined row with `stackable: false`.
|
|
8939
9074
|
*/
|
|
8940
9075
|
async getTaxRates() {
|
|
8941
9076
|
return this.adminRequest("GET", "/api/v1/tax/rates");
|
|
8942
9077
|
}
|
|
9078
|
+
/**
|
|
9079
|
+
* List the country tax presets available to apply.
|
|
9080
|
+
* Requires Admin mode (apiKey)
|
|
9081
|
+
*/
|
|
9082
|
+
async getTaxPresets() {
|
|
9083
|
+
return this.adminRequest("GET", "/api/v1/tax/presets");
|
|
9084
|
+
}
|
|
9085
|
+
/**
|
|
9086
|
+
* Apply a country's whole tax table in one call, instead of creating a rate
|
|
9087
|
+
* per province by hand. Requires Admin mode (apiKey).
|
|
9088
|
+
*
|
|
9089
|
+
* `CA` writes ten rates: federal GST 5% country-wide, one combined HST row
|
|
9090
|
+
* each for ON/NB/NL/NS/PE, and PST/RST/QST for BC/SK/MB/QC charged on top of
|
|
9091
|
+
* the GST. Alberta and the territories need no row — the GST covers them.
|
|
9092
|
+
*
|
|
9093
|
+
* Throws 409 when the store already has rates for that country; delete those
|
|
9094
|
+
* first if you meant to replace them. Rates land in the Standard tax class.
|
|
9095
|
+
*
|
|
9096
|
+
* Brainerce does not register the store for GST/HST and does not file returns.
|
|
9097
|
+
*
|
|
9098
|
+
* @example
|
|
9099
|
+
* ```typescript
|
|
9100
|
+
* const { created } = await client.applyTaxPreset('CA'); // created === 10
|
|
9101
|
+
* ```
|
|
9102
|
+
*/
|
|
9103
|
+
async applyTaxPreset(presetKey) {
|
|
9104
|
+
return this.adminRequest(
|
|
9105
|
+
"POST",
|
|
9106
|
+
`/api/v1/tax/presets/${encodePathSegment(presetKey)}/apply`
|
|
9107
|
+
);
|
|
9108
|
+
}
|
|
8943
9109
|
/**
|
|
8944
9110
|
* Get a single tax rate by ID
|
|
8945
9111
|
* Requires Admin mode (apiKey)
|
|
@@ -9985,6 +10151,156 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9985
10151
|
`/api/v1/oauth-providers/${encodePathSegment(provider)}`
|
|
9986
10152
|
);
|
|
9987
10153
|
}
|
|
10154
|
+
// -------------------- Translations (Admin) --------------------
|
|
10155
|
+
// These methods require Admin mode (apiKey) with a scope-bearing key —
|
|
10156
|
+
// `products:read`/`products:write` cover most entity types via
|
|
10157
|
+
// StorePermissionGuard's STORE_PERMISSION_TO_SCOPE mapping (VIEW_PRODUCTS /
|
|
10158
|
+
// EDIT_PRODUCTS). Routes: /api/stores/:storeId/translations/...
|
|
10159
|
+
//
|
|
10160
|
+
// Reads and writes the SAME `translations` JSON blob the dashboard editor
|
|
10161
|
+
// uses — this is not a separate translation system, it's API-key access to
|
|
10162
|
+
// the dashboard's own persistence path. Use it for bulk-importing
|
|
10163
|
+
// pre-translated content or automating locale coverage without a human in
|
|
10164
|
+
// the dashboard.
|
|
10165
|
+
/**
|
|
10166
|
+
* Get translation completeness across every translatable entity type, for
|
|
10167
|
+
* one or more locales. Useful as a pre-flight before a bulk import to see
|
|
10168
|
+
* which entity types/locales still need coverage.
|
|
10169
|
+
* Requires Admin mode (apiKey).
|
|
10170
|
+
*
|
|
10171
|
+
* @param storeId - Store to inspect.
|
|
10172
|
+
* @param locales - BCP-47 locale codes to check (e.g. `['he', 'fr']`). Omit
|
|
10173
|
+
* or pass an empty array to get rows with `total` populated but no
|
|
10174
|
+
* locale breakdown.
|
|
10175
|
+
*
|
|
10176
|
+
* @example
|
|
10177
|
+
* ```typescript
|
|
10178
|
+
* const status = await client.getTranslationStatus('store_123', ['he', 'fr']);
|
|
10179
|
+
* const blogHe = status.find((s) => s.entityType === 'blogPost' && s.locale === 'he');
|
|
10180
|
+
* console.log(`${blogHe?.missing} blog posts still need Hebrew`);
|
|
10181
|
+
* ```
|
|
10182
|
+
*/
|
|
10183
|
+
async getTranslationStatus(storeId, locales) {
|
|
10184
|
+
return this.adminRequest(
|
|
10185
|
+
"GET",
|
|
10186
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/status`,
|
|
10187
|
+
void 0,
|
|
10188
|
+
locales && locales.length > 0 ? { locales: locales.join(",") } : void 0
|
|
10189
|
+
);
|
|
10190
|
+
}
|
|
10191
|
+
/**
|
|
10192
|
+
* Get every persisted translation for a single entity, keyed by locale.
|
|
10193
|
+
* Requires Admin mode (apiKey).
|
|
10194
|
+
*
|
|
10195
|
+
* @example
|
|
10196
|
+
* ```typescript
|
|
10197
|
+
* const translations = await client.getTranslations('store_123', 'product', 'prod_abc');
|
|
10198
|
+
* console.log(translations.he?.name); // Hebrew product name, if set
|
|
10199
|
+
* ```
|
|
10200
|
+
*/
|
|
10201
|
+
async getTranslations(storeId, entityType, entityId) {
|
|
10202
|
+
return this.adminRequest(
|
|
10203
|
+
"GET",
|
|
10204
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}`
|
|
10205
|
+
);
|
|
10206
|
+
}
|
|
10207
|
+
/**
|
|
10208
|
+
* Set/update one locale's translation for an entity. Only the fields valid
|
|
10209
|
+
* for `entityType` are persisted (e.g. `title`/`excerpt`/`content` for
|
|
10210
|
+
* `blogPost`, `name`/`description` for `category`) — fields outside that
|
|
10211
|
+
* entity's allowlist are silently ignored server-side, and omitted fields
|
|
10212
|
+
* leave any existing translation for them untouched (this is a merge, not
|
|
10213
|
+
* a replace, of the locale's fields).
|
|
10214
|
+
* Requires Admin mode (apiKey) with `products:write` (or the equivalent
|
|
10215
|
+
* scope for the target entity type).
|
|
10216
|
+
*
|
|
10217
|
+
* @example
|
|
10218
|
+
* ```typescript
|
|
10219
|
+
* // Bulk-import a pre-translated blog post
|
|
10220
|
+
* await client.setTranslation('store_123', 'blogPost', 'post_abc', 'fr', {
|
|
10221
|
+
* title: 'Le titre en français',
|
|
10222
|
+
* excerpt: "L'extrait en français",
|
|
10223
|
+
* content: '<p>Le contenu en français</p>',
|
|
10224
|
+
* });
|
|
10225
|
+
* ```
|
|
10226
|
+
*/
|
|
10227
|
+
async setTranslation(storeId, entityType, entityId, locale, fields) {
|
|
10228
|
+
return this.adminRequest(
|
|
10229
|
+
"PUT",
|
|
10230
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}/${encodePathSegment(locale)}`,
|
|
10231
|
+
fields
|
|
10232
|
+
);
|
|
10233
|
+
}
|
|
10234
|
+
/**
|
|
10235
|
+
* Delete one locale's translation for an entity. The entity's base
|
|
10236
|
+
* (default-locale) fields are unaffected.
|
|
10237
|
+
* Requires Admin mode (apiKey) with `products:write` (or the equivalent
|
|
10238
|
+
* scope for the target entity type).
|
|
10239
|
+
*/
|
|
10240
|
+
async deleteTranslation(storeId, entityType, entityId, locale) {
|
|
10241
|
+
await this.adminRequest(
|
|
10242
|
+
"DELETE",
|
|
10243
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}/${encodePathSegment(locale)}`
|
|
10244
|
+
);
|
|
10245
|
+
}
|
|
10246
|
+
/**
|
|
10247
|
+
* AI-translate a single entity into one target locale and persist the
|
|
10248
|
+
* result inline (synchronous — the response already reflects the write).
|
|
10249
|
+
* Only fields that are still empty for `targetLocale` are filled; existing
|
|
10250
|
+
* translated values are never overwritten.
|
|
10251
|
+
* Requires Admin mode (apiKey) with `products:write` (or the equivalent
|
|
10252
|
+
* scope for the target entity type).
|
|
10253
|
+
*
|
|
10254
|
+
* @param sourceFields - Optional override of the source-language text to
|
|
10255
|
+
* translate from (e.g. unsaved edits from an open editor), instead of the
|
|
10256
|
+
* entity's persisted base fields. Keys outside the entity's translatable
|
|
10257
|
+
* field set are ignored.
|
|
10258
|
+
*
|
|
10259
|
+
* @example
|
|
10260
|
+
* ```typescript
|
|
10261
|
+
* const translations = await client.aiTranslateSingle('store_123', {
|
|
10262
|
+
* entityType: 'product',
|
|
10263
|
+
* entityId: 'prod_abc',
|
|
10264
|
+
* targetLocale: 'he',
|
|
10265
|
+
* });
|
|
10266
|
+
* ```
|
|
10267
|
+
*/
|
|
10268
|
+
async aiTranslateSingle(storeId, input) {
|
|
10269
|
+
return this.adminRequest(
|
|
10270
|
+
"POST",
|
|
10271
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/ai-translate-single`,
|
|
10272
|
+
input
|
|
10273
|
+
);
|
|
10274
|
+
}
|
|
10275
|
+
/**
|
|
10276
|
+
* Bulk AI-translate — enqueues a background job per entity (and, for
|
|
10277
|
+
* `entityType: 'attribute'`, one per attribute option too) rather than
|
|
10278
|
+
* translating inline. Returns the number of jobs queued, not the finished
|
|
10279
|
+
* translations; poll `getTranslationStatus` or `getTranslations` to see
|
|
10280
|
+
* results land.
|
|
10281
|
+
* Requires Admin mode (apiKey) with `products:write` (or the equivalent
|
|
10282
|
+
* scope for the target entity type).
|
|
10283
|
+
*
|
|
10284
|
+
* @param entityIds - Optional explicit ids to translate. Omit to target
|
|
10285
|
+
* every entity of `entityType` in the store that isn't already fully
|
|
10286
|
+
* translated for `targetLocale`.
|
|
10287
|
+
*
|
|
10288
|
+
* @example
|
|
10289
|
+
* ```typescript
|
|
10290
|
+
* // Translate every blog post missing French coverage
|
|
10291
|
+
* const { queued } = await client.aiTranslateBulk('store_123', {
|
|
10292
|
+
* entityType: 'blogPost',
|
|
10293
|
+
* targetLocale: 'fr',
|
|
10294
|
+
* });
|
|
10295
|
+
* ```
|
|
10296
|
+
*/
|
|
10297
|
+
async aiTranslateBulk(storeId, input) {
|
|
10298
|
+
return this.adminRequest(
|
|
10299
|
+
"POST",
|
|
10300
|
+
`/api/stores/${encodePathSegment(storeId)}/translations/ai-translate`,
|
|
10301
|
+
input
|
|
10302
|
+
);
|
|
10303
|
+
}
|
|
9988
10304
|
};
|
|
9989
10305
|
/**
|
|
9990
10306
|
* Fields present on `getAddressDetails().address` that the address endpoints
|
|
@@ -10217,9 +10533,7 @@ function validateDateAvailabilityConfig(config, fieldType, surface = "checkout")
|
|
|
10217
10533
|
(k) => config[k] !== void 0
|
|
10218
10534
|
);
|
|
10219
10535
|
if (relativeKeys.length > 0) {
|
|
10220
|
-
errors.push(
|
|
10221
|
-
`${relativeKeys.join("/")} only apply to checkout fields, not ${surface} fields`
|
|
10222
|
-
);
|
|
10536
|
+
errors.push(`${relativeKeys.join("/")} only apply to checkout fields, not ${surface} fields`);
|
|
10223
10537
|
}
|
|
10224
10538
|
}
|
|
10225
10539
|
if (config.blockedWeekdays) {
|
|
@@ -10242,7 +10556,9 @@ function validateDateAvailabilityConfig(config, fieldType, surface = "checkout")
|
|
|
10242
10556
|
const windowsByWeekday = /* @__PURE__ */ new Map();
|
|
10243
10557
|
for (const w of config.businessHours) {
|
|
10244
10558
|
if (!Number.isInteger(w.weekday) || w.weekday < 0 || w.weekday > 6) {
|
|
10245
|
-
errors.push(
|
|
10559
|
+
errors.push(
|
|
10560
|
+
`businessHours.weekday must be an integer 0-6, got ${JSON.stringify(w.weekday)}`
|
|
10561
|
+
);
|
|
10246
10562
|
continue;
|
|
10247
10563
|
}
|
|
10248
10564
|
const openValid = TIME_RE.test(w.open);
|
|
@@ -10354,7 +10670,8 @@ function addCalendarDays(dateYYYYMMDD, days) {
|
|
|
10354
10670
|
function parseDateFieldValue(raw, fieldType, timezone) {
|
|
10355
10671
|
const str = raw instanceof Date ? Number.isNaN(raw.getTime()) ? "" : raw.toISOString() : typeof raw === "string" ? raw.trim() : "";
|
|
10356
10672
|
const expected = fieldType === "DATE" ? "expected a calendar date in YYYY-MM-DD format" : "expected an ISO-8601 date/time such as 2026-08-13T13:00:00+03:00";
|
|
10357
|
-
if (!str)
|
|
10673
|
+
if (!str)
|
|
10674
|
+
return { status: "invalid", reason: `"${String(raw)}" is not a valid date \u2014 ${expected}` };
|
|
10358
10675
|
const dateOnly = DATE_ONLY_RE.exec(str);
|
|
10359
10676
|
const dateTime = dateOnly ? null : DATE_TIME_RE.exec(str);
|
|
10360
10677
|
const match = dateOnly ?? dateTime;
|
|
@@ -10443,7 +10760,14 @@ function timezoneOffsetMs(utcMillis, timezone) {
|
|
|
10443
10760
|
}
|
|
10444
10761
|
const get = (type) => Number(parts.find((p) => p.type === type)?.value ?? "0");
|
|
10445
10762
|
const hour = get("hour") === 24 ? 0 : get("hour");
|
|
10446
|
-
const asIfUtc = Date.UTC(
|
|
10763
|
+
const asIfUtc = Date.UTC(
|
|
10764
|
+
get("year"),
|
|
10765
|
+
get("month") - 1,
|
|
10766
|
+
get("day"),
|
|
10767
|
+
hour,
|
|
10768
|
+
get("minute"),
|
|
10769
|
+
get("second")
|
|
10770
|
+
);
|
|
10447
10771
|
return asIfUtc - Math.floor(utcMillis / 1e3) * 1e3;
|
|
10448
10772
|
}
|
|
10449
10773
|
function isCalendarDateAllowed(dateYYYYMMDD, config, clock) {
|