@brainerce/mcp-server 3.11.0 → 3.12.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/dist/bin/http.js CHANGED
@@ -163,7 +163,26 @@ import {
163
163
  getDescriptionContent, isHtmlDescription,
164
164
  getProductMetafieldValue, getProductCustomizationFields,
165
165
  } from 'brainerce';
166
- \`\`\``;
166
+ \`\`\`
167
+
168
+ ### Available features \u2014 request each topic for full docs
169
+
170
+ | Topic | What it covers |
171
+ |-------|----------------|
172
+ | \`products\` | Catalog listing, search, PDP, variants, swatches, stock, attributes |
173
+ | \`cart\` | Guest + logged-in cart, add/remove/update, reservations |
174
+ | \`checkout\` | Guest + customer checkout, shipping, address |
175
+ | \`payment\` | Payment providers, intents, status polling, confirmation |
176
+ | \`auth\` | Customer sign-up, login, OAuth, email verification |
177
+ | \`discounts\` | Coupons, banners, nudges |
178
+ | \`recommendations\` | Cross-sell / upsell grids |
179
+ | \`reviews\` | Product reviews & ratings |
180
+ | \`inventory\` | Stock badges, reservation countdown |
181
+ | \`inquiries\` | Contact / custom forms |
182
+ | \`i18n\` | Multi-language, RTL |
183
+ | \`admin\` | Server-to-server admin API (products, orders, customers) |
184
+ | \`storefront-bot\` | **AI chat widget** \u2014 add Brainerce's AI shopping assistant to any page with one script tag or \`BrainerceBot.mount()\`. Answers product questions, shows recommendation cards, handles cart adds, opens escalation form. |
185
+ | \`content\` | Blog, articles, static pages |`;
167
186
  }
168
187
  function getCheckoutFlowSection(_currency) {
169
188
  return `## \u26A0\uFE0F CHECKOUT FLOW (CRITICAL \u2014 READ CAREFULLY!)
@@ -1885,8 +1904,8 @@ function getTaxDisplaySection(_currency) {
1885
1904
  | Product Page | \u274C No | "Price excludes tax" (optional note) |
1886
1905
  | Cart Page | \u26A0\uFE0F "Calculated at checkout" | No address yet |
1887
1906
  | Checkout (before address) | \u26A0\uFE0F "Enter address to calculate" | Waiting for location |
1888
- | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` |
1889
- | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` |
1907
+ | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` (0 in VAT mode \u2192 \`checkout.taxBreakdown.totalTax\`) |
1908
+ | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` (0 in VAT mode \u2192 \`order.taxBreakdown.totalTax\`) |
1890
1909
 
1891
1910
  ### Cart Page \u2014 Partial Display
1892
1911
 
@@ -1938,7 +1957,13 @@ const totals = getCartTotals(cart);
1938
1957
  <div className="flex justify-between text-green-600"><span>Discount</span><span>-{formatPrice(order.discountAmount, { currency: order.currency })}</span></div>
1939
1958
  )}
1940
1959
  <div className="flex justify-between"><span>Shipping</span><span>{formatPrice(order.shippingAmount, { currency: order.currency })}</span></div>
1941
- <div className="flex justify-between"><span>Tax</span><span>{formatPrice(order.taxAmount, { currency: order.currency })}</span></div>
1960
+ {/* Inclusive (VAT) stores keep taxAmount="0" \u2014 the real VAT is on taxBreakdown.totalTax. */}
1961
+ {(() => {
1962
+ const tax = parseFloat(order.taxAmount || '0') || order.taxBreakdown?.totalTax || 0;
1963
+ if (tax <= 0) return null;
1964
+ const incl = order.taxBreakdown?.pricesIncludeTax;
1965
+ return <div className="flex justify-between"><span>{incl ? 'Tax (incl.)' : 'Tax'}</span><span>{formatPrice(tax, { currency: order.currency })}</span></div>;
1966
+ })()}
1942
1967
  <div className="flex justify-between font-bold text-lg border-t pt-2"><span>Total Paid</span><span>{formatPrice(order.totalAmount, { currency: order.currency })}</span></div>
1943
1968
  </div>
1944
1969
  \`\`\``;
@@ -2067,7 +2092,7 @@ A useful order card includes ALL of the following when the data is present. Each
2067
2092
  | **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
2068
2093
  | **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
2069
2094
  | Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
2070
- | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. |
2095
+ | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
2071
2096
 
2072
2097
  #### Rendering \`order.items[i].customizations\` by type
2073
2098
 
@@ -2512,8 +2537,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2512
2537
  await admin.mergeTaxClasses(food.id, standardClassId);
2513
2538
  \`\`\`
2514
2539
 
2515
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2516
- the store's classes (storefront-safe fields only) for a transparency badge.
2540
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2541
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2542
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2517
2543
 
2518
2544
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2519
2545
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2535,8 +2561,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2535
2561
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2536
2562
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2537
2563
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2538
- checkout with a region (recorded for reporting + provider scoping; currency
2539
- follows the cart until FX price conversion lands).
2564
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2565
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2566
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2567
+ otherwise the checkout is charged in the store base currency.
2540
2568
 
2541
2569
  \`\`\`typescript
2542
2570
  const eu = await admin.createRegion({
@@ -2549,9 +2577,12 @@ const { data: regions } = await admin.getRegions();
2549
2577
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2550
2578
  \`\`\`
2551
2579
 
2552
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2553
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2554
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2580
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2581
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2582
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2583
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2584
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2585
+ currency display.
2555
2586
 
2556
2587
  \`\`\`typescript
2557
2588
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3175,6 +3206,38 @@ Rules:
3175
3206
  count updates when the bot adds items.
3176
3207
  `;
3177
3208
  }
3209
+ function getStorefrontAnalyticsSection(connectionId) {
3210
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3211
+
3212
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3213
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3214
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3215
+ rate: visits \u2192 orders).
3216
+
3217
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3218
+
3219
+ \`\`\`html
3220
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3221
+ \`\`\`
3222
+
3223
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3224
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3225
+
3226
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3227
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3228
+ server-side then discarded. No cookie-consent banner is required for it under
3229
+ GDPR/ePrivacy.
3230
+
3231
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3232
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3233
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3234
+
3235
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3236
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3237
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3238
+ this month and from where?").
3239
+ `;
3240
+ }
3178
3241
  function getSectionByTopic(topic, connectionId, currency) {
3179
3242
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3180
3243
  const cur = currency || "USD";
@@ -3231,6 +3294,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3231
3294
  return getBlogSection();
3232
3295
  case "storefront-bot":
3233
3296
  return getStorefrontBotSection(cid);
3297
+ case "analytics":
3298
+ case "traffic-analytics":
3299
+ return getStorefrontAnalyticsSection(cid);
3234
3300
  case "all":
3235
3301
  return [
3236
3302
  "# Brainerce SDK \u2014 full topic dump",
@@ -3299,6 +3365,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3299
3365
  "",
3300
3366
  "---",
3301
3367
  "",
3368
+ getStorefrontAnalyticsSection(cid),
3369
+ "",
3370
+ "---",
3371
+ "",
3302
3372
  getProductCustomizationFieldsSection(),
3303
3373
  "",
3304
3374
  "---",
@@ -3368,6 +3438,7 @@ var GET_SDK_DOCS_SCHEMA = {
3368
3438
  "inquiries",
3369
3439
  "content",
3370
3440
  "storefront-bot",
3441
+ "analytics",
3371
3442
  "all"
3372
3443
  ]).describe("The SDK documentation topic to retrieve"),
3373
3444
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3405,7 +3476,7 @@ interface Product {
3405
3476
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3406
3477
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3407
3478
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3408
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3479
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3409
3480
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3410
3481
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3411
3482
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3659,7 +3730,7 @@ interface Checkout {
3659
3730
  status: CheckoutStatus;
3660
3731
  email?: string | null;
3661
3732
  customerId?: string | null;
3662
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3733
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3663
3734
  shippingAddress?: CheckoutAddress | null;
3664
3735
  billingAddress?: CheckoutAddress | null;
3665
3736
  shippingRateId?: string | null;
@@ -3679,6 +3750,18 @@ interface Checkout {
3679
3750
  itemCount: number;
3680
3751
  availableShippingRates?: ShippingRate[];
3681
3752
  reservation?: ReservationInfo;
3753
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3754
+ presentment?: {
3755
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3756
+ subtotal: string;
3757
+ discountAmount: string;
3758
+ shippingAmount: string;
3759
+ taxAmount: string;
3760
+ surchargeAmount: string;
3761
+ total: string; // equals the charged amount, to the cent
3762
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3763
+ fxBufferPercent: string | null;
3764
+ };
3682
3765
  createdAt: string;
3683
3766
  updatedAt: string;
3684
3767
  }
@@ -3770,6 +3853,8 @@ interface Order {
3770
3853
  totalAmount: string; // ALWAYS use this, not .total
3771
3854
  total?: string; // optional alias
3772
3855
  currency?: string;
3856
+ taxAmount?: string | null; // 0 in inclusive (VAT) mode \u2014 read taxBreakdown.totalTax
3857
+ taxBreakdown?: TaxBreakdown | null; // carries the included VAT when prices include tax
3773
3858
  customer?: OrderCustomer | null;
3774
3859
  items: OrderItem[];
3775
3860
  itemCount?: number;
@@ -4054,10 +4139,22 @@ interface CartAppliedDiscount {
4054
4139
  }
4055
4140
 
4056
4141
  // Recommendation types
4142
+ interface RecommendationVariant {
4143
+ id: string; name?: string; price?: string; salePrice?: string;
4144
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4145
+ }
4057
4146
  interface ProductRecommendation {
4058
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4147
+ id: string; name: string; slug: string;
4148
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4149
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4150
+ basePrice: string; salePrice?: string;
4059
4151
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4060
4152
  relationType: string;
4153
+ // Variant context (VARIABLE targets):
4154
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4155
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4156
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4157
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4061
4158
  }
4062
4159
  interface ProductRecommendationsResponse {
4063
4160
  crossSells: ProductRecommendation[];
@@ -4091,6 +4188,12 @@ interface CartBundleOfferOfferedProduct {
4091
4188
  salePrice: string | null;
4092
4189
  images: Array<{ url: string }>;
4093
4190
  type: string;
4191
+ // Variant context (VARIABLE offered products):
4192
+ variantId?: string | null; // merchant-pinned variant for this slot
4193
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4194
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4195
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4196
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4094
4197
  originalPrice: string;
4095
4198
  discountedPrice: string;
4096
4199
  }
@@ -4574,10 +4677,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4574
4677
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4575
4678
  // \u2192 region whose countries includes the code, else the default region, else null.
4576
4679
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4577
- // region (recorded for reporting + provider scoping; currency follows the cart
4578
- // until FX price conversion lands).
4680
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4681
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4682
+ // response carries a presentment overlay; else charged in store base currency.
4579
4683
  //
4580
- // Storefront (public, no apiKey \u2014 storeId mode):
4684
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4685
+ // the vc routes are gated on products:read, which every connection already has):
4581
4686
  interface PublicRegion {
4582
4687
  id: string; name: string; slug: string; currency: string;
4583
4688
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4629,7 +4734,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4629
4734
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4630
4735
  // mergeTaxClasses(id, targetId)
4631
4736
  //
4632
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4737
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4738
+ // the vc routes are gated on products:read, which every connection already has):
4633
4739
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4634
4740
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4635
4741
  //
package/dist/bin/stdio.js CHANGED
@@ -160,7 +160,26 @@ import {
160
160
  getDescriptionContent, isHtmlDescription,
161
161
  getProductMetafieldValue, getProductCustomizationFields,
162
162
  } from 'brainerce';
163
- \`\`\``;
163
+ \`\`\`
164
+
165
+ ### Available features \u2014 request each topic for full docs
166
+
167
+ | Topic | What it covers |
168
+ |-------|----------------|
169
+ | \`products\` | Catalog listing, search, PDP, variants, swatches, stock, attributes |
170
+ | \`cart\` | Guest + logged-in cart, add/remove/update, reservations |
171
+ | \`checkout\` | Guest + customer checkout, shipping, address |
172
+ | \`payment\` | Payment providers, intents, status polling, confirmation |
173
+ | \`auth\` | Customer sign-up, login, OAuth, email verification |
174
+ | \`discounts\` | Coupons, banners, nudges |
175
+ | \`recommendations\` | Cross-sell / upsell grids |
176
+ | \`reviews\` | Product reviews & ratings |
177
+ | \`inventory\` | Stock badges, reservation countdown |
178
+ | \`inquiries\` | Contact / custom forms |
179
+ | \`i18n\` | Multi-language, RTL |
180
+ | \`admin\` | Server-to-server admin API (products, orders, customers) |
181
+ | \`storefront-bot\` | **AI chat widget** \u2014 add Brainerce's AI shopping assistant to any page with one script tag or \`BrainerceBot.mount()\`. Answers product questions, shows recommendation cards, handles cart adds, opens escalation form. |
182
+ | \`content\` | Blog, articles, static pages |`;
164
183
  }
165
184
  function getCheckoutFlowSection(_currency) {
166
185
  return `## \u26A0\uFE0F CHECKOUT FLOW (CRITICAL \u2014 READ CAREFULLY!)
@@ -1882,8 +1901,8 @@ function getTaxDisplaySection(_currency) {
1882
1901
  | Product Page | \u274C No | "Price excludes tax" (optional note) |
1883
1902
  | Cart Page | \u26A0\uFE0F "Calculated at checkout" | No address yet |
1884
1903
  | Checkout (before address) | \u26A0\uFE0F "Enter address to calculate" | Waiting for location |
1885
- | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` |
1886
- | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` |
1904
+ | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` (0 in VAT mode \u2192 \`checkout.taxBreakdown.totalTax\`) |
1905
+ | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` (0 in VAT mode \u2192 \`order.taxBreakdown.totalTax\`) |
1887
1906
 
1888
1907
  ### Cart Page \u2014 Partial Display
1889
1908
 
@@ -1935,7 +1954,13 @@ const totals = getCartTotals(cart);
1935
1954
  <div className="flex justify-between text-green-600"><span>Discount</span><span>-{formatPrice(order.discountAmount, { currency: order.currency })}</span></div>
1936
1955
  )}
1937
1956
  <div className="flex justify-between"><span>Shipping</span><span>{formatPrice(order.shippingAmount, { currency: order.currency })}</span></div>
1938
- <div className="flex justify-between"><span>Tax</span><span>{formatPrice(order.taxAmount, { currency: order.currency })}</span></div>
1957
+ {/* Inclusive (VAT) stores keep taxAmount="0" \u2014 the real VAT is on taxBreakdown.totalTax. */}
1958
+ {(() => {
1959
+ const tax = parseFloat(order.taxAmount || '0') || order.taxBreakdown?.totalTax || 0;
1960
+ if (tax <= 0) return null;
1961
+ const incl = order.taxBreakdown?.pricesIncludeTax;
1962
+ return <div className="flex justify-between"><span>{incl ? 'Tax (incl.)' : 'Tax'}</span><span>{formatPrice(tax, { currency: order.currency })}</span></div>;
1963
+ })()}
1939
1964
  <div className="flex justify-between font-bold text-lg border-t pt-2"><span>Total Paid</span><span>{formatPrice(order.totalAmount, { currency: order.currency })}</span></div>
1940
1965
  </div>
1941
1966
  \`\`\``;
@@ -2064,7 +2089,7 @@ A useful order card includes ALL of the following when the data is present. Each
2064
2089
  | **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
2065
2090
  | **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
2066
2091
  | Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
2067
- | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. |
2092
+ | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
2068
2093
 
2069
2094
  #### Rendering \`order.items[i].customizations\` by type
2070
2095
 
@@ -2509,8 +2534,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2509
2534
  await admin.mergeTaxClasses(food.id, standardClassId);
2510
2535
  \`\`\`
2511
2536
 
2512
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2513
- the store's classes (storefront-safe fields only) for a transparency badge.
2537
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2538
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2539
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2514
2540
 
2515
2541
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2516
2542
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2532,8 +2558,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2532
2558
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2533
2559
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2534
2560
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2535
- checkout with a region (recorded for reporting + provider scoping; currency
2536
- follows the cart until FX price conversion lands).
2561
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2562
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2563
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2564
+ otherwise the checkout is charged in the store base currency.
2537
2565
 
2538
2566
  \`\`\`typescript
2539
2567
  const eu = await admin.createRegion({
@@ -2546,9 +2574,12 @@ const { data: regions } = await admin.getRegions();
2546
2574
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2547
2575
  \`\`\`
2548
2576
 
2549
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2550
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2551
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2577
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2578
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2579
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2580
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2581
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2582
+ currency display.
2552
2583
 
2553
2584
  \`\`\`typescript
2554
2585
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3172,6 +3203,38 @@ Rules:
3172
3203
  count updates when the bot adds items.
3173
3204
  `;
3174
3205
  }
3206
+ function getStorefrontAnalyticsSection(connectionId) {
3207
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3208
+
3209
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3210
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3211
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3212
+ rate: visits \u2192 orders).
3213
+
3214
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3215
+
3216
+ \`\`\`html
3217
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3218
+ \`\`\`
3219
+
3220
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3221
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3222
+
3223
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3224
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3225
+ server-side then discarded. No cookie-consent banner is required for it under
3226
+ GDPR/ePrivacy.
3227
+
3228
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3229
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3230
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3231
+
3232
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3233
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3234
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3235
+ this month and from where?").
3236
+ `;
3237
+ }
3175
3238
  function getSectionByTopic(topic, connectionId, currency) {
3176
3239
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3177
3240
  const cur = currency || "USD";
@@ -3228,6 +3291,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3228
3291
  return getBlogSection();
3229
3292
  case "storefront-bot":
3230
3293
  return getStorefrontBotSection(cid);
3294
+ case "analytics":
3295
+ case "traffic-analytics":
3296
+ return getStorefrontAnalyticsSection(cid);
3231
3297
  case "all":
3232
3298
  return [
3233
3299
  "# Brainerce SDK \u2014 full topic dump",
@@ -3296,6 +3362,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3296
3362
  "",
3297
3363
  "---",
3298
3364
  "",
3365
+ getStorefrontAnalyticsSection(cid),
3366
+ "",
3367
+ "---",
3368
+ "",
3299
3369
  getProductCustomizationFieldsSection(),
3300
3370
  "",
3301
3371
  "---",
@@ -3365,6 +3435,7 @@ var GET_SDK_DOCS_SCHEMA = {
3365
3435
  "inquiries",
3366
3436
  "content",
3367
3437
  "storefront-bot",
3438
+ "analytics",
3368
3439
  "all"
3369
3440
  ]).describe("The SDK documentation topic to retrieve"),
3370
3441
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3402,7 +3473,7 @@ interface Product {
3402
3473
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3403
3474
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3404
3475
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3405
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3476
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3406
3477
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3407
3478
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3408
3479
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3656,7 +3727,7 @@ interface Checkout {
3656
3727
  status: CheckoutStatus;
3657
3728
  email?: string | null;
3658
3729
  customerId?: string | null;
3659
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3730
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3660
3731
  shippingAddress?: CheckoutAddress | null;
3661
3732
  billingAddress?: CheckoutAddress | null;
3662
3733
  shippingRateId?: string | null;
@@ -3676,6 +3747,18 @@ interface Checkout {
3676
3747
  itemCount: number;
3677
3748
  availableShippingRates?: ShippingRate[];
3678
3749
  reservation?: ReservationInfo;
3750
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3751
+ presentment?: {
3752
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3753
+ subtotal: string;
3754
+ discountAmount: string;
3755
+ shippingAmount: string;
3756
+ taxAmount: string;
3757
+ surchargeAmount: string;
3758
+ total: string; // equals the charged amount, to the cent
3759
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3760
+ fxBufferPercent: string | null;
3761
+ };
3679
3762
  createdAt: string;
3680
3763
  updatedAt: string;
3681
3764
  }
@@ -3767,6 +3850,8 @@ interface Order {
3767
3850
  totalAmount: string; // ALWAYS use this, not .total
3768
3851
  total?: string; // optional alias
3769
3852
  currency?: string;
3853
+ taxAmount?: string | null; // 0 in inclusive (VAT) mode \u2014 read taxBreakdown.totalTax
3854
+ taxBreakdown?: TaxBreakdown | null; // carries the included VAT when prices include tax
3770
3855
  customer?: OrderCustomer | null;
3771
3856
  items: OrderItem[];
3772
3857
  itemCount?: number;
@@ -4051,10 +4136,22 @@ interface CartAppliedDiscount {
4051
4136
  }
4052
4137
 
4053
4138
  // Recommendation types
4139
+ interface RecommendationVariant {
4140
+ id: string; name?: string; price?: string; salePrice?: string;
4141
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4142
+ }
4054
4143
  interface ProductRecommendation {
4055
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4144
+ id: string; name: string; slug: string;
4145
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4146
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4147
+ basePrice: string; salePrice?: string;
4056
4148
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4057
4149
  relationType: string;
4150
+ // Variant context (VARIABLE targets):
4151
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4152
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4153
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4154
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4058
4155
  }
4059
4156
  interface ProductRecommendationsResponse {
4060
4157
  crossSells: ProductRecommendation[];
@@ -4088,6 +4185,12 @@ interface CartBundleOfferOfferedProduct {
4088
4185
  salePrice: string | null;
4089
4186
  images: Array<{ url: string }>;
4090
4187
  type: string;
4188
+ // Variant context (VARIABLE offered products):
4189
+ variantId?: string | null; // merchant-pinned variant for this slot
4190
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4191
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4192
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4193
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4091
4194
  originalPrice: string;
4092
4195
  discountedPrice: string;
4093
4196
  }
@@ -4571,10 +4674,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4571
4674
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4572
4675
  // \u2192 region whose countries includes the code, else the default region, else null.
4573
4676
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4574
- // region (recorded for reporting + provider scoping; currency follows the cart
4575
- // until FX price conversion lands).
4677
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4678
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4679
+ // response carries a presentment overlay; else charged in store base currency.
4576
4680
  //
4577
- // Storefront (public, no apiKey \u2014 storeId mode):
4681
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4682
+ // the vc routes are gated on products:read, which every connection already has):
4578
4683
  interface PublicRegion {
4579
4684
  id: string; name: string; slug: string; currency: string;
4580
4685
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4626,7 +4731,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4626
4731
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4627
4732
  // mergeTaxClasses(id, targetId)
4628
4733
  //
4629
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4734
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4735
+ // the vc routes are gated on products:read, which every connection already has):
4630
4736
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4631
4737
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4632
4738
  //
package/dist/index.js CHANGED
@@ -186,7 +186,26 @@ import {
186
186
  getDescriptionContent, isHtmlDescription,
187
187
  getProductMetafieldValue, getProductCustomizationFields,
188
188
  } from 'brainerce';
189
- \`\`\``;
189
+ \`\`\`
190
+
191
+ ### Available features \u2014 request each topic for full docs
192
+
193
+ | Topic | What it covers |
194
+ |-------|----------------|
195
+ | \`products\` | Catalog listing, search, PDP, variants, swatches, stock, attributes |
196
+ | \`cart\` | Guest + logged-in cart, add/remove/update, reservations |
197
+ | \`checkout\` | Guest + customer checkout, shipping, address |
198
+ | \`payment\` | Payment providers, intents, status polling, confirmation |
199
+ | \`auth\` | Customer sign-up, login, OAuth, email verification |
200
+ | \`discounts\` | Coupons, banners, nudges |
201
+ | \`recommendations\` | Cross-sell / upsell grids |
202
+ | \`reviews\` | Product reviews & ratings |
203
+ | \`inventory\` | Stock badges, reservation countdown |
204
+ | \`inquiries\` | Contact / custom forms |
205
+ | \`i18n\` | Multi-language, RTL |
206
+ | \`admin\` | Server-to-server admin API (products, orders, customers) |
207
+ | \`storefront-bot\` | **AI chat widget** \u2014 add Brainerce's AI shopping assistant to any page with one script tag or \`BrainerceBot.mount()\`. Answers product questions, shows recommendation cards, handles cart adds, opens escalation form. |
208
+ | \`content\` | Blog, articles, static pages |`;
190
209
  }
191
210
  function getCheckoutFlowSection(_currency) {
192
211
  return `## \u26A0\uFE0F CHECKOUT FLOW (CRITICAL \u2014 READ CAREFULLY!)
@@ -1908,8 +1927,8 @@ function getTaxDisplaySection(_currency) {
1908
1927
  | Product Page | \u274C No | "Price excludes tax" (optional note) |
1909
1928
  | Cart Page | \u26A0\uFE0F "Calculated at checkout" | No address yet |
1910
1929
  | Checkout (before address) | \u26A0\uFE0F "Enter address to calculate" | Waiting for location |
1911
- | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` |
1912
- | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` |
1930
+ | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` (0 in VAT mode \u2192 \`checkout.taxBreakdown.totalTax\`) |
1931
+ | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` (0 in VAT mode \u2192 \`order.taxBreakdown.totalTax\`) |
1913
1932
 
1914
1933
  ### Cart Page \u2014 Partial Display
1915
1934
 
@@ -1961,7 +1980,13 @@ const totals = getCartTotals(cart);
1961
1980
  <div className="flex justify-between text-green-600"><span>Discount</span><span>-{formatPrice(order.discountAmount, { currency: order.currency })}</span></div>
1962
1981
  )}
1963
1982
  <div className="flex justify-between"><span>Shipping</span><span>{formatPrice(order.shippingAmount, { currency: order.currency })}</span></div>
1964
- <div className="flex justify-between"><span>Tax</span><span>{formatPrice(order.taxAmount, { currency: order.currency })}</span></div>
1983
+ {/* Inclusive (VAT) stores keep taxAmount="0" \u2014 the real VAT is on taxBreakdown.totalTax. */}
1984
+ {(() => {
1985
+ const tax = parseFloat(order.taxAmount || '0') || order.taxBreakdown?.totalTax || 0;
1986
+ if (tax <= 0) return null;
1987
+ const incl = order.taxBreakdown?.pricesIncludeTax;
1988
+ return <div className="flex justify-between"><span>{incl ? 'Tax (incl.)' : 'Tax'}</span><span>{formatPrice(tax, { currency: order.currency })}</span></div>;
1989
+ })()}
1965
1990
  <div className="flex justify-between font-bold text-lg border-t pt-2"><span>Total Paid</span><span>{formatPrice(order.totalAmount, { currency: order.currency })}</span></div>
1966
1991
  </div>
1967
1992
  \`\`\``;
@@ -2090,7 +2115,7 @@ A useful order card includes ALL of the following when the data is present. Each
2090
2115
  | **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
2091
2116
  | **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
2092
2117
  | Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
2093
- | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. |
2118
+ | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
2094
2119
 
2095
2120
  #### Rendering \`order.items[i].customizations\` by type
2096
2121
 
@@ -2535,8 +2560,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2535
2560
  await admin.mergeTaxClasses(food.id, standardClassId);
2536
2561
  \`\`\`
2537
2562
 
2538
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2539
- the store's classes (storefront-safe fields only) for a transparency badge.
2563
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2564
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2565
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2540
2566
 
2541
2567
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2542
2568
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2558,8 +2584,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2558
2584
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2559
2585
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2560
2586
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2561
- checkout with a region (recorded for reporting + provider scoping; currency
2562
- follows the cart until FX price conversion lands).
2587
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2588
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2589
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2590
+ otherwise the checkout is charged in the store base currency.
2563
2591
 
2564
2592
  \`\`\`typescript
2565
2593
  const eu = await admin.createRegion({
@@ -2572,9 +2600,12 @@ const { data: regions } = await admin.getRegions();
2572
2600
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2573
2601
  \`\`\`
2574
2602
 
2575
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2576
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2577
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2603
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2604
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2605
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2606
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2607
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2608
+ currency display.
2578
2609
 
2579
2610
  \`\`\`typescript
2580
2611
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3198,6 +3229,38 @@ Rules:
3198
3229
  count updates when the bot adds items.
3199
3230
  `;
3200
3231
  }
3232
+ function getStorefrontAnalyticsSection(connectionId) {
3233
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3234
+
3235
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3236
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3237
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3238
+ rate: visits \u2192 orders).
3239
+
3240
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3241
+
3242
+ \`\`\`html
3243
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3244
+ \`\`\`
3245
+
3246
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3247
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3248
+
3249
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3250
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3251
+ server-side then discarded. No cookie-consent banner is required for it under
3252
+ GDPR/ePrivacy.
3253
+
3254
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3255
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3256
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3257
+
3258
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3259
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3260
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3261
+ this month and from where?").
3262
+ `;
3263
+ }
3201
3264
  function getSectionByTopic(topic, connectionId, currency) {
3202
3265
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3203
3266
  const cur = currency || "USD";
@@ -3254,6 +3317,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3254
3317
  return getBlogSection();
3255
3318
  case "storefront-bot":
3256
3319
  return getStorefrontBotSection(cid);
3320
+ case "analytics":
3321
+ case "traffic-analytics":
3322
+ return getStorefrontAnalyticsSection(cid);
3257
3323
  case "all":
3258
3324
  return [
3259
3325
  "# Brainerce SDK \u2014 full topic dump",
@@ -3322,6 +3388,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3322
3388
  "",
3323
3389
  "---",
3324
3390
  "",
3391
+ getStorefrontAnalyticsSection(cid),
3392
+ "",
3393
+ "---",
3394
+ "",
3325
3395
  getProductCustomizationFieldsSection(),
3326
3396
  "",
3327
3397
  "---",
@@ -3391,6 +3461,7 @@ var GET_SDK_DOCS_SCHEMA = {
3391
3461
  "inquiries",
3392
3462
  "content",
3393
3463
  "storefront-bot",
3464
+ "analytics",
3394
3465
  "all"
3395
3466
  ]).describe("The SDK documentation topic to retrieve"),
3396
3467
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3428,7 +3499,7 @@ interface Product {
3428
3499
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3429
3500
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3430
3501
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3431
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3502
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3432
3503
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3433
3504
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3434
3505
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3682,7 +3753,7 @@ interface Checkout {
3682
3753
  status: CheckoutStatus;
3683
3754
  email?: string | null;
3684
3755
  customerId?: string | null;
3685
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3756
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3686
3757
  shippingAddress?: CheckoutAddress | null;
3687
3758
  billingAddress?: CheckoutAddress | null;
3688
3759
  shippingRateId?: string | null;
@@ -3702,6 +3773,18 @@ interface Checkout {
3702
3773
  itemCount: number;
3703
3774
  availableShippingRates?: ShippingRate[];
3704
3775
  reservation?: ReservationInfo;
3776
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3777
+ presentment?: {
3778
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3779
+ subtotal: string;
3780
+ discountAmount: string;
3781
+ shippingAmount: string;
3782
+ taxAmount: string;
3783
+ surchargeAmount: string;
3784
+ total: string; // equals the charged amount, to the cent
3785
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3786
+ fxBufferPercent: string | null;
3787
+ };
3705
3788
  createdAt: string;
3706
3789
  updatedAt: string;
3707
3790
  }
@@ -3793,6 +3876,8 @@ interface Order {
3793
3876
  totalAmount: string; // ALWAYS use this, not .total
3794
3877
  total?: string; // optional alias
3795
3878
  currency?: string;
3879
+ taxAmount?: string | null; // 0 in inclusive (VAT) mode \u2014 read taxBreakdown.totalTax
3880
+ taxBreakdown?: TaxBreakdown | null; // carries the included VAT when prices include tax
3796
3881
  customer?: OrderCustomer | null;
3797
3882
  items: OrderItem[];
3798
3883
  itemCount?: number;
@@ -4077,10 +4162,22 @@ interface CartAppliedDiscount {
4077
4162
  }
4078
4163
 
4079
4164
  // Recommendation types
4165
+ interface RecommendationVariant {
4166
+ id: string; name?: string; price?: string; salePrice?: string;
4167
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4168
+ }
4080
4169
  interface ProductRecommendation {
4081
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4170
+ id: string; name: string; slug: string;
4171
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4172
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4173
+ basePrice: string; salePrice?: string;
4082
4174
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4083
4175
  relationType: string;
4176
+ // Variant context (VARIABLE targets):
4177
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4178
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4179
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4180
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4084
4181
  }
4085
4182
  interface ProductRecommendationsResponse {
4086
4183
  crossSells: ProductRecommendation[];
@@ -4114,6 +4211,12 @@ interface CartBundleOfferOfferedProduct {
4114
4211
  salePrice: string | null;
4115
4212
  images: Array<{ url: string }>;
4116
4213
  type: string;
4214
+ // Variant context (VARIABLE offered products):
4215
+ variantId?: string | null; // merchant-pinned variant for this slot
4216
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4217
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4218
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4219
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4117
4220
  originalPrice: string;
4118
4221
  discountedPrice: string;
4119
4222
  }
@@ -4597,10 +4700,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4597
4700
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4598
4701
  // \u2192 region whose countries includes the code, else the default region, else null.
4599
4702
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4600
- // region (recorded for reporting + provider scoping; currency follows the cart
4601
- // until FX price conversion lands).
4703
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4704
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4705
+ // response carries a presentment overlay; else charged in store base currency.
4602
4706
  //
4603
- // Storefront (public, no apiKey \u2014 storeId mode):
4707
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4708
+ // the vc routes are gated on products:read, which every connection already has):
4604
4709
  interface PublicRegion {
4605
4710
  id: string; name: string; slug: string; currency: string;
4606
4711
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4652,7 +4757,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4652
4757
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4653
4758
  // mergeTaxClasses(id, targetId)
4654
4759
  //
4655
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4760
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4761
+ // the vc routes are gated on products:read, which every connection already has):
4656
4762
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4657
4763
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4658
4764
  //
package/dist/index.mjs CHANGED
@@ -154,7 +154,26 @@ import {
154
154
  getDescriptionContent, isHtmlDescription,
155
155
  getProductMetafieldValue, getProductCustomizationFields,
156
156
  } from 'brainerce';
157
- \`\`\``;
157
+ \`\`\`
158
+
159
+ ### Available features \u2014 request each topic for full docs
160
+
161
+ | Topic | What it covers |
162
+ |-------|----------------|
163
+ | \`products\` | Catalog listing, search, PDP, variants, swatches, stock, attributes |
164
+ | \`cart\` | Guest + logged-in cart, add/remove/update, reservations |
165
+ | \`checkout\` | Guest + customer checkout, shipping, address |
166
+ | \`payment\` | Payment providers, intents, status polling, confirmation |
167
+ | \`auth\` | Customer sign-up, login, OAuth, email verification |
168
+ | \`discounts\` | Coupons, banners, nudges |
169
+ | \`recommendations\` | Cross-sell / upsell grids |
170
+ | \`reviews\` | Product reviews & ratings |
171
+ | \`inventory\` | Stock badges, reservation countdown |
172
+ | \`inquiries\` | Contact / custom forms |
173
+ | \`i18n\` | Multi-language, RTL |
174
+ | \`admin\` | Server-to-server admin API (products, orders, customers) |
175
+ | \`storefront-bot\` | **AI chat widget** \u2014 add Brainerce's AI shopping assistant to any page with one script tag or \`BrainerceBot.mount()\`. Answers product questions, shows recommendation cards, handles cart adds, opens escalation form. |
176
+ | \`content\` | Blog, articles, static pages |`;
158
177
  }
159
178
  function getCheckoutFlowSection(_currency) {
160
179
  return `## \u26A0\uFE0F CHECKOUT FLOW (CRITICAL \u2014 READ CAREFULLY!)
@@ -1876,8 +1895,8 @@ function getTaxDisplaySection(_currency) {
1876
1895
  | Product Page | \u274C No | "Price excludes tax" (optional note) |
1877
1896
  | Cart Page | \u26A0\uFE0F "Calculated at checkout" | No address yet |
1878
1897
  | Checkout (before address) | \u26A0\uFE0F "Enter address to calculate" | Waiting for location |
1879
- | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` |
1880
- | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` |
1898
+ | Checkout (after address) | \u2705 **REQUIRED** | \`checkout.taxAmount\` (0 in VAT mode \u2192 \`checkout.taxBreakdown.totalTax\`) |
1899
+ | Order Confirmation | \u2705 **REQUIRED** | \`order.taxAmount\` (0 in VAT mode \u2192 \`order.taxBreakdown.totalTax\`) |
1881
1900
 
1882
1901
  ### Cart Page \u2014 Partial Display
1883
1902
 
@@ -1929,7 +1948,13 @@ const totals = getCartTotals(cart);
1929
1948
  <div className="flex justify-between text-green-600"><span>Discount</span><span>-{formatPrice(order.discountAmount, { currency: order.currency })}</span></div>
1930
1949
  )}
1931
1950
  <div className="flex justify-between"><span>Shipping</span><span>{formatPrice(order.shippingAmount, { currency: order.currency })}</span></div>
1932
- <div className="flex justify-between"><span>Tax</span><span>{formatPrice(order.taxAmount, { currency: order.currency })}</span></div>
1951
+ {/* Inclusive (VAT) stores keep taxAmount="0" \u2014 the real VAT is on taxBreakdown.totalTax. */}
1952
+ {(() => {
1953
+ const tax = parseFloat(order.taxAmount || '0') || order.taxBreakdown?.totalTax || 0;
1954
+ if (tax <= 0) return null;
1955
+ const incl = order.taxBreakdown?.pricesIncludeTax;
1956
+ return <div className="flex justify-between"><span>{incl ? 'Tax (incl.)' : 'Tax'}</span><span>{formatPrice(tax, { currency: order.currency })}</span></div>;
1957
+ })()}
1933
1958
  <div className="flex justify-between font-bold text-lg border-t pt-2"><span>Total Paid</span><span>{formatPrice(order.totalAmount, { currency: order.currency })}</span></div>
1934
1959
  </div>
1935
1960
  \`\`\``;
@@ -2058,7 +2083,7 @@ A useful order card includes ALL of the following when the data is present. Each
2058
2083
  | **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
2059
2084
  | **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
2060
2085
  | Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
2061
- | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. |
2086
+ | Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
2062
2087
 
2063
2088
  #### Rendering \`order.items[i].customizations\` by type
2064
2089
 
@@ -2503,8 +2528,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2503
2528
  await admin.mergeTaxClasses(food.id, standardClassId);
2504
2529
  \`\`\`
2505
2530
 
2506
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2507
- the store's classes (storefront-safe fields only) for a transparency badge.
2531
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2532
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2533
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2508
2534
 
2509
2535
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2510
2536
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2526,8 +2552,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2526
2552
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2527
2553
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2528
2554
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2529
- checkout with a region (recorded for reporting + provider scoping; currency
2530
- follows the cart until FX price conversion lands).
2555
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2556
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2557
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2558
+ otherwise the checkout is charged in the store base currency.
2531
2559
 
2532
2560
  \`\`\`typescript
2533
2561
  const eu = await admin.createRegion({
@@ -2540,9 +2568,12 @@ const { data: regions } = await admin.getRegions();
2540
2568
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2541
2569
  \`\`\`
2542
2570
 
2543
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2544
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2545
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2571
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2572
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2573
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2574
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2575
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2576
+ currency display.
2546
2577
 
2547
2578
  \`\`\`typescript
2548
2579
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3166,6 +3197,38 @@ Rules:
3166
3197
  count updates when the bot adds items.
3167
3198
  `;
3168
3199
  }
3200
+ function getStorefrontAnalyticsSection(connectionId) {
3201
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3202
+
3203
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3204
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3205
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3206
+ rate: visits \u2192 orders).
3207
+
3208
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3209
+
3210
+ \`\`\`html
3211
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3212
+ \`\`\`
3213
+
3214
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3215
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3216
+
3217
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3218
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3219
+ server-side then discarded. No cookie-consent banner is required for it under
3220
+ GDPR/ePrivacy.
3221
+
3222
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3223
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3224
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3225
+
3226
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3227
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3228
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3229
+ this month and from where?").
3230
+ `;
3231
+ }
3169
3232
  function getSectionByTopic(topic, connectionId, currency) {
3170
3233
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3171
3234
  const cur = currency || "USD";
@@ -3222,6 +3285,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3222
3285
  return getBlogSection();
3223
3286
  case "storefront-bot":
3224
3287
  return getStorefrontBotSection(cid);
3288
+ case "analytics":
3289
+ case "traffic-analytics":
3290
+ return getStorefrontAnalyticsSection(cid);
3225
3291
  case "all":
3226
3292
  return [
3227
3293
  "# Brainerce SDK \u2014 full topic dump",
@@ -3290,6 +3356,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3290
3356
  "",
3291
3357
  "---",
3292
3358
  "",
3359
+ getStorefrontAnalyticsSection(cid),
3360
+ "",
3361
+ "---",
3362
+ "",
3293
3363
  getProductCustomizationFieldsSection(),
3294
3364
  "",
3295
3365
  "---",
@@ -3359,6 +3429,7 @@ var GET_SDK_DOCS_SCHEMA = {
3359
3429
  "inquiries",
3360
3430
  "content",
3361
3431
  "storefront-bot",
3432
+ "analytics",
3362
3433
  "all"
3363
3434
  ]).describe("The SDK documentation topic to retrieve"),
3364
3435
  salesChannelId: z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3396,7 +3467,7 @@ interface Product {
3396
3467
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3397
3468
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3398
3469
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3399
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3470
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3400
3471
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3401
3472
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3402
3473
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3650,7 +3721,7 @@ interface Checkout {
3650
3721
  status: CheckoutStatus;
3651
3722
  email?: string | null;
3652
3723
  customerId?: string | null;
3653
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3724
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3654
3725
  shippingAddress?: CheckoutAddress | null;
3655
3726
  billingAddress?: CheckoutAddress | null;
3656
3727
  shippingRateId?: string | null;
@@ -3670,6 +3741,18 @@ interface Checkout {
3670
3741
  itemCount: number;
3671
3742
  availableShippingRates?: ShippingRate[];
3672
3743
  reservation?: ReservationInfo;
3744
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3745
+ presentment?: {
3746
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3747
+ subtotal: string;
3748
+ discountAmount: string;
3749
+ shippingAmount: string;
3750
+ taxAmount: string;
3751
+ surchargeAmount: string;
3752
+ total: string; // equals the charged amount, to the cent
3753
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3754
+ fxBufferPercent: string | null;
3755
+ };
3673
3756
  createdAt: string;
3674
3757
  updatedAt: string;
3675
3758
  }
@@ -3761,6 +3844,8 @@ interface Order {
3761
3844
  totalAmount: string; // ALWAYS use this, not .total
3762
3845
  total?: string; // optional alias
3763
3846
  currency?: string;
3847
+ taxAmount?: string | null; // 0 in inclusive (VAT) mode \u2014 read taxBreakdown.totalTax
3848
+ taxBreakdown?: TaxBreakdown | null; // carries the included VAT when prices include tax
3764
3849
  customer?: OrderCustomer | null;
3765
3850
  items: OrderItem[];
3766
3851
  itemCount?: number;
@@ -4045,10 +4130,22 @@ interface CartAppliedDiscount {
4045
4130
  }
4046
4131
 
4047
4132
  // Recommendation types
4133
+ interface RecommendationVariant {
4134
+ id: string; name?: string; price?: string; salePrice?: string;
4135
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4136
+ }
4048
4137
  interface ProductRecommendation {
4049
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4138
+ id: string; name: string; slug: string;
4139
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4140
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4141
+ basePrice: string; salePrice?: string;
4050
4142
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4051
4143
  relationType: string;
4144
+ // Variant context (VARIABLE targets):
4145
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4146
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4147
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4148
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4052
4149
  }
4053
4150
  interface ProductRecommendationsResponse {
4054
4151
  crossSells: ProductRecommendation[];
@@ -4082,6 +4179,12 @@ interface CartBundleOfferOfferedProduct {
4082
4179
  salePrice: string | null;
4083
4180
  images: Array<{ url: string }>;
4084
4181
  type: string;
4182
+ // Variant context (VARIABLE offered products):
4183
+ variantId?: string | null; // merchant-pinned variant for this slot
4184
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4185
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4186
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4187
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4085
4188
  originalPrice: string;
4086
4189
  discountedPrice: string;
4087
4190
  }
@@ -4565,10 +4668,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4565
4668
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4566
4669
  // \u2192 region whose countries includes the code, else the default region, else null.
4567
4670
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4568
- // region (recorded for reporting + provider scoping; currency follows the cart
4569
- // until FX price conversion lands).
4671
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4672
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4673
+ // response carries a presentment overlay; else charged in store base currency.
4570
4674
  //
4571
- // Storefront (public, no apiKey \u2014 storeId mode):
4675
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4676
+ // the vc routes are gated on products:read, which every connection already has):
4572
4677
  interface PublicRegion {
4573
4678
  id: string; name: string; slug: string; currency: string;
4574
4679
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4620,7 +4725,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4620
4725
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4621
4726
  // mergeTaxClasses(id, targetId)
4622
4727
  //
4623
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4728
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4729
+ // the vc routes are gated on products:read, which every connection already has):
4624
4730
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4625
4731
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4626
4732
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainerce/mcp-server",
3
- "version": "3.11.0",
3
+ "version": "3.12.0",
4
4
  "description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
5
5
  "bin": {
6
6
  "brainerce-mcp": "dist/bin/stdio.js"