brainerce 1.39.0 → 1.41.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 +27 -5
- package/dist/index.d.mts +119 -24
- package/dist/index.d.ts +119 -24
- package/dist/index.js +62 -25
- package/dist/index.mjs +62 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -400,7 +400,7 @@ if (!page) notFound();
|
|
|
400
400
|
|
|
401
401
|
All `get` / `getBySlug` return `null` on 404 — render a hard-coded fallback so the page never crashes when the merchant hasn't seeded yet.
|
|
402
402
|
|
|
403
|
-
**Security**: `FAQ.items[i].answer`, `RICH_TEXT.html`,
|
|
403
|
+
**Security**: `FAQ.items[i].answer`, `RICH_TEXT.html`, `PAGE.html`, and `Product.description` are **merchant-authored HTML**. The server does NOT pre-sanitize FAQ/RICH_TEXT/PAGE (merchants may embed iframes); `Product.description` is server-sanitized on write but you still sanitize on render. `Product.description` may contain `<video>` and host-locked YouTube/Vimeo `<iframe>` embeds — allow those tags (iframe restricted to `www.youtube.com` / `www.youtube-nocookie.com` / `player.vimeo.com`) and add those hosts to your CSP `frame-src`. ALWAYS sanitize before injecting:
|
|
404
404
|
|
|
405
405
|
```typescript
|
|
406
406
|
import DOMPurify from 'isomorphic-dompurify';
|
|
@@ -2375,7 +2375,16 @@ const checkoutId = checkout.id;
|
|
|
2375
2375
|
|
|
2376
2376
|
#### Get All Payment Providers (Recommended)
|
|
2377
2377
|
|
|
2378
|
-
Use this method to get ALL enabled payment providers and build dynamic UI
|
|
2378
|
+
Use this method to get ALL enabled payment providers and build dynamic UI.
|
|
2379
|
+
|
|
2380
|
+
**Primary vs. additive methods (Shopify-parity).** Each provider carries a
|
|
2381
|
+
`methodType`. A `CREDIT_CARD` provider is the *primary* card processor — the
|
|
2382
|
+
single method that settles the order (`defaultProvider`, `isAdditive: false`,
|
|
2383
|
+
`presentation: 'card_form'`). Everything else is *additive* (`isAdditive: true`),
|
|
2384
|
+
e.g. PayPal is a `'WALLET'` with `presentation: 'express_button'`. Render additive
|
|
2385
|
+
methods as accelerated-checkout **express buttons above the card form** — they sit
|
|
2386
|
+
*alongside* the primary, never replace it. (Exception: a wallet-only store has no
|
|
2387
|
+
card processor, so its wallet becomes the `defaultProvider` and stands alone.)
|
|
2379
2388
|
|
|
2380
2389
|
```typescript
|
|
2381
2390
|
const { hasPayments, providers, defaultProvider } = await client.getPaymentProviders();
|
|
@@ -2391,7 +2400,10 @@ const { hasPayments, providers, defaultProvider } = await client.getPaymentProvi
|
|
|
2391
2400
|
// publicKey: 'pk_live_xxx...',
|
|
2392
2401
|
// supportedMethods: ['card', 'ideal'],
|
|
2393
2402
|
// testMode: false,
|
|
2394
|
-
// isDefault: true
|
|
2403
|
+
// isDefault: true,
|
|
2404
|
+
// methodType: 'CREDIT_CARD', // primary card processor
|
|
2405
|
+
// presentation: 'card_form',
|
|
2406
|
+
// isAdditive: false
|
|
2395
2407
|
// },
|
|
2396
2408
|
// {
|
|
2397
2409
|
// id: 'provider_yyy',
|
|
@@ -2400,12 +2412,20 @@ const { hasPayments, providers, defaultProvider } = await client.getPaymentProvi
|
|
|
2400
2412
|
// publicKey: 'client_id_xxx...',
|
|
2401
2413
|
// supportedMethods: ['paypal'],
|
|
2402
2414
|
// testMode: false,
|
|
2403
|
-
// isDefault: false
|
|
2415
|
+
// isDefault: false,
|
|
2416
|
+
// methodType: 'WALLET', // additive — render as an express button
|
|
2417
|
+
// presentation: 'express_button',
|
|
2418
|
+
// isAdditive: true
|
|
2404
2419
|
// }
|
|
2405
2420
|
// ],
|
|
2406
|
-
// defaultProvider: { ... } // The
|
|
2421
|
+
// defaultProvider: { ... } // The settling primary (never an additive wallet
|
|
2422
|
+
// // while a card processor is installed)
|
|
2407
2423
|
// }
|
|
2408
2424
|
|
|
2425
|
+
// Recommended pattern: express buttons (additive) above the primary card form.
|
|
2426
|
+
const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
|
|
2427
|
+
const primary = defaultProvider && !defaultProvider.isAdditive ? defaultProvider : undefined;
|
|
2428
|
+
|
|
2409
2429
|
// Build dynamic UI based on available providers
|
|
2410
2430
|
if (!hasPayments) {
|
|
2411
2431
|
return <div>Payment not configured for this store</div>;
|
|
@@ -3424,6 +3444,8 @@ client.trackEvent({ eventType: 'engagement', path: '/products/shoes', engagedMs:
|
|
|
3424
3444
|
|
|
3425
3445
|
> `trackEvent()` is fire-and-forget — errors are silently swallowed so a failed beacon never breaks your storefront. Available in `salesChannelId` and `storeId` modes.
|
|
3426
3446
|
|
|
3447
|
+
> **Analytics always goes directly to the Brainerce API** (`keepalive` beacon), even when you route the rest of the SDK through a same-origin BFF proxy (`proxyMode` / `baseUrl: '/api/store'`). This is deliberate: a beacon relayed through your server would arrive from the server's datacenter IP and mis-geolocate **every** visitor to that region (e.g. a Vercel US function → all visitors show as US). When `proxyMode` is on — or `baseUrl` shares the page origin — the SDK auto-targets `https://api.brainerce.com` for beacons; override with `analyticsBaseUrl` only if your API is self-hosted.
|
|
3448
|
+
|
|
3427
3449
|
**Privacy:** cookieless, no PII stored. The visitor IP is resolved to a country server-side and then discarded. No cookie-consent banner required under GDPR/ePrivacy.
|
|
3428
3450
|
|
|
3429
3451
|
---
|
package/dist/index.d.mts
CHANGED
|
@@ -112,6 +112,32 @@ interface BrainerceClientOptions {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
proxyMode?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Base URL used for analytics beacons (`trackEvent`) ONLY — everything else
|
|
117
|
+
* keeps using `baseUrl`.
|
|
118
|
+
*
|
|
119
|
+
* Analytics must reach the Brainerce API **directly from the browser** so the
|
|
120
|
+
* visitor's real IP — and therefore country — is preserved. If a beacon is routed
|
|
121
|
+
* through a same-origin BFF proxy (see `proxyMode`), it arrives at the backend from
|
|
122
|
+
* the proxy server's datacenter IP instead (e.g. a Vercel US region), and every
|
|
123
|
+
* visitor is mis-geolocated to that datacenter.
|
|
124
|
+
*
|
|
125
|
+
* You normally do NOT need to set this: when `proxyMode` is on (or `baseUrl` shares
|
|
126
|
+
* the current page's origin), the SDK automatically sends analytics to
|
|
127
|
+
* `https://api.brainerce.com`. Set it explicitly only when your Brainerce API lives
|
|
128
|
+
* at a custom / self-hosted origin.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const client = new BrainerceClient({
|
|
133
|
+
* connectionId: 'vc_abc123...',
|
|
134
|
+
* baseUrl: '/api/store', // products / cart / auth go through the BFF proxy
|
|
135
|
+
* proxyMode: true,
|
|
136
|
+
* // analytics auto-routes direct to https://api.brainerce.com — no config needed
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
analyticsBaseUrl?: string;
|
|
115
141
|
}
|
|
116
142
|
interface StoreInfo {
|
|
117
143
|
id: string;
|
|
@@ -256,13 +282,20 @@ interface Product {
|
|
|
256
282
|
*/
|
|
257
283
|
localeSlugs?: Record<string, string> | null;
|
|
258
284
|
/**
|
|
259
|
-
* Product description as HTML.
|
|
285
|
+
* Product description as HTML. May contain self-hosted `<video>` and
|
|
286
|
+
* host-locked YouTube/Vimeo `<iframe>` embeds. **Always sanitize before
|
|
287
|
+
* rendering**, e.g.:
|
|
260
288
|
*
|
|
261
289
|
* import { sanitizeHTML } from '@brainerce/utils';
|
|
262
290
|
* <div dangerouslySetInnerHTML={{ __html: sanitizeHTML(product.description) }} />
|
|
263
291
|
*
|
|
264
|
-
* The backend strips dangerous tags/attributes on write, but storefronts
|
|
265
|
-
* still treat
|
|
292
|
+
* The backend strips dangerous tags/attributes on write, but storefronts
|
|
293
|
+
* should still treat this as untrusted HTML and sanitize on render. Your
|
|
294
|
+
* sanitizer must allow `video`/`source` and — for embeds — `iframe` restricted
|
|
295
|
+
* to the hosts `www.youtube.com`, `www.youtube-nocookie.com`,
|
|
296
|
+
* `player.vimeo.com` (never arbitrary iframes). Sizing is on the `width`/
|
|
297
|
+
* `height` attributes (inline `style` is stripped). If you set a CSP, add
|
|
298
|
+
* those three hosts to `frame-src` or embeds render blank.
|
|
266
299
|
*/
|
|
267
300
|
description?: string | null;
|
|
268
301
|
descriptionFormat?: 'text' | 'html' | 'markdown' | null;
|
|
@@ -1064,7 +1097,14 @@ interface CreateProductDto {
|
|
|
1064
1097
|
status?: 'active' | 'draft';
|
|
1065
1098
|
type?: 'SIMPLE' | 'VARIABLE';
|
|
1066
1099
|
isDownloadable?: boolean;
|
|
1100
|
+
/** Existing category IDs to assign. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `categoryNames`. */
|
|
1067
1101
|
categories?: string[];
|
|
1102
|
+
/** Category names to assign — matched case-insensitively and auto-created if none exists. Merged with any IDs in `categories`. */
|
|
1103
|
+
categoryNames?: string[];
|
|
1104
|
+
/** Existing brand IDs to assign. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `brandNames`. */
|
|
1105
|
+
brands?: string[];
|
|
1106
|
+
/** Brand names to assign — matched case-insensitively and auto-created if none exists. Merged with any IDs in `brands`. */
|
|
1107
|
+
brandNames?: string[];
|
|
1068
1108
|
tags?: string[];
|
|
1069
1109
|
images?: ProductImage[];
|
|
1070
1110
|
}
|
|
@@ -1078,7 +1118,14 @@ interface UpdateProductDto {
|
|
|
1078
1118
|
costPrice?: number | null;
|
|
1079
1119
|
status?: 'active' | 'draft';
|
|
1080
1120
|
isDownloadable?: boolean;
|
|
1121
|
+
/** Existing category IDs to assign (replaces the current set). Pass `[]` to clear. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `categoryNames`. */
|
|
1081
1122
|
categories?: string[];
|
|
1123
|
+
/** Category names to assign — matched case-insensitively and auto-created if missing. Additive: added to `categories` (or to the product's current categories when `categories` is omitted). */
|
|
1124
|
+
categoryNames?: string[];
|
|
1125
|
+
/** Existing brand IDs to assign (replaces the current set). Pass `[]` to clear. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `brandNames`. */
|
|
1126
|
+
brands?: string[];
|
|
1127
|
+
/** Brand names to assign — matched case-insensitively and auto-created if missing. Additive: added to `brands` (or to the product's current brands when `brands` is omitted). */
|
|
1128
|
+
brandNames?: string[];
|
|
1082
1129
|
tags?: string[];
|
|
1083
1130
|
images?: ProductImage[];
|
|
1084
1131
|
}
|
|
@@ -3098,6 +3145,18 @@ interface PaymentClientSdk {
|
|
|
3098
3145
|
*
|
|
3099
3146
|
* **Alias:** `PaymentProvider` is the same type (use either name).
|
|
3100
3147
|
*/
|
|
3148
|
+
/**
|
|
3149
|
+
* Payment-method taxonomy (Shopify-parity). `CREDIT_CARD` is a *primary* card
|
|
3150
|
+
* processor — the single method that settles the order; everything else is an
|
|
3151
|
+
* *additive* method (wallet/alt/local/redeemable) that coexists alongside it.
|
|
3152
|
+
*/
|
|
3153
|
+
type PaymentMethodType = 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
3154
|
+
/**
|
|
3155
|
+
* How a storefront should render a payment method. `card_form` = the main card
|
|
3156
|
+
* fields; `express_button` = an accelerated-checkout button (render it ABOVE the
|
|
3157
|
+
* card form, like PayPal / Apple Pay); `redirect` = send the buyer offsite.
|
|
3158
|
+
*/
|
|
3159
|
+
type PaymentPresentation = 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
3101
3160
|
interface PaymentProviderConfig {
|
|
3102
3161
|
/** Provider ID (use this when creating payment intents) */
|
|
3103
3162
|
id: string;
|
|
@@ -3113,8 +3172,26 @@ interface PaymentProviderConfig {
|
|
|
3113
3172
|
supportedMethods: string[];
|
|
3114
3173
|
/** Whether this is test/sandbox mode */
|
|
3115
3174
|
testMode: boolean;
|
|
3116
|
-
/**
|
|
3175
|
+
/**
|
|
3176
|
+
* Whether this is the store's default *settling* provider. Only a primary
|
|
3177
|
+
* (`methodType: 'CREDIT_CARD'`) provider is ever the default — an additive
|
|
3178
|
+
* wallet never becomes the default while a card processor is installed.
|
|
3179
|
+
*/
|
|
3117
3180
|
isDefault: boolean;
|
|
3181
|
+
/**
|
|
3182
|
+
* Payment-method taxonomy — primary card processor vs additive wallet/alt.
|
|
3183
|
+
* Use it to place the method: primary → the main card form; additive → an
|
|
3184
|
+
* express button. See `presentation`.
|
|
3185
|
+
*/
|
|
3186
|
+
methodType: PaymentMethodType;
|
|
3187
|
+
/** Rendering hint derived from `methodType` (card_form | express_button | …). */
|
|
3188
|
+
presentation: PaymentPresentation;
|
|
3189
|
+
/**
|
|
3190
|
+
* True for wallet/alternative methods (e.g. PayPal) that are shown *alongside*
|
|
3191
|
+
* the primary card processor rather than replacing it. Render these as
|
|
3192
|
+
* accelerated-checkout express buttons above the card form.
|
|
3193
|
+
*/
|
|
3194
|
+
isAdditive: boolean;
|
|
3118
3195
|
/** Client-side SDK configuration for providers that render a widget */
|
|
3119
3196
|
clientSdk?: PaymentClientSdk;
|
|
3120
3197
|
}
|
|
@@ -5427,6 +5504,8 @@ declare class BrainerceClient {
|
|
|
5427
5504
|
private readonly storeId?;
|
|
5428
5505
|
private readonly connectionId?;
|
|
5429
5506
|
private readonly baseUrl;
|
|
5507
|
+
/** Direct API base for analytics beacons — bypasses any same-origin BFF proxy so the visitor's real IP (hence country) reaches the backend. */
|
|
5508
|
+
private readonly analyticsBaseUrl;
|
|
5430
5509
|
private readonly timeout;
|
|
5431
5510
|
private customerToken;
|
|
5432
5511
|
private customerCartId;
|
|
@@ -5596,6 +5675,20 @@ declare class BrainerceClient {
|
|
|
5596
5675
|
* ```
|
|
5597
5676
|
*/
|
|
5598
5677
|
trackEvent(payload?: TrackEventPayload): Promise<void>;
|
|
5678
|
+
/**
|
|
5679
|
+
* Resolve the base URL for analytics beacons. Analytics must go DIRECTLY to the
|
|
5680
|
+
* Brainerce API (never through a same-origin BFF proxy) so the visitor's real IP —
|
|
5681
|
+
* hence country — reaches the backend. Explicit `analyticsBaseUrl` wins; otherwise
|
|
5682
|
+
* use the direct API when `baseUrl` is a same-origin proxy, else the (already-direct)
|
|
5683
|
+
* `baseUrl` (which may be a custom / self-hosted Brainerce API).
|
|
5684
|
+
*/
|
|
5685
|
+
private resolveAnalyticsBaseUrl;
|
|
5686
|
+
/**
|
|
5687
|
+
* Fire-and-forget analytics beacon sent directly to `analyticsBaseUrl`. Uses
|
|
5688
|
+
* `keepalive` so it survives page unload (engagement events fired on pagehide),
|
|
5689
|
+
* omits credentials (cookieless), and never throws.
|
|
5690
|
+
*/
|
|
5691
|
+
private sendAnalyticsBeacon;
|
|
5599
5692
|
/**
|
|
5600
5693
|
* Get a list of products with pagination and filtering
|
|
5601
5694
|
* Works in vibe-coded, storefront (public), and admin mode
|
|
@@ -7930,6 +8023,14 @@ declare class BrainerceClient {
|
|
|
7930
8023
|
*
|
|
7931
8024
|
* @returns Payment providers config with hasPayments flag and provider list
|
|
7932
8025
|
*
|
|
8026
|
+
* Each provider carries a `methodType` (Shopify-parity taxonomy) and a derived
|
|
8027
|
+
* `isAdditive` / `presentation`. There is at most ONE primary card processor
|
|
8028
|
+
* (`methodType: 'CREDIT_CARD'`, the `defaultProvider`) that settles the order;
|
|
8029
|
+
* additive methods (`isAdditive: true`, e.g. PayPal as a `'WALLET'`) are shown
|
|
8030
|
+
* *alongside* it as accelerated-checkout express buttons — they never replace
|
|
8031
|
+
* the card form. A wallet-only store is the one exception: with no card
|
|
8032
|
+
* processor installed, the wallet becomes the `defaultProvider` and stands alone.
|
|
8033
|
+
*
|
|
7933
8034
|
* @example
|
|
7934
8035
|
* ```typescript
|
|
7935
8036
|
* const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
@@ -7939,27 +8040,21 @@ declare class BrainerceClient {
|
|
|
7939
8040
|
* return;
|
|
7940
8041
|
* }
|
|
7941
8042
|
*
|
|
7942
|
-
* //
|
|
7943
|
-
* const
|
|
7944
|
-
*
|
|
7945
|
-
*
|
|
7946
|
-
*
|
|
7947
|
-
* if (stripeProvider) {
|
|
7948
|
-
* const stripe = await loadStripe(stripeProvider.publicKey, {
|
|
7949
|
-
* stripeAccount: stripeProvider.stripeAccountId, // For Stripe Connect
|
|
7950
|
-
* });
|
|
7951
|
-
* // Show Stripe payment form
|
|
7952
|
-
* }
|
|
7953
|
-
*
|
|
7954
|
-
* // Initialize PayPal if available
|
|
7955
|
-
* if (paypalProvider) {
|
|
7956
|
-
* // Load PayPal SDK with paypalProvider.publicKey as client-id
|
|
7957
|
-
* // Show PayPal buttons
|
|
8043
|
+
* // Render additive methods (wallets) as express buttons ABOVE the card form.
|
|
8044
|
+
* const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
|
|
8045
|
+
* for (const m of expressMethods) {
|
|
8046
|
+
* // m.presentation === 'express_button' — render an express/accelerated button
|
|
8047
|
+
* // and call createPaymentIntent({ providerId: m.id }) when the buyer taps it.
|
|
7958
8048
|
* }
|
|
7959
8049
|
*
|
|
7960
|
-
* //
|
|
7961
|
-
* if (defaultProvider) {
|
|
7962
|
-
*
|
|
8050
|
+
* // Render the primary card processor (the settling default) as the main form.
|
|
8051
|
+
* if (defaultProvider && !defaultProvider.isAdditive) {
|
|
8052
|
+
* if (defaultProvider.provider === 'stripe') {
|
|
8053
|
+
* const stripe = await loadStripe(defaultProvider.publicKey, {
|
|
8054
|
+
* stripeAccount: defaultProvider.stripeAccountId, // For Stripe Connect
|
|
8055
|
+
* });
|
|
8056
|
+
* // Show the Stripe card form
|
|
8057
|
+
* }
|
|
7963
8058
|
* }
|
|
7964
8059
|
* ```
|
|
7965
8060
|
*/
|
|
@@ -9614,7 +9709,7 @@ declare class BrainerceError extends Error {
|
|
|
9614
9709
|
constructor(message: string, statusCode: number, details?: unknown);
|
|
9615
9710
|
}
|
|
9616
9711
|
|
|
9617
|
-
declare const SDK_VERSION = "1.
|
|
9712
|
+
declare const SDK_VERSION = "1.41.0";
|
|
9618
9713
|
|
|
9619
9714
|
/**
|
|
9620
9715
|
* Verify a webhook signature from Brainerce
|
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,32 @@ interface BrainerceClientOptions {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
proxyMode?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Base URL used for analytics beacons (`trackEvent`) ONLY — everything else
|
|
117
|
+
* keeps using `baseUrl`.
|
|
118
|
+
*
|
|
119
|
+
* Analytics must reach the Brainerce API **directly from the browser** so the
|
|
120
|
+
* visitor's real IP — and therefore country — is preserved. If a beacon is routed
|
|
121
|
+
* through a same-origin BFF proxy (see `proxyMode`), it arrives at the backend from
|
|
122
|
+
* the proxy server's datacenter IP instead (e.g. a Vercel US region), and every
|
|
123
|
+
* visitor is mis-geolocated to that datacenter.
|
|
124
|
+
*
|
|
125
|
+
* You normally do NOT need to set this: when `proxyMode` is on (or `baseUrl` shares
|
|
126
|
+
* the current page's origin), the SDK automatically sends analytics to
|
|
127
|
+
* `https://api.brainerce.com`. Set it explicitly only when your Brainerce API lives
|
|
128
|
+
* at a custom / self-hosted origin.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const client = new BrainerceClient({
|
|
133
|
+
* connectionId: 'vc_abc123...',
|
|
134
|
+
* baseUrl: '/api/store', // products / cart / auth go through the BFF proxy
|
|
135
|
+
* proxyMode: true,
|
|
136
|
+
* // analytics auto-routes direct to https://api.brainerce.com — no config needed
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
analyticsBaseUrl?: string;
|
|
115
141
|
}
|
|
116
142
|
interface StoreInfo {
|
|
117
143
|
id: string;
|
|
@@ -256,13 +282,20 @@ interface Product {
|
|
|
256
282
|
*/
|
|
257
283
|
localeSlugs?: Record<string, string> | null;
|
|
258
284
|
/**
|
|
259
|
-
* Product description as HTML.
|
|
285
|
+
* Product description as HTML. May contain self-hosted `<video>` and
|
|
286
|
+
* host-locked YouTube/Vimeo `<iframe>` embeds. **Always sanitize before
|
|
287
|
+
* rendering**, e.g.:
|
|
260
288
|
*
|
|
261
289
|
* import { sanitizeHTML } from '@brainerce/utils';
|
|
262
290
|
* <div dangerouslySetInnerHTML={{ __html: sanitizeHTML(product.description) }} />
|
|
263
291
|
*
|
|
264
|
-
* The backend strips dangerous tags/attributes on write, but storefronts
|
|
265
|
-
* still treat
|
|
292
|
+
* The backend strips dangerous tags/attributes on write, but storefronts
|
|
293
|
+
* should still treat this as untrusted HTML and sanitize on render. Your
|
|
294
|
+
* sanitizer must allow `video`/`source` and — for embeds — `iframe` restricted
|
|
295
|
+
* to the hosts `www.youtube.com`, `www.youtube-nocookie.com`,
|
|
296
|
+
* `player.vimeo.com` (never arbitrary iframes). Sizing is on the `width`/
|
|
297
|
+
* `height` attributes (inline `style` is stripped). If you set a CSP, add
|
|
298
|
+
* those three hosts to `frame-src` or embeds render blank.
|
|
266
299
|
*/
|
|
267
300
|
description?: string | null;
|
|
268
301
|
descriptionFormat?: 'text' | 'html' | 'markdown' | null;
|
|
@@ -1064,7 +1097,14 @@ interface CreateProductDto {
|
|
|
1064
1097
|
status?: 'active' | 'draft';
|
|
1065
1098
|
type?: 'SIMPLE' | 'VARIABLE';
|
|
1066
1099
|
isDownloadable?: boolean;
|
|
1100
|
+
/** Existing category IDs to assign. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `categoryNames`. */
|
|
1067
1101
|
categories?: string[];
|
|
1102
|
+
/** Category names to assign — matched case-insensitively and auto-created if none exists. Merged with any IDs in `categories`. */
|
|
1103
|
+
categoryNames?: string[];
|
|
1104
|
+
/** Existing brand IDs to assign. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `brandNames`. */
|
|
1105
|
+
brands?: string[];
|
|
1106
|
+
/** Brand names to assign — matched case-insensitively and auto-created if none exists. Merged with any IDs in `brands`. */
|
|
1107
|
+
brandNames?: string[];
|
|
1068
1108
|
tags?: string[];
|
|
1069
1109
|
images?: ProductImage[];
|
|
1070
1110
|
}
|
|
@@ -1078,7 +1118,14 @@ interface UpdateProductDto {
|
|
|
1078
1118
|
costPrice?: number | null;
|
|
1079
1119
|
status?: 'active' | 'draft';
|
|
1080
1120
|
isDownloadable?: boolean;
|
|
1121
|
+
/** Existing category IDs to assign (replaces the current set). Pass `[]` to clear. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `categoryNames`. */
|
|
1081
1122
|
categories?: string[];
|
|
1123
|
+
/** Category names to assign — matched case-insensitively and auto-created if missing. Additive: added to `categories` (or to the product's current categories when `categories` is omitted). */
|
|
1124
|
+
categoryNames?: string[];
|
|
1125
|
+
/** Existing brand IDs to assign (replaces the current set). Pass `[]` to clear. Unknown/cross-store IDs are rejected with 400. To assign by name (auto-creating if missing), use `brandNames`. */
|
|
1126
|
+
brands?: string[];
|
|
1127
|
+
/** Brand names to assign — matched case-insensitively and auto-created if missing. Additive: added to `brands` (or to the product's current brands when `brands` is omitted). */
|
|
1128
|
+
brandNames?: string[];
|
|
1082
1129
|
tags?: string[];
|
|
1083
1130
|
images?: ProductImage[];
|
|
1084
1131
|
}
|
|
@@ -3098,6 +3145,18 @@ interface PaymentClientSdk {
|
|
|
3098
3145
|
*
|
|
3099
3146
|
* **Alias:** `PaymentProvider` is the same type (use either name).
|
|
3100
3147
|
*/
|
|
3148
|
+
/**
|
|
3149
|
+
* Payment-method taxonomy (Shopify-parity). `CREDIT_CARD` is a *primary* card
|
|
3150
|
+
* processor — the single method that settles the order; everything else is an
|
|
3151
|
+
* *additive* method (wallet/alt/local/redeemable) that coexists alongside it.
|
|
3152
|
+
*/
|
|
3153
|
+
type PaymentMethodType = 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
3154
|
+
/**
|
|
3155
|
+
* How a storefront should render a payment method. `card_form` = the main card
|
|
3156
|
+
* fields; `express_button` = an accelerated-checkout button (render it ABOVE the
|
|
3157
|
+
* card form, like PayPal / Apple Pay); `redirect` = send the buyer offsite.
|
|
3158
|
+
*/
|
|
3159
|
+
type PaymentPresentation = 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
3101
3160
|
interface PaymentProviderConfig {
|
|
3102
3161
|
/** Provider ID (use this when creating payment intents) */
|
|
3103
3162
|
id: string;
|
|
@@ -3113,8 +3172,26 @@ interface PaymentProviderConfig {
|
|
|
3113
3172
|
supportedMethods: string[];
|
|
3114
3173
|
/** Whether this is test/sandbox mode */
|
|
3115
3174
|
testMode: boolean;
|
|
3116
|
-
/**
|
|
3175
|
+
/**
|
|
3176
|
+
* Whether this is the store's default *settling* provider. Only a primary
|
|
3177
|
+
* (`methodType: 'CREDIT_CARD'`) provider is ever the default — an additive
|
|
3178
|
+
* wallet never becomes the default while a card processor is installed.
|
|
3179
|
+
*/
|
|
3117
3180
|
isDefault: boolean;
|
|
3181
|
+
/**
|
|
3182
|
+
* Payment-method taxonomy — primary card processor vs additive wallet/alt.
|
|
3183
|
+
* Use it to place the method: primary → the main card form; additive → an
|
|
3184
|
+
* express button. See `presentation`.
|
|
3185
|
+
*/
|
|
3186
|
+
methodType: PaymentMethodType;
|
|
3187
|
+
/** Rendering hint derived from `methodType` (card_form | express_button | …). */
|
|
3188
|
+
presentation: PaymentPresentation;
|
|
3189
|
+
/**
|
|
3190
|
+
* True for wallet/alternative methods (e.g. PayPal) that are shown *alongside*
|
|
3191
|
+
* the primary card processor rather than replacing it. Render these as
|
|
3192
|
+
* accelerated-checkout express buttons above the card form.
|
|
3193
|
+
*/
|
|
3194
|
+
isAdditive: boolean;
|
|
3118
3195
|
/** Client-side SDK configuration for providers that render a widget */
|
|
3119
3196
|
clientSdk?: PaymentClientSdk;
|
|
3120
3197
|
}
|
|
@@ -5427,6 +5504,8 @@ declare class BrainerceClient {
|
|
|
5427
5504
|
private readonly storeId?;
|
|
5428
5505
|
private readonly connectionId?;
|
|
5429
5506
|
private readonly baseUrl;
|
|
5507
|
+
/** Direct API base for analytics beacons — bypasses any same-origin BFF proxy so the visitor's real IP (hence country) reaches the backend. */
|
|
5508
|
+
private readonly analyticsBaseUrl;
|
|
5430
5509
|
private readonly timeout;
|
|
5431
5510
|
private customerToken;
|
|
5432
5511
|
private customerCartId;
|
|
@@ -5596,6 +5675,20 @@ declare class BrainerceClient {
|
|
|
5596
5675
|
* ```
|
|
5597
5676
|
*/
|
|
5598
5677
|
trackEvent(payload?: TrackEventPayload): Promise<void>;
|
|
5678
|
+
/**
|
|
5679
|
+
* Resolve the base URL for analytics beacons. Analytics must go DIRECTLY to the
|
|
5680
|
+
* Brainerce API (never through a same-origin BFF proxy) so the visitor's real IP —
|
|
5681
|
+
* hence country — reaches the backend. Explicit `analyticsBaseUrl` wins; otherwise
|
|
5682
|
+
* use the direct API when `baseUrl` is a same-origin proxy, else the (already-direct)
|
|
5683
|
+
* `baseUrl` (which may be a custom / self-hosted Brainerce API).
|
|
5684
|
+
*/
|
|
5685
|
+
private resolveAnalyticsBaseUrl;
|
|
5686
|
+
/**
|
|
5687
|
+
* Fire-and-forget analytics beacon sent directly to `analyticsBaseUrl`. Uses
|
|
5688
|
+
* `keepalive` so it survives page unload (engagement events fired on pagehide),
|
|
5689
|
+
* omits credentials (cookieless), and never throws.
|
|
5690
|
+
*/
|
|
5691
|
+
private sendAnalyticsBeacon;
|
|
5599
5692
|
/**
|
|
5600
5693
|
* Get a list of products with pagination and filtering
|
|
5601
5694
|
* Works in vibe-coded, storefront (public), and admin mode
|
|
@@ -7930,6 +8023,14 @@ declare class BrainerceClient {
|
|
|
7930
8023
|
*
|
|
7931
8024
|
* @returns Payment providers config with hasPayments flag and provider list
|
|
7932
8025
|
*
|
|
8026
|
+
* Each provider carries a `methodType` (Shopify-parity taxonomy) and a derived
|
|
8027
|
+
* `isAdditive` / `presentation`. There is at most ONE primary card processor
|
|
8028
|
+
* (`methodType: 'CREDIT_CARD'`, the `defaultProvider`) that settles the order;
|
|
8029
|
+
* additive methods (`isAdditive: true`, e.g. PayPal as a `'WALLET'`) are shown
|
|
8030
|
+
* *alongside* it as accelerated-checkout express buttons — they never replace
|
|
8031
|
+
* the card form. A wallet-only store is the one exception: with no card
|
|
8032
|
+
* processor installed, the wallet becomes the `defaultProvider` and stands alone.
|
|
8033
|
+
*
|
|
7933
8034
|
* @example
|
|
7934
8035
|
* ```typescript
|
|
7935
8036
|
* const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
@@ -7939,27 +8040,21 @@ declare class BrainerceClient {
|
|
|
7939
8040
|
* return;
|
|
7940
8041
|
* }
|
|
7941
8042
|
*
|
|
7942
|
-
* //
|
|
7943
|
-
* const
|
|
7944
|
-
*
|
|
7945
|
-
*
|
|
7946
|
-
*
|
|
7947
|
-
* if (stripeProvider) {
|
|
7948
|
-
* const stripe = await loadStripe(stripeProvider.publicKey, {
|
|
7949
|
-
* stripeAccount: stripeProvider.stripeAccountId, // For Stripe Connect
|
|
7950
|
-
* });
|
|
7951
|
-
* // Show Stripe payment form
|
|
7952
|
-
* }
|
|
7953
|
-
*
|
|
7954
|
-
* // Initialize PayPal if available
|
|
7955
|
-
* if (paypalProvider) {
|
|
7956
|
-
* // Load PayPal SDK with paypalProvider.publicKey as client-id
|
|
7957
|
-
* // Show PayPal buttons
|
|
8043
|
+
* // Render additive methods (wallets) as express buttons ABOVE the card form.
|
|
8044
|
+
* const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
|
|
8045
|
+
* for (const m of expressMethods) {
|
|
8046
|
+
* // m.presentation === 'express_button' — render an express/accelerated button
|
|
8047
|
+
* // and call createPaymentIntent({ providerId: m.id }) when the buyer taps it.
|
|
7958
8048
|
* }
|
|
7959
8049
|
*
|
|
7960
|
-
* //
|
|
7961
|
-
* if (defaultProvider) {
|
|
7962
|
-
*
|
|
8050
|
+
* // Render the primary card processor (the settling default) as the main form.
|
|
8051
|
+
* if (defaultProvider && !defaultProvider.isAdditive) {
|
|
8052
|
+
* if (defaultProvider.provider === 'stripe') {
|
|
8053
|
+
* const stripe = await loadStripe(defaultProvider.publicKey, {
|
|
8054
|
+
* stripeAccount: defaultProvider.stripeAccountId, // For Stripe Connect
|
|
8055
|
+
* });
|
|
8056
|
+
* // Show the Stripe card form
|
|
8057
|
+
* }
|
|
7963
8058
|
* }
|
|
7964
8059
|
* ```
|
|
7965
8060
|
*/
|
|
@@ -9614,7 +9709,7 @@ declare class BrainerceError extends Error {
|
|
|
9614
9709
|
constructor(message: string, statusCode: number, details?: unknown);
|
|
9615
9710
|
}
|
|
9616
9711
|
|
|
9617
|
-
declare const SDK_VERSION = "1.
|
|
9712
|
+
declare const SDK_VERSION = "1.41.0";
|
|
9618
9713
|
|
|
9619
9714
|
/**
|
|
9620
9715
|
* Verify a webhook signature from Brainerce
|
package/dist/index.js
CHANGED
|
@@ -185,7 +185,7 @@ function isDevGuardsEnabled() {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// src/version.ts
|
|
188
|
-
var SDK_VERSION = "1.
|
|
188
|
+
var SDK_VERSION = "1.41.0";
|
|
189
189
|
|
|
190
190
|
// src/client.ts
|
|
191
191
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -653,6 +653,7 @@ var BrainerceClient = class {
|
|
|
653
653
|
}
|
|
654
654
|
}
|
|
655
655
|
this.proxyMode = options.proxyMode || false;
|
|
656
|
+
this.analyticsBaseUrl = this.resolveAnalyticsBaseUrl(options.analyticsBaseUrl, resolvedBase);
|
|
656
657
|
this.onAuthError = options.onAuthError;
|
|
657
658
|
this.hydrateSessionCart();
|
|
658
659
|
this.detectRecoverCartFromUrl();
|
|
@@ -1106,12 +1107,46 @@ var BrainerceClient = class {
|
|
|
1106
1107
|
* ```
|
|
1107
1108
|
*/
|
|
1108
1109
|
async trackEvent(payload = {}) {
|
|
1110
|
+
if (this.isVibeCodedMode() && this.connectionId) {
|
|
1111
|
+
this.sendAnalyticsBeacon(`/api/vc/${encodePathSegment(this.connectionId)}/events`, payload);
|
|
1112
|
+
} else if (this.isStorefrontMode() && this.storeId) {
|
|
1113
|
+
this.sendAnalyticsBeacon(`/api/stores/${encodePathSegment(this.storeId)}/events`, payload);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Resolve the base URL for analytics beacons. Analytics must go DIRECTLY to the
|
|
1118
|
+
* Brainerce API (never through a same-origin BFF proxy) so the visitor's real IP —
|
|
1119
|
+
* hence country — reaches the backend. Explicit `analyticsBaseUrl` wins; otherwise
|
|
1120
|
+
* use the direct API when `baseUrl` is a same-origin proxy, else the (already-direct)
|
|
1121
|
+
* `baseUrl` (which may be a custom / self-hosted Brainerce API).
|
|
1122
|
+
*/
|
|
1123
|
+
resolveAnalyticsBaseUrl(explicit, resolvedBase) {
|
|
1124
|
+
if (explicit) return explicit.replace(/\/$/, "");
|
|
1125
|
+
const sameOriginProxy = this.proxyMode || typeof window !== "undefined" && !!window.location?.origin && resolvedBase.startsWith(window.location.origin);
|
|
1126
|
+
return sameOriginProxy ? DEFAULT_BASE_URL : resolvedBase;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Fire-and-forget analytics beacon sent directly to `analyticsBaseUrl`. Uses
|
|
1130
|
+
* `keepalive` so it survives page unload (engagement events fired on pagehide),
|
|
1131
|
+
* omits credentials (cookieless), and never throws.
|
|
1132
|
+
*/
|
|
1133
|
+
sendAnalyticsBeacon(eventsPath, payload) {
|
|
1109
1134
|
try {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1135
|
+
const headers = {
|
|
1136
|
+
"Content-Type": "application/json",
|
|
1137
|
+
"X-SDK-Version": SDK_VERSION,
|
|
1138
|
+
"ngrok-skip-browser-warning": "true"
|
|
1139
|
+
};
|
|
1140
|
+
if (this.origin) headers["Origin"] = this.origin;
|
|
1141
|
+
void fetch(`${this.analyticsBaseUrl}${eventsPath}`, {
|
|
1142
|
+
method: "POST",
|
|
1143
|
+
headers,
|
|
1144
|
+
body: JSON.stringify(payload),
|
|
1145
|
+
keepalive: true,
|
|
1146
|
+
credentials: "omit",
|
|
1147
|
+
mode: "cors"
|
|
1148
|
+
}).catch(() => {
|
|
1149
|
+
});
|
|
1115
1150
|
} catch {
|
|
1116
1151
|
}
|
|
1117
1152
|
}
|
|
@@ -5229,6 +5264,14 @@ var BrainerceClient = class {
|
|
|
5229
5264
|
*
|
|
5230
5265
|
* @returns Payment providers config with hasPayments flag and provider list
|
|
5231
5266
|
*
|
|
5267
|
+
* Each provider carries a `methodType` (Shopify-parity taxonomy) and a derived
|
|
5268
|
+
* `isAdditive` / `presentation`. There is at most ONE primary card processor
|
|
5269
|
+
* (`methodType: 'CREDIT_CARD'`, the `defaultProvider`) that settles the order;
|
|
5270
|
+
* additive methods (`isAdditive: true`, e.g. PayPal as a `'WALLET'`) are shown
|
|
5271
|
+
* *alongside* it as accelerated-checkout express buttons — they never replace
|
|
5272
|
+
* the card form. A wallet-only store is the one exception: with no card
|
|
5273
|
+
* processor installed, the wallet becomes the `defaultProvider` and stands alone.
|
|
5274
|
+
*
|
|
5232
5275
|
* @example
|
|
5233
5276
|
* ```typescript
|
|
5234
5277
|
* const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
@@ -5238,27 +5281,21 @@ var BrainerceClient = class {
|
|
|
5238
5281
|
* return;
|
|
5239
5282
|
* }
|
|
5240
5283
|
*
|
|
5241
|
-
* //
|
|
5242
|
-
* const
|
|
5243
|
-
*
|
|
5244
|
-
*
|
|
5245
|
-
*
|
|
5246
|
-
* if (stripeProvider) {
|
|
5247
|
-
* const stripe = await loadStripe(stripeProvider.publicKey, {
|
|
5248
|
-
* stripeAccount: stripeProvider.stripeAccountId, // For Stripe Connect
|
|
5249
|
-
* });
|
|
5250
|
-
* // Show Stripe payment form
|
|
5284
|
+
* // Render additive methods (wallets) as express buttons ABOVE the card form.
|
|
5285
|
+
* const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
|
|
5286
|
+
* for (const m of expressMethods) {
|
|
5287
|
+
* // m.presentation === 'express_button' — render an express/accelerated button
|
|
5288
|
+
* // and call createPaymentIntent({ providerId: m.id }) when the buyer taps it.
|
|
5251
5289
|
* }
|
|
5252
5290
|
*
|
|
5253
|
-
* //
|
|
5254
|
-
* if (
|
|
5255
|
-
*
|
|
5256
|
-
*
|
|
5257
|
-
*
|
|
5258
|
-
*
|
|
5259
|
-
*
|
|
5260
|
-
*
|
|
5261
|
-
* console.log(`Using default: ${defaultProvider.name} (${defaultProvider.provider})`);
|
|
5291
|
+
* // Render the primary card processor (the settling default) as the main form.
|
|
5292
|
+
* if (defaultProvider && !defaultProvider.isAdditive) {
|
|
5293
|
+
* if (defaultProvider.provider === 'stripe') {
|
|
5294
|
+
* const stripe = await loadStripe(defaultProvider.publicKey, {
|
|
5295
|
+
* stripeAccount: defaultProvider.stripeAccountId, // For Stripe Connect
|
|
5296
|
+
* });
|
|
5297
|
+
* // Show the Stripe card form
|
|
5298
|
+
* }
|
|
5262
5299
|
* }
|
|
5263
5300
|
* ```
|
|
5264
5301
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// src/version.ts
|
|
118
|
-
var SDK_VERSION = "1.
|
|
118
|
+
var SDK_VERSION = "1.41.0";
|
|
119
119
|
|
|
120
120
|
// src/client.ts
|
|
121
121
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -583,6 +583,7 @@ var BrainerceClient = class {
|
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
585
|
this.proxyMode = options.proxyMode || false;
|
|
586
|
+
this.analyticsBaseUrl = this.resolveAnalyticsBaseUrl(options.analyticsBaseUrl, resolvedBase);
|
|
586
587
|
this.onAuthError = options.onAuthError;
|
|
587
588
|
this.hydrateSessionCart();
|
|
588
589
|
this.detectRecoverCartFromUrl();
|
|
@@ -1036,12 +1037,46 @@ var BrainerceClient = class {
|
|
|
1036
1037
|
* ```
|
|
1037
1038
|
*/
|
|
1038
1039
|
async trackEvent(payload = {}) {
|
|
1040
|
+
if (this.isVibeCodedMode() && this.connectionId) {
|
|
1041
|
+
this.sendAnalyticsBeacon(`/api/vc/${encodePathSegment(this.connectionId)}/events`, payload);
|
|
1042
|
+
} else if (this.isStorefrontMode() && this.storeId) {
|
|
1043
|
+
this.sendAnalyticsBeacon(`/api/stores/${encodePathSegment(this.storeId)}/events`, payload);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Resolve the base URL for analytics beacons. Analytics must go DIRECTLY to the
|
|
1048
|
+
* Brainerce API (never through a same-origin BFF proxy) so the visitor's real IP —
|
|
1049
|
+
* hence country — reaches the backend. Explicit `analyticsBaseUrl` wins; otherwise
|
|
1050
|
+
* use the direct API when `baseUrl` is a same-origin proxy, else the (already-direct)
|
|
1051
|
+
* `baseUrl` (which may be a custom / self-hosted Brainerce API).
|
|
1052
|
+
*/
|
|
1053
|
+
resolveAnalyticsBaseUrl(explicit, resolvedBase) {
|
|
1054
|
+
if (explicit) return explicit.replace(/\/$/, "");
|
|
1055
|
+
const sameOriginProxy = this.proxyMode || typeof window !== "undefined" && !!window.location?.origin && resolvedBase.startsWith(window.location.origin);
|
|
1056
|
+
return sameOriginProxy ? DEFAULT_BASE_URL : resolvedBase;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Fire-and-forget analytics beacon sent directly to `analyticsBaseUrl`. Uses
|
|
1060
|
+
* `keepalive` so it survives page unload (engagement events fired on pagehide),
|
|
1061
|
+
* omits credentials (cookieless), and never throws.
|
|
1062
|
+
*/
|
|
1063
|
+
sendAnalyticsBeacon(eventsPath, payload) {
|
|
1039
1064
|
try {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1065
|
+
const headers = {
|
|
1066
|
+
"Content-Type": "application/json",
|
|
1067
|
+
"X-SDK-Version": SDK_VERSION,
|
|
1068
|
+
"ngrok-skip-browser-warning": "true"
|
|
1069
|
+
};
|
|
1070
|
+
if (this.origin) headers["Origin"] = this.origin;
|
|
1071
|
+
void fetch(`${this.analyticsBaseUrl}${eventsPath}`, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers,
|
|
1074
|
+
body: JSON.stringify(payload),
|
|
1075
|
+
keepalive: true,
|
|
1076
|
+
credentials: "omit",
|
|
1077
|
+
mode: "cors"
|
|
1078
|
+
}).catch(() => {
|
|
1079
|
+
});
|
|
1045
1080
|
} catch {
|
|
1046
1081
|
}
|
|
1047
1082
|
}
|
|
@@ -5159,6 +5194,14 @@ var BrainerceClient = class {
|
|
|
5159
5194
|
*
|
|
5160
5195
|
* @returns Payment providers config with hasPayments flag and provider list
|
|
5161
5196
|
*
|
|
5197
|
+
* Each provider carries a `methodType` (Shopify-parity taxonomy) and a derived
|
|
5198
|
+
* `isAdditive` / `presentation`. There is at most ONE primary card processor
|
|
5199
|
+
* (`methodType: 'CREDIT_CARD'`, the `defaultProvider`) that settles the order;
|
|
5200
|
+
* additive methods (`isAdditive: true`, e.g. PayPal as a `'WALLET'`) are shown
|
|
5201
|
+
* *alongside* it as accelerated-checkout express buttons — they never replace
|
|
5202
|
+
* the card form. A wallet-only store is the one exception: with no card
|
|
5203
|
+
* processor installed, the wallet becomes the `defaultProvider` and stands alone.
|
|
5204
|
+
*
|
|
5162
5205
|
* @example
|
|
5163
5206
|
* ```typescript
|
|
5164
5207
|
* const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
@@ -5168,27 +5211,21 @@ var BrainerceClient = class {
|
|
|
5168
5211
|
* return;
|
|
5169
5212
|
* }
|
|
5170
5213
|
*
|
|
5171
|
-
* //
|
|
5172
|
-
* const
|
|
5173
|
-
*
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
5176
|
-
* if (stripeProvider) {
|
|
5177
|
-
* const stripe = await loadStripe(stripeProvider.publicKey, {
|
|
5178
|
-
* stripeAccount: stripeProvider.stripeAccountId, // For Stripe Connect
|
|
5179
|
-
* });
|
|
5180
|
-
* // Show Stripe payment form
|
|
5214
|
+
* // Render additive methods (wallets) as express buttons ABOVE the card form.
|
|
5215
|
+
* const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal
|
|
5216
|
+
* for (const m of expressMethods) {
|
|
5217
|
+
* // m.presentation === 'express_button' — render an express/accelerated button
|
|
5218
|
+
* // and call createPaymentIntent({ providerId: m.id }) when the buyer taps it.
|
|
5181
5219
|
* }
|
|
5182
5220
|
*
|
|
5183
|
-
* //
|
|
5184
|
-
* if (
|
|
5185
|
-
*
|
|
5186
|
-
*
|
|
5187
|
-
*
|
|
5188
|
-
*
|
|
5189
|
-
*
|
|
5190
|
-
*
|
|
5191
|
-
* console.log(`Using default: ${defaultProvider.name} (${defaultProvider.provider})`);
|
|
5221
|
+
* // Render the primary card processor (the settling default) as the main form.
|
|
5222
|
+
* if (defaultProvider && !defaultProvider.isAdditive) {
|
|
5223
|
+
* if (defaultProvider.provider === 'stripe') {
|
|
5224
|
+
* const stripe = await loadStripe(defaultProvider.publicKey, {
|
|
5225
|
+
* stripeAccount: defaultProvider.stripeAccountId, // For Stripe Connect
|
|
5226
|
+
* });
|
|
5227
|
+
* // Show the Stripe card form
|
|
5228
|
+
* }
|
|
5192
5229
|
* }
|
|
5193
5230
|
* ```
|
|
5194
5231
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainerce",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|