brainerce 2.0.2 → 2.2.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 +799 -113
- package/dist/index.d.mts +970 -176
- package/dist/index.d.ts +970 -176
- package/dist/index.js +517 -195
- package/dist/index.mjs +517 -195
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -204,7 +204,7 @@ function isDevGuardsEnabled() {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// src/version.ts
|
|
207
|
-
var SDK_VERSION = "2.0
|
|
207
|
+
var SDK_VERSION = "2.2.0";
|
|
208
208
|
|
|
209
209
|
// src/client.ts
|
|
210
210
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -450,7 +450,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
450
450
|
* Works in all three SDK modes (vibe-coded, storefront, admin):
|
|
451
451
|
* - **Public reads** (`get`, `list`, `getBySlug`): work in any mode.
|
|
452
452
|
* - **Write operations** (`create`, `update`, `publish`, `unpublish`,
|
|
453
|
-
* `remove`): admin mode only — they call `/api/
|
|
453
|
+
* `remove`): admin mode only — they call `/api/content/...` with
|
|
454
454
|
* the API key. Calling from storefront / vibe-coded mode throws.
|
|
455
455
|
*
|
|
456
456
|
* **Default key:** every type has `'main'` as its universal default key.
|
|
@@ -487,7 +487,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
487
487
|
this.content = (() => {
|
|
488
488
|
const DEFAULT_KEY = "main";
|
|
489
489
|
const publicGetPath = (type, key) => `/content/${encodeURIComponent(type)}/${encodeURIComponent(key)}`;
|
|
490
|
-
const adminBase = () => "/api/
|
|
490
|
+
const adminBase = () => "/api/content";
|
|
491
491
|
const publicGet = async (type, key, locale) => {
|
|
492
492
|
const query = locale ? { locale } : void 0;
|
|
493
493
|
const path = publicGetPath(type, key);
|
|
@@ -1221,6 +1221,45 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
1221
1221
|
getStoreDirection(locale) {
|
|
1222
1222
|
return getDirectionForLocale(locale ?? this.locale);
|
|
1223
1223
|
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Get what the merchant actually configured on this sales channel: store
|
|
1226
|
+
* identity and multi-language setup, the channel's own settings (low-stock
|
|
1227
|
+
* warning and threshold, reservation strategy and timeout, birthday and
|
|
1228
|
+
* email-verification requirements, granted scopes) and which optional
|
|
1229
|
+
* features are switched on (payment providers, social login, coupons,
|
|
1230
|
+
* discount rules, shipping zones, downloadables, content, loyalty).
|
|
1231
|
+
*
|
|
1232
|
+
* Read this instead of hardcoding. A storefront with a hardcoded low-stock
|
|
1233
|
+
* threshold shows the wrong badge on every store whose merchant chose a
|
|
1234
|
+
* different one, and a storefront that renders a feature nobody enabled
|
|
1235
|
+
* ships a control that silently does nothing.
|
|
1236
|
+
*
|
|
1237
|
+
* Only available in vibe-coded mode (`salesChannelId`). The payload belongs
|
|
1238
|
+
* to one sales channel: `storeId` mode has no channel to read it from, and
|
|
1239
|
+
* an `apiKey` addresses the store rather than any single channel.
|
|
1240
|
+
*
|
|
1241
|
+
* Call it once for the whole app and share the result. It is per channel,
|
|
1242
|
+
* not per product, so fetching it on every page is wasted work.
|
|
1243
|
+
*
|
|
1244
|
+
* @example
|
|
1245
|
+
* ```typescript
|
|
1246
|
+
* const caps = await client.getStoreCapabilities();
|
|
1247
|
+
*
|
|
1248
|
+
* // Low stock: honour the switch before the number.
|
|
1249
|
+
* const threshold = caps.connection.lowStockWarning
|
|
1250
|
+
* ? caps.connection.lowStockThreshold
|
|
1251
|
+
* : 0;
|
|
1252
|
+
*
|
|
1253
|
+
* if (caps.features.hasCoupons) renderCouponInput();
|
|
1254
|
+
* if (caps.store.i18n?.enabled) renderLocaleSwitcher(caps.store.i18n.supportedLocales);
|
|
1255
|
+
* ```
|
|
1256
|
+
*/
|
|
1257
|
+
async getStoreCapabilities() {
|
|
1258
|
+
if (this.isVibeCodedMode()) {
|
|
1259
|
+
return this.vibeCodedRequest("GET", "/capabilities");
|
|
1260
|
+
}
|
|
1261
|
+
throw new BrainerceError("getStoreCapabilities is only available in vibe-coded mode", 400);
|
|
1262
|
+
}
|
|
1224
1263
|
// -------------------- Analytics --------------------
|
|
1225
1264
|
/**
|
|
1226
1265
|
* Send a storefront analytics beacon (pageview or engagement).
|
|
@@ -2068,11 +2107,9 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
2068
2107
|
queryParams
|
|
2069
2108
|
);
|
|
2070
2109
|
}
|
|
2071
|
-
|
|
2072
|
-
"
|
|
2073
|
-
|
|
2074
|
-
void 0,
|
|
2075
|
-
queryParams
|
|
2110
|
+
throw new BrainerceError(
|
|
2111
|
+
"getSearchSuggestions is a storefront API. There is no admin suggestions endpoint \u2014 construct the client with `salesChannelId` or `storeId` to use it, or call getProducts({ search }) in admin mode.",
|
|
2112
|
+
400
|
|
2076
2113
|
);
|
|
2077
2114
|
}
|
|
2078
2115
|
/**
|
|
@@ -3048,14 +3085,20 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
3048
3085
|
});
|
|
3049
3086
|
}
|
|
3050
3087
|
/**
|
|
3051
|
-
* Get platform capabilities for coupon features.
|
|
3052
|
-
*
|
|
3088
|
+
* Get platform capabilities for coupon features, keyed by platform.
|
|
3089
|
+
*
|
|
3090
|
+
* ⛔ **Returns an empty object today.** Platform coupon capabilities moved
|
|
3091
|
+
* into the standalone connector apps and are not re-exposed here yet, so the
|
|
3092
|
+
* endpoint answers `{}` for every store. Treat a missing key as "unknown",
|
|
3093
|
+
* never as "the platform lacks the feature", and do not index into a platform
|
|
3094
|
+
* key without checking it exists first — there are none to find.
|
|
3053
3095
|
*
|
|
3054
3096
|
* @example
|
|
3055
3097
|
* ```typescript
|
|
3056
3098
|
* const capabilities = await client.getCouponPlatformCapabilities();
|
|
3057
|
-
*
|
|
3058
|
-
*
|
|
3099
|
+
* const meta = capabilities['GOOGLE'];
|
|
3100
|
+
* if (meta && !meta.supportsProducts) {
|
|
3101
|
+
* console.log('This platform cannot target individual products');
|
|
3059
3102
|
* }
|
|
3060
3103
|
* ```
|
|
3061
3104
|
*/
|
|
@@ -3181,7 +3224,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
3181
3224
|
);
|
|
3182
3225
|
}
|
|
3183
3226
|
console.warn(
|
|
3184
|
-
`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
|
|
3227
|
+
`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.`
|
|
3185
3228
|
);
|
|
3186
3229
|
}
|
|
3187
3230
|
/**
|
|
@@ -5676,6 +5719,324 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
5676
5719
|
"checkout"
|
|
5677
5720
|
);
|
|
5678
5721
|
}
|
|
5722
|
+
/**
|
|
5723
|
+
* Apply a gift card to a checkout.
|
|
5724
|
+
*
|
|
5725
|
+
* Merchants issue cards from the dashboard, so live codes exist and this field
|
|
5726
|
+
* is worth building. There is still no way for a shopper to BUY a gift card —
|
|
5727
|
+
* no product type, no purchase flow — so every card in circulation was issued
|
|
5728
|
+
* by hand.
|
|
5729
|
+
*
|
|
5730
|
+
* Redemption is deliberately NOT gated on the store's gift-card switch: a
|
|
5731
|
+
* store that turned the feature off still owes every card already in a
|
|
5732
|
+
* customer's hand, so the field keeps working. Build it unconditionally.
|
|
5733
|
+
*
|
|
5734
|
+
* A gift card is a **means of payment, not a discount**. The order total does
|
|
5735
|
+
* not change and tax stays calculated on the full value; what changes is
|
|
5736
|
+
* `providerAmountDue`, the amount the payment provider will be charged.
|
|
5737
|
+
*
|
|
5738
|
+
* Render it as its own line — "Gift card −₪54.50" beside the total — and NOT
|
|
5739
|
+
* by adding it to `discountAmount`. A shopper who sees stored value folded
|
|
5740
|
+
* into a discount is being shown the wrong thing, and so is their receipt.
|
|
5741
|
+
*
|
|
5742
|
+
* Only as much of the card as the order still owes is applied, so a card
|
|
5743
|
+
* larger than the basket leaves a balance on it for next time, and a smaller
|
|
5744
|
+
* one leaves an amount for the provider to charge.
|
|
5745
|
+
*
|
|
5746
|
+
* @example
|
|
5747
|
+
* const { amountApplied, providerAmountDue, tenderId } =
|
|
5748
|
+
* await client.applyGiftCard('checkout_123', 'A1B2-C3D4-E5F6-G7H8-J9K0');
|
|
5749
|
+
* // total is unchanged; charge the provider providerAmountDue
|
|
5750
|
+
*/
|
|
5751
|
+
async applyGiftCard(checkoutId, code) {
|
|
5752
|
+
if (this.isVibeCodedMode()) {
|
|
5753
|
+
return this.vibeCodedRequest(
|
|
5754
|
+
"POST",
|
|
5755
|
+
`/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5756
|
+
{ code }
|
|
5757
|
+
);
|
|
5758
|
+
}
|
|
5759
|
+
if (this.storeId && !this.apiKey) {
|
|
5760
|
+
return this.storefrontRequest(
|
|
5761
|
+
"POST",
|
|
5762
|
+
`/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5763
|
+
{ code }
|
|
5764
|
+
);
|
|
5765
|
+
}
|
|
5766
|
+
return this.adminRequest(
|
|
5767
|
+
"POST",
|
|
5768
|
+
`/api/v1/checkout/${encodePathSegment(checkoutId)}/gift-card`,
|
|
5769
|
+
{ code }
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Remove a previously applied gift card from a checkout.
|
|
5774
|
+
*
|
|
5775
|
+
* Takes the `tenderId` returned by {@link applyGiftCard}, not the code — a
|
|
5776
|
+
* checkout can carry more than one card, and the code is never echoed back.
|
|
5777
|
+
*
|
|
5778
|
+
* The held value goes straight back to the card. Nothing was ever debited
|
|
5779
|
+
* while it was applied, so removing costs the shopper nothing.
|
|
5780
|
+
*/
|
|
5781
|
+
async removeGiftCard(checkoutId, tenderId) {
|
|
5782
|
+
const path = `/checkout/${encodePathSegment(checkoutId)}/gift-card/${encodePathSegment(tenderId)}`;
|
|
5783
|
+
if (this.isVibeCodedMode()) {
|
|
5784
|
+
return this.vibeCodedRequest("DELETE", path);
|
|
5785
|
+
}
|
|
5786
|
+
if (this.storeId && !this.apiKey) {
|
|
5787
|
+
return this.storefrontRequest("DELETE", path);
|
|
5788
|
+
}
|
|
5789
|
+
return this.adminRequest("DELETE", `/api/v1${path}`);
|
|
5790
|
+
}
|
|
5791
|
+
/**
|
|
5792
|
+
* Check what is left on a gift card.
|
|
5793
|
+
*
|
|
5794
|
+
* Rate limited, and deliberately uninformative: a code that does not exist,
|
|
5795
|
+
* one that has been disabled and one that has expired all return the SAME
|
|
5796
|
+
* response — `{ balance: '0.00', usable: false }` — and take the same time to
|
|
5797
|
+
* do it. Do not build UI that tries to tell those apart, because the API will
|
|
5798
|
+
* not tell you, by design: a gift-card code is bearer value, and an endpoint
|
|
5799
|
+
* that confirmed which codes were real would be a free way to find them.
|
|
5800
|
+
*
|
|
5801
|
+
* Show "we cannot use this code" and let the shopper re-enter it.
|
|
5802
|
+
*/
|
|
5803
|
+
async checkGiftCardBalance(code) {
|
|
5804
|
+
if (this.isVibeCodedMode()) {
|
|
5805
|
+
return this.vibeCodedRequest("POST", "/gift-cards/balance", { code });
|
|
5806
|
+
}
|
|
5807
|
+
if (this.storeId && !this.apiKey) {
|
|
5808
|
+
return this.storefrontRequest("POST", "/gift-cards/balance", { code });
|
|
5809
|
+
}
|
|
5810
|
+
return this.adminRequest("POST", "/api/v1/gift-cards/balance", { code });
|
|
5811
|
+
}
|
|
5812
|
+
// ==========================================================================
|
|
5813
|
+
// Gift cards — administration (API key only)
|
|
5814
|
+
//
|
|
5815
|
+
// Full programmatic management, unlike Shopify, which requires you to ask
|
|
5816
|
+
// their support for the equivalent scope. The gate here is the SCOPE the
|
|
5817
|
+
// merchant granted your key, so the separation matters: `gift_cards:issue`
|
|
5818
|
+
// mints stored value and `gift_cards:adjust` rewrites a balance, and neither
|
|
5819
|
+
// is implied by `gift_cards:read`. Ask for the least your integration needs.
|
|
5820
|
+
//
|
|
5821
|
+
// Every call reaches the same service the dashboard uses, so the rules hold
|
|
5822
|
+
// identically: a note is mandatory on anything that moves value, a debit
|
|
5823
|
+
// cannot cross what live checkout holds have reserved, and there is no delete
|
|
5824
|
+
// anywhere — the ledger is append-only.
|
|
5825
|
+
// ==========================================================================
|
|
5826
|
+
/**
|
|
5827
|
+
* List gift cards.
|
|
5828
|
+
*
|
|
5829
|
+
* `search` matches the LAST FOUR of a code or part of a recipient email. It
|
|
5830
|
+
* cannot match a full code: only an HMAC is stored, so there is nothing to
|
|
5831
|
+
* search against.
|
|
5832
|
+
*
|
|
5833
|
+
* Requires `gift_cards:read`.
|
|
5834
|
+
*/
|
|
5835
|
+
async listGiftCards(params) {
|
|
5836
|
+
const q = new URLSearchParams();
|
|
5837
|
+
if (params?.page) q.set("page", String(params.page));
|
|
5838
|
+
if (params?.limit) q.set("limit", String(params.limit));
|
|
5839
|
+
if (params?.filter) q.set("filter", params.filter);
|
|
5840
|
+
if (params?.search) q.set("search", params.search);
|
|
5841
|
+
const qs = q.toString();
|
|
5842
|
+
return this.adminRequest(
|
|
5843
|
+
"GET",
|
|
5844
|
+
`/api/v1/gift-cards${qs ? `?${qs}` : ""}`
|
|
5845
|
+
);
|
|
5846
|
+
}
|
|
5847
|
+
/**
|
|
5848
|
+
* Outstanding gift card liability, per currency.
|
|
5849
|
+
*
|
|
5850
|
+
* This is the month-end number. Read `byCurrency` if the store sells in more
|
|
5851
|
+
* than one — currencies are never summed together.
|
|
5852
|
+
*
|
|
5853
|
+
* Requires `gift_cards:read`.
|
|
5854
|
+
*/
|
|
5855
|
+
async getGiftCardLiability() {
|
|
5856
|
+
return this.adminRequest("GET", "/api/v1/gift-cards/liability");
|
|
5857
|
+
}
|
|
5858
|
+
/**
|
|
5859
|
+
* One gift card with its full ledger.
|
|
5860
|
+
*
|
|
5861
|
+
* Requires `gift_cards:read`.
|
|
5862
|
+
*/
|
|
5863
|
+
async getGiftCard(giftCardId) {
|
|
5864
|
+
return this.adminRequest(
|
|
5865
|
+
"GET",
|
|
5866
|
+
`/api/v1/gift-cards/${encodePathSegment(giftCardId)}`
|
|
5867
|
+
);
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Issue a gift card.
|
|
5871
|
+
*
|
|
5872
|
+
* ⚠️ **The code comes back exactly once.** It is stored only as an HMAC, so
|
|
5873
|
+
* this response is the only time it exists in readable form anywhere. Persist
|
|
5874
|
+
* it or deliver it before you discard the response — no later call, dashboard
|
|
5875
|
+
* screen or database query can recover it.
|
|
5876
|
+
*
|
|
5877
|
+
* A `note` is required. Refused when gift cards are switched off for the
|
|
5878
|
+
* store. Pass an `Idempotency-Key` header to make a retry safe.
|
|
5879
|
+
*
|
|
5880
|
+
* Requires `gift_cards:issue`.
|
|
5881
|
+
*
|
|
5882
|
+
* @example
|
|
5883
|
+
* ```typescript
|
|
5884
|
+
* const card = await client.issueGiftCard({
|
|
5885
|
+
* amount: '200.00',
|
|
5886
|
+
* note: 'Compensation for order #1042',
|
|
5887
|
+
* recipientEmail: 'dana@example.com',
|
|
5888
|
+
* });
|
|
5889
|
+
* await sendToCustomer(card.plaintextCode); // your only chance
|
|
5890
|
+
* ```
|
|
5891
|
+
*/
|
|
5892
|
+
async issueGiftCard(data) {
|
|
5893
|
+
return this.adminRequest("POST", "/api/v1/gift-cards", data);
|
|
5894
|
+
}
|
|
5895
|
+
/**
|
|
5896
|
+
* Re-issue a gift card onto a new code.
|
|
5897
|
+
*
|
|
5898
|
+
* The answer to a customer losing their code. Mints a new code, moves the
|
|
5899
|
+
* WHOLE balance to it, and REVOKES the old card.
|
|
5900
|
+
*
|
|
5901
|
+
* **This is not a resend.** The old code stops working the moment this
|
|
5902
|
+
* returns — if the customer still holds a printed card, it dies. Refused
|
|
5903
|
+
* while a checkout holds value on the card. The original expiry carries
|
|
5904
|
+
* forward, so this cannot be used to restart an expiry clock.
|
|
5905
|
+
*
|
|
5906
|
+
* The new code is returned exactly once, under the same rules as issuance.
|
|
5907
|
+
*
|
|
5908
|
+
* Requires `gift_cards:issue`.
|
|
5909
|
+
*/
|
|
5910
|
+
async reissueGiftCard(giftCardId, note) {
|
|
5911
|
+
return this.adminRequest(
|
|
5912
|
+
"POST",
|
|
5913
|
+
`/api/v1/gift-cards/${encodePathSegment(giftCardId)}/reissue`,
|
|
5914
|
+
{ note }
|
|
5915
|
+
);
|
|
5916
|
+
}
|
|
5917
|
+
/**
|
|
5918
|
+
* Adjust a gift card balance.
|
|
5919
|
+
*
|
|
5920
|
+
* `delta` is a SIGNED decimal string: `"25.00"` adds, `"-25.00"` takes away.
|
|
5921
|
+
* The `note` is required and is written to the ledger permanently — it is the
|
|
5922
|
+
* row a finance review reads a year from now.
|
|
5923
|
+
*
|
|
5924
|
+
* A debit cannot take the balance below what live checkout holds have already
|
|
5925
|
+
* reserved; that refusal names the held amount so you can act on it.
|
|
5926
|
+
*
|
|
5927
|
+
* Requires `gift_cards:adjust`.
|
|
5928
|
+
*/
|
|
5929
|
+
async adjustGiftCardBalance(giftCardId, delta, note) {
|
|
5930
|
+
return this.adminRequest(
|
|
5931
|
+
"PATCH",
|
|
5932
|
+
`/api/v1/gift-cards/${encodePathSegment(giftCardId)}/adjust`,
|
|
5933
|
+
{ delta, note }
|
|
5934
|
+
);
|
|
5935
|
+
}
|
|
5936
|
+
/**
|
|
5937
|
+
* Enable, disable or revoke one gift card.
|
|
5938
|
+
*
|
|
5939
|
+
* Deliberately does NOT touch live holds: a checkout that already reserved
|
|
5940
|
+
* value settles normally, because pulling it out from under a shopper
|
|
5941
|
+
* mid-payment would strand a provider charge already in flight. Disabling
|
|
5942
|
+
* stops NEW holds, which is what "off" actually means.
|
|
5943
|
+
*
|
|
5944
|
+
* Requires `gift_cards:write`.
|
|
5945
|
+
*/
|
|
5946
|
+
async setGiftCardStatus(giftCardId, status) {
|
|
5947
|
+
return this.adminRequest(
|
|
5948
|
+
"PATCH",
|
|
5949
|
+
`/api/v1/gift-cards/${encodePathSegment(giftCardId)}/status`,
|
|
5950
|
+
{ status }
|
|
5951
|
+
);
|
|
5952
|
+
}
|
|
5953
|
+
/**
|
|
5954
|
+
* Disable or reactivate many gift cards at once.
|
|
5955
|
+
*
|
|
5956
|
+
* `REVOKED` is not accepted here — it belongs to re-issue, which moves the
|
|
5957
|
+
* balance to a replacement first. Revoking in bulk would strand balances with
|
|
5958
|
+
* nowhere to go. Cards already revoked are skipped, so the returned count is
|
|
5959
|
+
* the honest one and may be lower than the ids you sent.
|
|
5960
|
+
*
|
|
5961
|
+
* There is no bulk delete, here or anywhere: the ledger is append-only and a
|
|
5962
|
+
* card may carry a statutory retention life.
|
|
5963
|
+
*
|
|
5964
|
+
* Requires `gift_cards:write`.
|
|
5965
|
+
*/
|
|
5966
|
+
async bulkSetGiftCardStatus(giftCardIds, status) {
|
|
5967
|
+
return this.adminRequest("PATCH", "/api/v1/gift-cards/bulk/status", {
|
|
5968
|
+
giftCardIds,
|
|
5969
|
+
status
|
|
5970
|
+
});
|
|
5971
|
+
}
|
|
5972
|
+
// ==========================================================================
|
|
5973
|
+
// Donations
|
|
5974
|
+
// ==========================================================================
|
|
5975
|
+
/**
|
|
5976
|
+
* Start a donation.
|
|
5977
|
+
*
|
|
5978
|
+
* A donation does not go through the cart. There is no line item, no
|
|
5979
|
+
* quantity, no shipping and no order — a donor names an amount and pays it,
|
|
5980
|
+
* which is a different shape of transaction from a purchase. Do not model a
|
|
5981
|
+
* donation as a product; if you already have, the amount is the giveaway:
|
|
5982
|
+
* you cannot let a donor type one.
|
|
5983
|
+
*
|
|
5984
|
+
* ⛔ A successful return is NOT a completed gift. You get back a PENDING
|
|
5985
|
+
* donation and a provider intent to complete, exactly as with a checkout.
|
|
5986
|
+
* The gift is only real once the provider's webhook confirms it, which is
|
|
5987
|
+
* when the donation reads back as `PAID`. Show a thank-you that reflects
|
|
5988
|
+
* that, and never send a receipt off the back of this call.
|
|
5989
|
+
*
|
|
5990
|
+
* Requires donations to be switched on for the store; a store that has not
|
|
5991
|
+
* turned them on is rejected rather than silently accepting money.
|
|
5992
|
+
*
|
|
5993
|
+
* ```ts
|
|
5994
|
+
* const donation = await brainerce.createDonation({
|
|
5995
|
+
* amount: 180,
|
|
5996
|
+
* feeCoverAmount: 6.3, // offered as a checkbox; most donors accept
|
|
5997
|
+
* donorEmail: 'sarah@example.com',
|
|
5998
|
+
* donorName: 'Sarah Cohen',
|
|
5999
|
+
* tributeType: 'IN_MEMORY',
|
|
6000
|
+
* tributeName: 'Avraham Cohen',
|
|
6001
|
+
* returnPath: '/thank-you',
|
|
6002
|
+
* });
|
|
6003
|
+
* // → complete donation.payment with your provider, then poll getDonation()
|
|
6004
|
+
* ```
|
|
6005
|
+
*/
|
|
6006
|
+
async createDonation(input) {
|
|
6007
|
+
if (this.isVibeCodedMode()) {
|
|
6008
|
+
return this.vibeCodedRequest("POST", "/donations", input);
|
|
6009
|
+
}
|
|
6010
|
+
if (this.storeId && !this.apiKey) {
|
|
6011
|
+
return this.storefrontRequest("POST", "/donations", input);
|
|
6012
|
+
}
|
|
6013
|
+
throw new Error(
|
|
6014
|
+
"createDonation is a storefront call. Initialise the SDK with a salesChannelId or storeId \u2014 an admin apiKey cannot start a donation."
|
|
6015
|
+
);
|
|
6016
|
+
}
|
|
6017
|
+
/**
|
|
6018
|
+
* Read a donation back — for a thank-you page, and to see whether it is paid.
|
|
6019
|
+
*
|
|
6020
|
+
* Rate limited to 5 requests a minute, because an id that either resolves or
|
|
6021
|
+
* 404s is a way to enumerate them. Poll it a handful of times after the donor
|
|
6022
|
+
* returns from the provider; do not put it behind a 1-second interval.
|
|
6023
|
+
*
|
|
6024
|
+
* The payload is narrow on purpose: no failure reason, and `donorName` comes
|
|
6025
|
+
* back `null` whenever the gift was marked anonymous — so you can render this
|
|
6026
|
+
* straight onto a public page without leaking anything.
|
|
6027
|
+
*/
|
|
6028
|
+
async getDonation(donationId) {
|
|
6029
|
+
const path = `/donations/${encodePathSegment(donationId)}`;
|
|
6030
|
+
if (this.isVibeCodedMode()) {
|
|
6031
|
+
return this.vibeCodedRequest("GET", path);
|
|
6032
|
+
}
|
|
6033
|
+
if (this.storeId && !this.apiKey) {
|
|
6034
|
+
return this.storefrontRequest("GET", path);
|
|
6035
|
+
}
|
|
6036
|
+
throw new Error(
|
|
6037
|
+
"getDonation is a storefront call. Initialise the SDK with a salesChannelId or storeId."
|
|
6038
|
+
);
|
|
6039
|
+
}
|
|
5679
6040
|
/**
|
|
5680
6041
|
* Set customer information on checkout
|
|
5681
6042
|
*
|
|
@@ -5751,10 +6112,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
5751
6112
|
"/checkouts/shipping-destinations"
|
|
5752
6113
|
);
|
|
5753
6114
|
}
|
|
5754
|
-
return this.adminRequest(
|
|
5755
|
-
"GET",
|
|
5756
|
-
"/api/v1/checkouts/shipping-destinations"
|
|
5757
|
-
);
|
|
6115
|
+
return this.adminRequest("GET", "/api/checkouts/shipping-destinations");
|
|
5758
6116
|
}
|
|
5759
6117
|
/**
|
|
5760
6118
|
* Set shipping address on checkout (includes customer email).
|
|
@@ -6013,7 +6371,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
6013
6371
|
if (this.storeId && !this.apiKey) {
|
|
6014
6372
|
return this.storefrontRequest("GET", "/pickup-locations");
|
|
6015
6373
|
}
|
|
6016
|
-
return this.adminRequest("GET", "/api/
|
|
6374
|
+
return this.adminRequest("GET", "/api/checkouts/pickup-locations");
|
|
6017
6375
|
}
|
|
6018
6376
|
/**
|
|
6019
6377
|
* Set delivery type on checkout (shipping or pickup).
|
|
@@ -8027,119 +8385,6 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8027
8385
|
}
|
|
8028
8386
|
return this.storefrontRequest("GET", "/customers/me/cart");
|
|
8029
8387
|
}
|
|
8030
|
-
// -------------------- Custom API Integrations --------------------
|
|
8031
|
-
// These methods require Admin mode (apiKey)
|
|
8032
|
-
/**
|
|
8033
|
-
* Get all Custom API integrations for a store
|
|
8034
|
-
* Requires Admin mode (apiKey)
|
|
8035
|
-
*
|
|
8036
|
-
* @example
|
|
8037
|
-
* ```typescript
|
|
8038
|
-
* const integrations = await client.getCustomApiIntegrations();
|
|
8039
|
-
* integrations.forEach(api => {
|
|
8040
|
-
* console.log(`${api.name}: ${api.status}`);
|
|
8041
|
-
* });
|
|
8042
|
-
* ```
|
|
8043
|
-
*/
|
|
8044
|
-
async getCustomApiIntegrations() {
|
|
8045
|
-
return this.adminRequest("GET", "/api/v1/custom-api");
|
|
8046
|
-
}
|
|
8047
|
-
/**
|
|
8048
|
-
* Get a single Custom API integration by ID
|
|
8049
|
-
* Requires Admin mode (apiKey)
|
|
8050
|
-
*
|
|
8051
|
-
* @example
|
|
8052
|
-
* ```typescript
|
|
8053
|
-
* const api = await client.getCustomApiIntegration('api_123');
|
|
8054
|
-
* console.log(`API: ${api.name}, URL: ${api.baseUrl}`);
|
|
8055
|
-
* ```
|
|
8056
|
-
*/
|
|
8057
|
-
async getCustomApiIntegration(integrationId) {
|
|
8058
|
-
return this.adminRequest(
|
|
8059
|
-
"GET",
|
|
8060
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`
|
|
8061
|
-
);
|
|
8062
|
-
}
|
|
8063
|
-
/**
|
|
8064
|
-
* Create a new Custom API integration
|
|
8065
|
-
* Requires Admin mode (apiKey)
|
|
8066
|
-
*
|
|
8067
|
-
* @example
|
|
8068
|
-
* ```typescript
|
|
8069
|
-
* const api = await client.createCustomApiIntegration({
|
|
8070
|
-
* name: 'My External API',
|
|
8071
|
-
* baseUrl: 'https://api.example.com',
|
|
8072
|
-
* authType: 'api_key',
|
|
8073
|
-
* credentials: {
|
|
8074
|
-
* apiKey: 'sk_123...',
|
|
8075
|
-
* headerName: 'X-API-Key',
|
|
8076
|
-
* },
|
|
8077
|
-
* syncDirection: 'bidirectional',
|
|
8078
|
-
* syncConfig: {
|
|
8079
|
-
* products: true,
|
|
8080
|
-
* orders: true,
|
|
8081
|
-
* inventory: true,
|
|
8082
|
-
* },
|
|
8083
|
-
* });
|
|
8084
|
-
* ```
|
|
8085
|
-
*/
|
|
8086
|
-
async createCustomApiIntegration(data) {
|
|
8087
|
-
return this.adminRequest("POST", "/api/v1/custom-api", data);
|
|
8088
|
-
}
|
|
8089
|
-
/**
|
|
8090
|
-
* Update a Custom API integration
|
|
8091
|
-
* Requires Admin mode (apiKey)
|
|
8092
|
-
*
|
|
8093
|
-
* @example
|
|
8094
|
-
* ```typescript
|
|
8095
|
-
* const api = await client.updateCustomApiIntegration('api_123', {
|
|
8096
|
-
* enabled: false,
|
|
8097
|
-
* syncConfig: { products: true, orders: false, inventory: true },
|
|
8098
|
-
* });
|
|
8099
|
-
* ```
|
|
8100
|
-
*/
|
|
8101
|
-
async updateCustomApiIntegration(integrationId, data) {
|
|
8102
|
-
return this.adminRequest(
|
|
8103
|
-
"PATCH",
|
|
8104
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`,
|
|
8105
|
-
data
|
|
8106
|
-
);
|
|
8107
|
-
}
|
|
8108
|
-
/**
|
|
8109
|
-
* Delete a Custom API integration
|
|
8110
|
-
* Requires Admin mode (apiKey)
|
|
8111
|
-
*
|
|
8112
|
-
* @example
|
|
8113
|
-
* ```typescript
|
|
8114
|
-
* await client.deleteCustomApiIntegration('api_123');
|
|
8115
|
-
* ```
|
|
8116
|
-
*/
|
|
8117
|
-
async deleteCustomApiIntegration(integrationId) {
|
|
8118
|
-
await this.adminRequest(
|
|
8119
|
-
"DELETE",
|
|
8120
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}`
|
|
8121
|
-
);
|
|
8122
|
-
}
|
|
8123
|
-
/**
|
|
8124
|
-
* Test connection to a Custom API
|
|
8125
|
-
* Requires Admin mode (apiKey)
|
|
8126
|
-
*
|
|
8127
|
-
* @example
|
|
8128
|
-
* ```typescript
|
|
8129
|
-
* const result = await client.testCustomApiConnection('api_123');
|
|
8130
|
-
* if (result.success) {
|
|
8131
|
-
* console.log(`Connection OK, latency: ${result.latency}ms`);
|
|
8132
|
-
* } else {
|
|
8133
|
-
* console.error(`Connection failed: ${result.error}`);
|
|
8134
|
-
* }
|
|
8135
|
-
* ```
|
|
8136
|
-
*/
|
|
8137
|
-
async testCustomApiConnection(integrationId) {
|
|
8138
|
-
return this.adminRequest(
|
|
8139
|
-
"POST",
|
|
8140
|
-
`/api/v1/custom-api/${encodePathSegment(integrationId)}/test`
|
|
8141
|
-
);
|
|
8142
|
-
}
|
|
8143
8388
|
// -------------------- Inventory Reservations --------------------
|
|
8144
8389
|
// These methods are only available in vibe-coded mode when reservation strategy
|
|
8145
8390
|
// is set to ON_CART or ON_CHECKOUT
|
|
@@ -8779,12 +9024,18 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8779
9024
|
* ```typescript
|
|
8780
9025
|
* const rate = await client.createZoneShippingRate('zone_123', {
|
|
8781
9026
|
* name: 'Standard Shipping',
|
|
8782
|
-
* type: '
|
|
8783
|
-
*
|
|
8784
|
-
*
|
|
8785
|
-
*
|
|
9027
|
+
* type: 'FLAT_RATE',
|
|
9028
|
+
* rateConfig: { amount: 5.99 },
|
|
9029
|
+
* minDeliveryDays: 3,
|
|
9030
|
+
* maxDeliveryDays: 5,
|
|
8786
9031
|
* });
|
|
8787
9032
|
* ```
|
|
9033
|
+
*
|
|
9034
|
+
* The price lives in `rateConfig`, whose shape follows `type` — `FLAT_RATE`
|
|
9035
|
+
* takes `{ amount }`, `WEIGHT_BASED` and `PRICE_BASED` take tier arrays, and
|
|
9036
|
+
* `FREE` and `LOCAL_PICKUP` take none. Unknown top-level properties are
|
|
9037
|
+
* rejected outright rather than ignored, so a stray `price` or
|
|
9038
|
+
* `estimatedDays` fails the whole call with 400.
|
|
8788
9039
|
*/
|
|
8789
9040
|
async createZoneShippingRate(zoneId, data) {
|
|
8790
9041
|
return this.adminRequest(
|
|
@@ -8985,6 +9236,10 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
8985
9236
|
*
|
|
8986
9237
|
* Returns `appliesTax=false` when tax is disabled, the country is missing,
|
|
8987
9238
|
* or no active rate covers it.
|
|
9239
|
+
*
|
|
9240
|
+
* The preview has no province, so it only sees country-level rates: a
|
|
9241
|
+
* Canadian estimate shows the federal GST alone and the provincial PST/QST
|
|
9242
|
+
* joins it at checkout. Render `note` so the buyer is not surprised.
|
|
8988
9243
|
*/
|
|
8989
9244
|
async estimateTax(params) {
|
|
8990
9245
|
const query = params.country ? { country: params.country, subtotal: params.subtotal } : { subtotal: params.subtotal };
|
|
@@ -9055,10 +9310,43 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9055
9310
|
/**
|
|
9056
9311
|
* Get all tax rates for the store
|
|
9057
9312
|
* Requires Admin mode (apiKey)
|
|
9313
|
+
*
|
|
9314
|
+
* A jurisdiction can need more than one rate. Canada is the common case: a
|
|
9315
|
+
* country-level `GST` row plus a province-level `PST`/`QST` row, both with
|
|
9316
|
+
* `stackable: true`, which the checkout charges together. Provinces on HST
|
|
9317
|
+
* carry a single combined row with `stackable: false`.
|
|
9058
9318
|
*/
|
|
9059
9319
|
async getTaxRates() {
|
|
9060
9320
|
return this.adminRequest("GET", "/api/v1/tax/rates");
|
|
9061
9321
|
}
|
|
9322
|
+
/**
|
|
9323
|
+
* List the country tax presets available to apply.
|
|
9324
|
+
* Requires Admin mode (apiKey)
|
|
9325
|
+
*/
|
|
9326
|
+
async getTaxPresets() {
|
|
9327
|
+
return this.adminRequest("GET", "/api/v1/tax/presets");
|
|
9328
|
+
}
|
|
9329
|
+
/**
|
|
9330
|
+
* Apply a country's whole tax table in one call, instead of creating a rate
|
|
9331
|
+
* per province by hand. Requires Admin mode (apiKey).
|
|
9332
|
+
*
|
|
9333
|
+
* `CA` writes ten rates: federal GST 5% country-wide, one combined HST row
|
|
9334
|
+
* each for ON/NB/NL/NS/PE, and PST/RST/QST for BC/SK/MB/QC charged on top of
|
|
9335
|
+
* the GST. Alberta and the territories need no row — the GST covers them.
|
|
9336
|
+
*
|
|
9337
|
+
* Throws 409 when the store already has rates for that country; delete those
|
|
9338
|
+
* first if you meant to replace them. Rates land in the Standard tax class.
|
|
9339
|
+
*
|
|
9340
|
+
* Brainerce does not register the store for GST/HST and does not file returns.
|
|
9341
|
+
*
|
|
9342
|
+
* @example
|
|
9343
|
+
* ```typescript
|
|
9344
|
+
* const { created } = await client.applyTaxPreset('CA'); // created === 10
|
|
9345
|
+
* ```
|
|
9346
|
+
*/
|
|
9347
|
+
async applyTaxPreset(presetKey) {
|
|
9348
|
+
return this.adminRequest("POST", `/api/v1/tax/presets/${encodePathSegment(presetKey)}/apply`);
|
|
9349
|
+
}
|
|
9062
9350
|
/**
|
|
9063
9351
|
* Get a single tax rate by ID
|
|
9064
9352
|
* Requires Admin mode (apiKey)
|
|
@@ -9687,6 +9975,27 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9687
9975
|
}
|
|
9688
9976
|
// -------------------- Store Team Management (Admin) --------------------
|
|
9689
9977
|
// Store-level team management. Each store has its own team with roles and permissions.
|
|
9978
|
+
/**
|
|
9979
|
+
* Every store-level team operation is dashboard-only.
|
|
9980
|
+
*
|
|
9981
|
+
* `store-team.controller.ts:53` carries `DashboardOnlyGuard`, which rejects
|
|
9982
|
+
* `api_key` and `app_installation` principals outright, so no SDK caller can
|
|
9983
|
+
* reach these however the URL is spelled. They additionally pointed at
|
|
9984
|
+
* `/api/v1/stores/:storeId/team*`, and `@Controller('v1')`
|
|
9985
|
+
* (external-api.controller.ts:181) has no `stores` root — so what they
|
|
9986
|
+
* actually returned was a 404, not the 403 you would expect from the guard.
|
|
9987
|
+
*
|
|
9988
|
+
* Throwing beats either status code: a 404 reads as "wrong id" and a 403 as
|
|
9989
|
+
* "missing permission", and both send the caller looking for a fix that does
|
|
9990
|
+
* not exist. Use the account-level `getTeamMembers()` family, or the
|
|
9991
|
+
* dashboard.
|
|
9992
|
+
*/
|
|
9993
|
+
dashboardOnlyTeamOperation(operation) {
|
|
9994
|
+
throw new BrainerceError(
|
|
9995
|
+
`${operation} is a dashboard-only operation. Store-level team management sits behind DashboardOnlyGuard, which rejects API-key principals, so there is no SDK path to it in any mode. Use the account-level team methods (getTeamMembers, inviteTeamMember, ...) or the Brainerce dashboard.`,
|
|
9996
|
+
403
|
|
9997
|
+
);
|
|
9998
|
+
}
|
|
9690
9999
|
/**
|
|
9691
10000
|
* Get the team for a specific store (members + pending invitations)
|
|
9692
10001
|
* Requires Admin mode (apiKey) and MANAGE_TEAM permission
|
|
@@ -9696,11 +10005,8 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9696
10005
|
* const { members, invitations } = await client.getStoreTeam('store_id');
|
|
9697
10006
|
* ```
|
|
9698
10007
|
*/
|
|
9699
|
-
async getStoreTeam(
|
|
9700
|
-
return this.
|
|
9701
|
-
"GET",
|
|
9702
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team`
|
|
9703
|
-
);
|
|
10008
|
+
async getStoreTeam(_storeId) {
|
|
10009
|
+
return this.dashboardOnlyTeamOperation("getStoreTeam");
|
|
9704
10010
|
}
|
|
9705
10011
|
/**
|
|
9706
10012
|
* Invite a new member to a store
|
|
@@ -9717,12 +10023,8 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9717
10023
|
* });
|
|
9718
10024
|
* ```
|
|
9719
10025
|
*/
|
|
9720
|
-
async inviteStoreMember(
|
|
9721
|
-
return this.
|
|
9722
|
-
"POST",
|
|
9723
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/invite`,
|
|
9724
|
-
data
|
|
9725
|
-
);
|
|
10026
|
+
async inviteStoreMember(_storeId, _data) {
|
|
10027
|
+
return this.dashboardOnlyTeamOperation("inviteStoreMember");
|
|
9726
10028
|
}
|
|
9727
10029
|
/**
|
|
9728
10030
|
* Update a store team member's role and/or permissions
|
|
@@ -9739,12 +10041,8 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9739
10041
|
* });
|
|
9740
10042
|
* ```
|
|
9741
10043
|
*/
|
|
9742
|
-
async updateStoreMember(
|
|
9743
|
-
return this.
|
|
9744
|
-
"PATCH",
|
|
9745
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/${encodePathSegment(memberId)}`,
|
|
9746
|
-
data
|
|
9747
|
-
);
|
|
10044
|
+
async updateStoreMember(_storeId, _memberId, _data) {
|
|
10045
|
+
return this.dashboardOnlyTeamOperation("updateStoreMember");
|
|
9748
10046
|
}
|
|
9749
10047
|
/**
|
|
9750
10048
|
* Replace the set of vibe-coded sales channels a store member is restricted to.
|
|
@@ -9765,42 +10063,29 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9765
10063
|
* });
|
|
9766
10064
|
* ```
|
|
9767
10065
|
*/
|
|
9768
|
-
async updateStoreMemberSalesChannels(
|
|
9769
|
-
return this.
|
|
9770
|
-
"PATCH",
|
|
9771
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/${encodePathSegment(memberId)}/sales-channels`,
|
|
9772
|
-
data
|
|
9773
|
-
);
|
|
10066
|
+
async updateStoreMemberSalesChannels(_storeId, _memberId, _data) {
|
|
10067
|
+
return this.dashboardOnlyTeamOperation("updateStoreMemberSalesChannels");
|
|
9774
10068
|
}
|
|
9775
10069
|
/**
|
|
9776
10070
|
* Remove a member from a store team
|
|
9777
10071
|
* Requires Admin mode (apiKey) and MANAGE_TEAM permission
|
|
9778
10072
|
*/
|
|
9779
|
-
async removeStoreMember(
|
|
9780
|
-
|
|
9781
|
-
"DELETE",
|
|
9782
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/${encodePathSegment(memberId)}`
|
|
9783
|
-
);
|
|
10073
|
+
async removeStoreMember(_storeId, _memberId) {
|
|
10074
|
+
return this.dashboardOnlyTeamOperation("removeStoreMember");
|
|
9784
10075
|
}
|
|
9785
10076
|
/**
|
|
9786
10077
|
* Resend a store invitation email
|
|
9787
10078
|
* Requires Admin mode (apiKey) and MANAGE_TEAM permission
|
|
9788
10079
|
*/
|
|
9789
|
-
async resendStoreInvitation(
|
|
9790
|
-
return this.
|
|
9791
|
-
"POST",
|
|
9792
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/invitations/${encodePathSegment(invitationId)}/resend`
|
|
9793
|
-
);
|
|
10080
|
+
async resendStoreInvitation(_storeId, _invitationId) {
|
|
10081
|
+
return this.dashboardOnlyTeamOperation("resendStoreInvitation");
|
|
9794
10082
|
}
|
|
9795
10083
|
/**
|
|
9796
10084
|
* Revoke a store invitation
|
|
9797
10085
|
* Requires Admin mode (apiKey) and MANAGE_TEAM permission
|
|
9798
10086
|
*/
|
|
9799
|
-
async revokeStoreInvitation(
|
|
9800
|
-
|
|
9801
|
-
"DELETE",
|
|
9802
|
-
`/api/v1/stores/${encodePathSegment(storeId)}/team/invitations/${encodePathSegment(invitationId)}`
|
|
9803
|
-
);
|
|
10087
|
+
async revokeStoreInvitation(_storeId, _invitationId) {
|
|
10088
|
+
return this.dashboardOnlyTeamOperation("revokeStoreInvitation");
|
|
9804
10089
|
}
|
|
9805
10090
|
/**
|
|
9806
10091
|
* Get public invitation details by token (no auth required)
|
|
@@ -9809,7 +10094,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9809
10094
|
async getStoreInvitationByToken(token) {
|
|
9810
10095
|
return this.request(
|
|
9811
10096
|
"GET",
|
|
9812
|
-
`/api/
|
|
10097
|
+
`/api/store-invitations/${encodePathSegment(token)}`
|
|
9813
10098
|
);
|
|
9814
10099
|
}
|
|
9815
10100
|
/**
|
|
@@ -9817,9 +10102,9 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9817
10102
|
* Requires Admin mode (apiKey)
|
|
9818
10103
|
*/
|
|
9819
10104
|
async acceptStoreInvitation(token) {
|
|
9820
|
-
|
|
9821
|
-
"
|
|
9822
|
-
|
|
10105
|
+
throw new BrainerceError(
|
|
10106
|
+
"acceptStoreInvitation requires a dashboard session belonging to the invited user: the invitation is matched against the email address of that user, which an API key does not have. Send the invitee to the invitation link instead.",
|
|
10107
|
+
403
|
|
9823
10108
|
);
|
|
9824
10109
|
}
|
|
9825
10110
|
/**
|
|
@@ -9836,7 +10121,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9836
10121
|
* ```
|
|
9837
10122
|
*/
|
|
9838
10123
|
async getMyStores() {
|
|
9839
|
-
return this.
|
|
10124
|
+
return this.dashboardOnlyUserContext("getMyStores");
|
|
9840
10125
|
}
|
|
9841
10126
|
/**
|
|
9842
10127
|
* Get the current user's resolved permissions for a specific store
|
|
@@ -9850,10 +10135,23 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9850
10135
|
* }
|
|
9851
10136
|
* ```
|
|
9852
10137
|
*/
|
|
9853
|
-
async getMyStorePermissions(
|
|
9854
|
-
return this.
|
|
9855
|
-
|
|
9856
|
-
|
|
10138
|
+
async getMyStorePermissions(_storeId) {
|
|
10139
|
+
return this.dashboardOnlyUserContext("getMyStorePermissions");
|
|
10140
|
+
}
|
|
10141
|
+
/**
|
|
10142
|
+
* `/me/*` answers "who am I and what can I reach", which only a real user can
|
|
10143
|
+
* ask. `UserContextController` (store-team.controller.ts:246) is guarded by
|
|
10144
|
+
* `DashboardOnlyGuard` for a load-bearing reason its own G16 comment spells
|
|
10145
|
+
* out: both routes resolve access purely from `@CurrentUserId()`, which is
|
|
10146
|
+
* `undefined` for an api_key principal, so the store filter would be stripped
|
|
10147
|
+
* and every store on the platform returned. The guard is the control. These
|
|
10148
|
+
* also pointed at `/api/v1/me/*`, which no controller serves, so the observed
|
|
10149
|
+
* failure was a 404 rather than the guard's 403.
|
|
10150
|
+
*/
|
|
10151
|
+
dashboardOnlyUserContext(operation) {
|
|
10152
|
+
throw new BrainerceError(
|
|
10153
|
+
`${operation} resolves the CURRENT USER, so it requires a dashboard session and is rejected for API-key principals by design. An API key is already scoped to one store: read it with getStore(), and read the permissions of a member from getStoreTeam() in the dashboard.`,
|
|
10154
|
+
403
|
|
9857
10155
|
);
|
|
9858
10156
|
}
|
|
9859
10157
|
// -------------------- Email Settings & Templates (Admin) --------------------
|
|
@@ -9996,7 +10294,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9996
10294
|
* Requires Admin mode (apiKey)
|
|
9997
10295
|
*/
|
|
9998
10296
|
async getSyncConflicts() {
|
|
9999
|
-
return this.
|
|
10297
|
+
return this.syncConflictsNotImplemented("getSyncConflicts");
|
|
10000
10298
|
}
|
|
10001
10299
|
/**
|
|
10002
10300
|
* Resolve a sync conflict
|
|
@@ -10006,12 +10304,28 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
10006
10304
|
* @param resolution - 'MERGE' to link to existing product, 'CREATE_NEW' to create new product
|
|
10007
10305
|
*/
|
|
10008
10306
|
async resolveSyncConflict(conflictId, resolution) {
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10307
|
+
void conflictId;
|
|
10308
|
+
void resolution;
|
|
10309
|
+
return this.syncConflictsNotImplemented("resolveSyncConflict");
|
|
10310
|
+
}
|
|
10311
|
+
/**
|
|
10312
|
+
* Sync conflicts were never implemented on the server.
|
|
10313
|
+
*
|
|
10314
|
+
* There is no `sync-conflict` route anywhere in the backend — not under
|
|
10315
|
+
* `@Controller('v1')`, not on any other controller. These two methods have
|
|
10316
|
+
* called a URL that has never existed, and `SyncConflict` /
|
|
10317
|
+
* `SyncConflictResolution` / `ResolveSyncConflictDto` are exported types with
|
|
10318
|
+
* no producer. The METAFIELD conflict siblings below are real
|
|
10319
|
+
* (external-api.controller.ts:4788, :4802) and are easy to mistake for these.
|
|
10320
|
+
*
|
|
10321
|
+
* Kept as throwing stubs rather than deleted: removing exported methods from a
|
|
10322
|
+
* published package is a breaking change, and a caller who has been swallowing
|
|
10323
|
+
* a 404 deserves to be told why.
|
|
10324
|
+
*/
|
|
10325
|
+
syncConflictsNotImplemented(operation) {
|
|
10326
|
+
throw new BrainerceError(
|
|
10327
|
+
`${operation} is not implemented: the platform exposes no sync-conflict endpoint. If you are looking for metafield sync conflicts, use getMetafieldConflicts() / resolveMetafieldConflict().`,
|
|
10328
|
+
501
|
|
10015
10329
|
);
|
|
10016
10330
|
}
|
|
10017
10331
|
// -------------------- Metafield Conflicts (Admin) --------------------
|
|
@@ -10486,9 +10800,7 @@ function validateDateAvailabilityConfig(config, fieldType, surface = "checkout")
|
|
|
10486
10800
|
(k) => config[k] !== void 0
|
|
10487
10801
|
);
|
|
10488
10802
|
if (relativeKeys.length > 0) {
|
|
10489
|
-
errors.push(
|
|
10490
|
-
`${relativeKeys.join("/")} only apply to checkout fields, not ${surface} fields`
|
|
10491
|
-
);
|
|
10803
|
+
errors.push(`${relativeKeys.join("/")} only apply to checkout fields, not ${surface} fields`);
|
|
10492
10804
|
}
|
|
10493
10805
|
}
|
|
10494
10806
|
if (config.blockedWeekdays) {
|
|
@@ -10511,7 +10823,9 @@ function validateDateAvailabilityConfig(config, fieldType, surface = "checkout")
|
|
|
10511
10823
|
const windowsByWeekday = /* @__PURE__ */ new Map();
|
|
10512
10824
|
for (const w of config.businessHours) {
|
|
10513
10825
|
if (!Number.isInteger(w.weekday) || w.weekday < 0 || w.weekday > 6) {
|
|
10514
|
-
errors.push(
|
|
10826
|
+
errors.push(
|
|
10827
|
+
`businessHours.weekday must be an integer 0-6, got ${JSON.stringify(w.weekday)}`
|
|
10828
|
+
);
|
|
10515
10829
|
continue;
|
|
10516
10830
|
}
|
|
10517
10831
|
const openValid = TIME_RE.test(w.open);
|
|
@@ -10623,7 +10937,8 @@ function addCalendarDays(dateYYYYMMDD, days) {
|
|
|
10623
10937
|
function parseDateFieldValue(raw, fieldType, timezone) {
|
|
10624
10938
|
const str = raw instanceof Date ? Number.isNaN(raw.getTime()) ? "" : raw.toISOString() : typeof raw === "string" ? raw.trim() : "";
|
|
10625
10939
|
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";
|
|
10626
|
-
if (!str)
|
|
10940
|
+
if (!str)
|
|
10941
|
+
return { status: "invalid", reason: `"${String(raw)}" is not a valid date \u2014 ${expected}` };
|
|
10627
10942
|
const dateOnly = DATE_ONLY_RE.exec(str);
|
|
10628
10943
|
const dateTime = dateOnly ? null : DATE_TIME_RE.exec(str);
|
|
10629
10944
|
const match = dateOnly ?? dateTime;
|
|
@@ -10712,7 +11027,14 @@ function timezoneOffsetMs(utcMillis, timezone) {
|
|
|
10712
11027
|
}
|
|
10713
11028
|
const get = (type) => Number(parts.find((p) => p.type === type)?.value ?? "0");
|
|
10714
11029
|
const hour = get("hour") === 24 ? 0 : get("hour");
|
|
10715
|
-
const asIfUtc = Date.UTC(
|
|
11030
|
+
const asIfUtc = Date.UTC(
|
|
11031
|
+
get("year"),
|
|
11032
|
+
get("month") - 1,
|
|
11033
|
+
get("day"),
|
|
11034
|
+
hour,
|
|
11035
|
+
get("minute"),
|
|
11036
|
+
get("second")
|
|
11037
|
+
);
|
|
10716
11038
|
return asIfUtc - Math.floor(utcMillis / 1e3) * 1e3;
|
|
10717
11039
|
}
|
|
10718
11040
|
function isCalendarDateAllowed(dateYYYYMMDD, config, clock) {
|