@brainerce/mcp-server 3.5.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -119,7 +119,7 @@ npm install brainerce
119
119
  import { BrainerceClient } from 'brainerce';
120
120
 
121
121
  export const client = new BrainerceClient({
122
- connectionId: '${connectionId}',
122
+ salesChannelId: '${connectionId}',
123
123
  });
124
124
 
125
125
  // Cart helpers \u2014 save cart ID to localStorage
@@ -221,7 +221,7 @@ async function startCheckout() {
221
221
  ### Full Checkout Flow (Multi-Provider)
222
222
 
223
223
  1. Customer fills cart
224
- 2. Customer optionally applies coupon (cart page)
224
+ 2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
225
225
  3. Detect payment providers \u2192 \`getPaymentProviders()\`
226
226
  4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
227
227
  5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
@@ -239,7 +239,7 @@ import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-
239
239
 
240
240
  function CheckoutPage() {
241
241
  const [paymentData, setPaymentData] = useState<{
242
- clientSecret: string; provider: string; checkoutId: string;
242
+ clientSecret: string; provider: string; checkoutId: string; clientSdk?: { renderType: string };
243
243
  } | null>(null);
244
244
  const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe> | null>(null);
245
245
  const [paypalClientId, setPaypalClientId] = useState<string | null>(null);
@@ -301,7 +301,7 @@ function CheckoutPage() {
301
301
  // saveCard: customerOptedIn, // optional opt-in
302
302
  });
303
303
 
304
- setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId });
304
+ setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId, clientSdk: paymentIntent.clientSdk });
305
305
 
306
306
  // Step 5: Initialize the correct payment provider
307
307
  if (paymentIntent.provider === 'stripe' && stripeProvider) {
@@ -911,6 +911,10 @@ function ProductPage({ product, storeInfo }: { product: Product; storeInfo: Stor
911
911
  ? getVariantPrice(selectedVariant, product.basePrice).toString()
912
912
  : product.salePrice || product.basePrice;
913
913
 
914
+ // \u2705 For catalog cards: use pre-computed priceMin/priceMax instead of iterating variants
915
+ // product.priceMin / product.priceMax are always set for VARIABLE products with explicit variant prices.
916
+ // product.priceVaries is true when the range should be shown as "\u20AA49 \u2013 \u20AA199".
917
+
914
918
  // Build attribute buttons (size, color, etc.)
915
919
  const allOptions = product.variants?.map(v => getVariantOptions(v)) || [];
916
920
  const attrNames = [...new Set(allOptions.flatMap(opts => opts.map(o => o.name)))];
@@ -1125,6 +1129,7 @@ const total = cart.total;
1125
1129
 
1126
1130
  ### Coupons
1127
1131
 
1132
+ **On the cart page** (before checkout is created):
1128
1133
  \`\`\`typescript
1129
1134
  // Apply coupon \u2014 returns updated Cart with discountAmount
1130
1135
  const updatedCart = await client.applyCoupon(cartId, 'SAVE20');
@@ -1132,13 +1137,24 @@ console.log(updatedCart.discountAmount); // "10.00"
1132
1137
  console.log(updatedCart.couponCode); // "SAVE20"
1133
1138
 
1134
1139
  // Remove coupon
1135
- const updatedCart = await client.removeCoupon(cartId);
1140
+ await client.removeCoupon(cartId);
1136
1141
 
1137
1142
  // Calculate totals including discount
1138
1143
  const totals = getCartTotals(cart); // { subtotal, discount, shipping, total }
1139
1144
  \`\`\`
1140
1145
 
1141
- Add a coupon code input on the cart page. Coupons only work on server \`Cart\` (has \`id\` field). Checkout inherits the coupon \u2014 show \`checkout.discountAmount\` and \`checkout.couponCode\` in checkout order summary.
1146
+ **On the checkout page** (after checkout session exists \u2014 ALWAYS use this when checkoutId is available):
1147
+ \`\`\`typescript
1148
+ // Applies to cart AND updates checkout totals in one call
1149
+ const checkout = await client.applyCheckoutCoupon(checkoutId, 'SAVE20');
1150
+ console.log(checkout.discountAmount); // "10.00"
1151
+ console.log(checkout.total); // correctly updated total
1152
+
1153
+ // Remove coupon from checkout
1154
+ await client.removeCheckoutCoupon(checkoutId);
1155
+ \`\`\`
1156
+
1157
+ > \u26A0\uFE0F **Critical:** if a checkout session already exists, ALWAYS use \`applyCheckoutCoupon(checkoutId, code)\`. Using \`applyCoupon(cartId, code)\` after checkout creation does NOT update the checkout total \u2014 payment will charge the original amount. Show \`checkout.discountAmount\` and \`checkout.couponCode\` in the order summary.
1142
1158
 
1143
1159
  ### \u26A0\uFE0F Checkout Order Summary \u2014 Use checkout.lineItems, NOT cart.items!
1144
1160
 
@@ -1958,7 +1974,7 @@ subsequent SDK call sends the \`Accept-Language\` header automatically:
1958
1974
  \`\`\`typescript
1959
1975
  import { BrainerceClient } from 'brainerce';
1960
1976
 
1961
- const client = new BrainerceClient({ connectionId: 'vc_...' });
1977
+ const client = new BrainerceClient({ salesChannelId: 'vc_...' });
1962
1978
  client.setLocale('he'); // done \u2014 all calls are now in Hebrew
1963
1979
  \`\`\`
1964
1980
 
@@ -2675,6 +2691,9 @@ interface Product {
2675
2691
  basePrice: string; // Use parseFloat() for calculations
2676
2692
  salePrice?: string | null;
2677
2693
  costPrice?: string | null;
2694
+ priceMin?: string | null; // Lowest variant price (VARIABLE products). Use for catalog/JSON-LD range display.
2695
+ priceMax?: string | null; // Highest variant price (VARIABLE products).
2696
+ priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
2678
2697
  status: string;
2679
2698
  type: 'SIMPLE' | 'VARIABLE';
2680
2699
  isDownloadable?: boolean;
@@ -3245,9 +3264,9 @@ var HELPERS_TYPES = `// ---- Helper Functions (import from 'brainerce') ----
3245
3264
  // Price helpers
3246
3265
  function formatPrice(priceString: string | number | undefined | null, options?: { currency?: string; locale?: string; }): string;
3247
3266
  function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice'>): number;
3248
- function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice'>): {
3267
+ function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'>): {
3249
3268
  price: number; originalPrice: number; isOnSale: boolean; discountAmount: number; discountPercent: number;
3250
- };
3269
+ }; // Falls back to priceMin when basePrice=0 (VARIABLE products)
3251
3270
  function getVariantPrice(variant: Pick<ProductVariant, 'price' | 'salePrice'>, productBasePrice: string): number;
3252
3271
 
3253
3272
  // Cart helpers
@@ -3749,7 +3768,7 @@ function selectVariant(product: Product, selections: Record<string, string>) {
3749
3768
  import { client } from './brainerce';
3750
3769
 
3751
3770
  // 1. Submit the address + email. Email is REQUIRED on the DTO.
3752
- const { shippingRates } = await client.setShippingAddress(checkoutId, {
3771
+ const { checkout, rates } = await client.setShippingAddress(checkoutId, {
3753
3772
  email,
3754
3773
  firstName,
3755
3774
  lastName,
@@ -3760,10 +3779,11 @@ const { shippingRates } = await client.setShippingAddress(checkoutId, {
3760
3779
  postalCode,
3761
3780
  country,
3762
3781
  });
3782
+ // rates = available shipping rates; checkout = updated checkout object
3763
3783
 
3764
3784
  // 2. Let the customer pick one of the returned rates.
3765
- const chosen = shippingRates[0]; // user selection
3766
- await client.setShippingMethod(checkoutId, chosen.id);
3785
+ const chosen = rates[0]; // user selection
3786
+ await client.selectShippingMethod(checkoutId, chosen.id);
3767
3787
 
3768
3788
  // 3. Re-fetch the checkout to get updated totals (tax may change after address).
3769
3789
  const checkout = await client.getCheckout(checkoutId);
@@ -3817,21 +3837,21 @@ if (providers.length === 0) {
3817
3837
  }
3818
3838
 
3819
3839
  const active = providers[0];`,
3820
- "checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js and @stripe/react-stripe-js
3821
- // (or the vanilla Stripe.js browser SDK).
3840
+ "checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js.
3822
3841
  import { loadStripe } from '@stripe/stripe-js';
3823
3842
  import { client } from './brainerce';
3824
3843
 
3825
3844
  const stripe = await loadStripe(stripePublicKey);
3826
3845
  if (!stripe) throw new Error('Stripe failed to load');
3827
3846
 
3828
- // The SDK returns a clientSecret via the provider's config after setPaymentMethod.
3829
- const { clientSecret } = await client.setPaymentMethod(checkoutId, {
3830
- providerId: stripeProvider.id,
3847
+ // Create a payment intent \u2014 returns clientSecret for Stripe Elements.
3848
+ const intent = await client.createPaymentIntent(checkoutId, {
3849
+ successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
3850
+ cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
3831
3851
  });
3832
3852
 
3833
- // Use Stripe Elements to collect the card, then confirm:
3834
- const result = await stripe.confirmCardPayment(clientSecret, {
3853
+ // Use Stripe Elements to confirm:
3854
+ const result = await stripe.confirmCardPayment(intent.clientSecret, {
3835
3855
  payment_method: { card: cardElement },
3836
3856
  });
3837
3857
 
@@ -3840,21 +3860,21 @@ if (result.error) {
3840
3860
  return;
3841
3861
  }
3842
3862
 
3843
- // Success \u2014 redirect to your confirmation page with checkoutId in the URL.
3863
+ // Success \u2014 redirect to confirmation page.
3844
3864
  // The confirmation page runs handlePaymentSuccess + waitForOrder.
3845
3865
  window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);`,
3846
3866
  "checkout-paypal-confirm": `// PayPal confirm flow. Use the PayPal JS SDK (render a PayPal button component).
3847
3867
  import { client } from './brainerce';
3848
3868
 
3849
- // When the PayPal button's onApprove fires with the PayPal order ID:
3850
- async function onPayPalApprove(paypalOrderId: string) {
3851
- await client.setPaymentMethod(checkoutId, {
3852
- providerId: paypalProvider.id,
3853
- providerData: { paypalOrderId },
3854
- });
3869
+ // Create a payment intent first \u2014 for PayPal this returns the PayPal order ID.
3870
+ const intent = await client.createPaymentIntent(checkoutId, {
3871
+ successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
3872
+ cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
3873
+ });
3855
3874
 
3856
- // PayPal confirmation \u2014 the SDK captures the order server-side.
3857
- // After this resolves, redirect to confirmation.
3875
+ // Render a PayPal button using intent.clientSdk (provider SDK config).
3876
+ // When the PayPal button's onApprove fires, redirect to confirmation:
3877
+ function onPayPalApprove() {
3858
3878
  window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);
3859
3879
  }`,
3860
3880
  "checkout-sandbox-confirm": `// Sandbox payment \u2014 store has sandboxPaymentsEnabled=true. No real charge.
@@ -3897,7 +3917,7 @@ async function registerAndMaybeVerify(input: {
3897
3917
  firstName: string;
3898
3918
  lastName: string;
3899
3919
  }) {
3900
- const result = await client.register(input);
3920
+ const result = await client.registerCustomer(input);
3901
3921
  if (result.requiresVerification) {
3902
3922
  // Route the user to your verify-email UI. Do NOT treat them as logged in.
3903
3923
  return { state: 'needs-verification' as const };
@@ -3918,7 +3938,7 @@ async function resendCode() {
3918
3938
  import { client } from './brainerce';
3919
3939
 
3920
3940
  async function login(email: string, password: string) {
3921
- const result = await client.login({ email, password });
3941
+ const result = await client.loginCustomer(email, password);
3922
3942
  if (result.requiresVerification) {
3923
3943
  return { state: 'needs-verification' as const };
3924
3944
  }
@@ -3953,25 +3973,37 @@ async function submitReset(token: string, newPassword: string) {
3953
3973
  "oauth-redirect-and-callback": `// OAuth sign-in. Redirect flow, NOT popup.
3954
3974
  import { client } from './brainerce';
3955
3975
 
3956
- // On your login / register UI:
3957
- const providers = await client.getAvailableOAuthProviders();
3958
- // Render one button per provider. On click, redirect the browser:
3959
- for (const p of providers) {
3960
- // button.onclick = () => window.location.assign(p.authorizationUrl)
3976
+ // Step 1: Get available provider names (strings: 'GOOGLE', 'FACEBOOK', 'GITHUB')
3977
+ const { providers } = await client.getAvailableOAuthProviders();
3978
+
3979
+ // Step 2: For each provider, fetch the authorization URL, then redirect.
3980
+ // Call this when the user clicks a provider button:
3981
+ async function redirectToOAuth(provider: string) {
3982
+ const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
3983
+ redirectUrl: \`\${window.location.origin}/auth/callback\`,
3984
+ });
3985
+ window.location.href = authorizationUrl; // full-page redirect, NOT a popup
3961
3986
  }
3962
3987
 
3963
- // On your OAuth callback route (SERVER-SIDE / BFF):
3964
- // 1. Read token + oauth_success (or oauth_error) from query params.
3965
- // 2. If oauth_error: redirect to login with the error message.
3966
- // 3. If oauth_success: exchange the token for a session cookie (your BFF),
3967
- // then redirect the browser to /account or wherever.
3968
- // Do NOT allow the token to land in browser history as a query param.`,
3988
+ // Step 3: On your /auth/callback route, read token from URL params:
3989
+ const params = new URLSearchParams(window.location.search);
3990
+ const oauthError = params.get('oauth_error');
3991
+ const token = params.get('token');
3992
+ if (oauthError) {
3993
+ window.location.href = '/login?error=' + encodeURIComponent(oauthError);
3994
+ } else if (token) {
3995
+ client.setCustomerToken(token);
3996
+ window.location.href = '/account';
3997
+ }`,
3969
3998
  "coupon-apply-and-remove": `// Coupon input. Build the UI even if no coupons exist today \u2014 it auto-hides.
3999
+ // IMPORTANT: use applyCheckoutCoupon when a checkoutId is available (checkout page).
4000
+ // Using applyCoupon after checkout creation does NOT update the checkout total.
3970
4001
  import { client } from './brainerce';
3971
4002
 
3972
- async function applyCoupon(code: string) {
4003
+ // On cart page (no checkout session yet)
4004
+ async function applyCouponToCart(cartId: string, code: string) {
3973
4005
  try {
3974
- const cart = await client.applyCoupon(code);
4006
+ const cart = await client.applyCoupon(cartId, code);
3975
4007
  return { ok: true, cart };
3976
4008
  } catch (err) {
3977
4009
  // Invalid / expired / minimum not met. Surface the specific error.
@@ -3979,17 +4011,30 @@ async function applyCoupon(code: string) {
3979
4011
  }
3980
4012
  }
3981
4013
 
3982
- async function removeCoupon() {
3983
- const cart = await client.removeCoupon();
3984
- return cart;
4014
+ async function removeCouponFromCart(cartId: string) {
4015
+ return client.removeCoupon(cartId);
4016
+ }
4017
+
4018
+ // On checkout page (checkout session already exists \u2014 always prefer this)
4019
+ async function applyCouponToCheckout(checkoutId: string, code: string) {
4020
+ try {
4021
+ const checkout = await client.applyCheckoutCoupon(checkoutId, code);
4022
+ return { ok: true, checkout }; // checkout.total is already updated
4023
+ } catch (err) {
4024
+ return { ok: false, error: (err as Error).message };
4025
+ }
4026
+ }
4027
+
4028
+ async function removeCouponFromCheckout(checkoutId: string) {
4029
+ return client.removeCheckoutCoupon(checkoutId);
3985
4030
  }`,
3986
4031
  "reservation-countdown": `// Reservation countdown. Use the SDK's expiry timestamp \u2014 do NOT invent
3987
4032
  // your own timer state.
3988
4033
  import { client } from './brainerce';
3989
4034
 
3990
- function getRemainingMs(cart: { reservationExpiresAt?: string | null }): number {
3991
- if (!cart.reservationExpiresAt) return 0;
3992
- const expiry = new Date(cart.reservationExpiresAt).getTime();
4035
+ function getRemainingMs(cart: { reservation?: { expiresAt?: string } }): number {
4036
+ if (!cart.reservation?.expiresAt) return 0;
4037
+ const expiry = new Date(cart.reservation.expiresAt).getTime();
3993
4038
  return Math.max(0, expiry - Date.now());
3994
4039
  }
3995
4040
 
@@ -4016,8 +4061,8 @@ function onSearchChange(query: string, onResults: (items: unknown[]) => void) {
4016
4061
  return;
4017
4062
  }
4018
4063
  searchTimer = setTimeout(async () => {
4019
- const results = await client.searchProducts(query);
4020
- onResults(results);
4064
+ const { products } = await client.getSearchSuggestions(query);
4065
+ onResults(products);
4021
4066
  }, 300);
4022
4067
  }`,
4023
4068
  "i18n-set-locale": `// i18n \u2014 only if get-store-capabilities reports i18n.enabled.
@@ -4374,19 +4419,13 @@ import { client } from './brainerce-client';
4374
4419
 
4375
4420
  interface CheckoutOptions {
4376
4421
  cartId: string;
4377
- shippingAddress: object;
4378
- shippingMethodId: string;
4379
- paymentProviderId: string;
4380
4422
  }
4381
4423
 
4382
4424
  async function createCheckoutSafe(opts: CheckoutOptions) {
4383
4425
  try {
4384
- return await client.createCheckout({
4385
- cartId: opts.cartId,
4386
- shippingAddress: opts.shippingAddress,
4387
- shippingMethodId: opts.shippingMethodId,
4388
- paymentProviderId: opts.paymentProviderId,
4389
- });
4426
+ // createCheckout only takes cartId (+ optional customerId, selectedItemIds).
4427
+ // Shipping address, shipping method, and payment are set via separate calls.
4428
+ return await client.createCheckout({ cartId: opts.cartId });
4390
4429
  } catch (err: unknown) {
4391
4430
  if (!isApiError(err, 'PRICE_DRIFT')) throw err;
4392
4431
 
@@ -5061,21 +5100,21 @@ var FLOWS = {
5061
5100
  1. **Collect address + email.** Build a form that captures customer email, billing address, shipping address (with \`line1\`, \`line2\`, \`city\`, \`region\`, \`postalCode\`, \`country\`). \`email\` is REQUIRED on \`SetShippingAddressDto\`.
5062
5101
  2. **Submit the address to get shipping rates.**
5063
5102
  \`\`\`ts
5064
- const { shippingRates } = await client.setShippingAddress(checkoutId, {
5103
+ const { checkout, rates } = await client.setShippingAddress(checkoutId, {
5065
5104
  email,
5066
5105
  firstName,
5067
5106
  lastName,
5068
5107
  line1, line2, city, region, postalCode, country,
5069
5108
  });
5070
5109
  \`\`\`
5071
- The response includes the available shipping rates for that address + the store's zones.
5110
+ \`rates\` = available shipping rates for that address + the store's zones. \`checkout\` = updated checkout object.
5072
5111
  3. **Let the customer pick a rate**, then persist the selection:
5073
5112
  \`\`\`ts
5074
- await client.setShippingMethod(checkoutId, rateId);
5113
+ await client.selectShippingMethod(checkoutId, rateId);
5075
5114
  \`\`\`
5076
- 4. **Fetch payment providers for this checkout:**
5115
+ 4. **Fetch payment providers:**
5077
5116
  \`\`\`ts
5078
- const providers = await client.getPaymentProviders(checkoutId);
5117
+ const providers = await client.getPaymentProviders();
5079
5118
  \`\`\`
5080
5119
  The response tells you which providers are configured (Stripe, Grow, PayPal, Sandbox) and how to render each. Each provider has a \`renderType\` telling you whether to show a Stripe Elements form, a redirect button, a PayPal button, or a sandbox "complete test order" button.
5081
5120
  5. **Confirm payment using the provider's recommended flow.** For Stripe: Stripe Elements \u2192 \`stripe.confirmCardPayment\` using the clientSecret returned by the SDK. For sandbox payments: call \`completeGuestCheckout(checkoutId)\` directly. For PayPal/Grow: follow the redirect and handle the return on your confirmation page.
@@ -5091,28 +5130,28 @@ Never bypass these steps. Never call \`submitGuestOrder\` / \`createOrder\` \u20
5091
5130
  "auth-register": {
5092
5131
  title: "Registration",
5093
5132
  body: `1. **Collect** email, password, first name, last name. Enforce strong passwords client-side: 8+ chars, upper, lower, number, special.
5094
- 2. **Call register:**
5133
+ 2. **Call registerCustomer:**
5095
5134
  \`\`\`ts
5096
- const result = await client.register({ email, password, firstName, lastName });
5135
+ const result = await client.registerCustomer({ email, password, firstName, lastName });
5097
5136
  \`\`\`
5098
5137
  3. **Branch on \`result.requiresVerification\`:**
5099
- - If \`true\`: route the user to your verify-email UI. Do NOT treat them as logged in yet.
5100
- - If \`false\`: the user is logged in. Store the token via your BFF session and route to the account area.
5138
+ - If \`true\`: store the token temporarily (e.g. sessionStorage), route the user to your verify-email UI. Do NOT treat them as logged in yet.
5139
+ - If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the account area.
5101
5140
  4. **On the verify-email step:** collect a 6-digit code and call \`client.verifyEmail(code)\`. Offer a "resend code" button wired to \`client.resendVerificationEmail()\`.
5102
- 5. **After verifyEmail resolves:** the user is now logged in. Persist the session via your BFF and route to the account area.
5141
+ 5. **After verifyEmail resolves:** call \`client.setCustomerToken(result.token)\` \u2014 the user is now logged in \u2014 then route to the account area.
5103
5142
 
5104
5143
  Build the verify-email step EVEN IF the store currently has verification disabled. It auto-hides; store owners enable it later.`
5105
5144
  },
5106
5145
  "auth-login": {
5107
5146
  title: "Login",
5108
5147
  body: `1. **Collect** email + password.
5109
- 2. **Call login:**
5148
+ 2. **Call loginCustomer:**
5110
5149
  \`\`\`ts
5111
- const result = await client.login({ email, password });
5150
+ const result = await client.loginCustomer(email, password);
5112
5151
  \`\`\`
5113
5152
  3. **Branch on \`result.requiresVerification\`:**
5114
5153
  - If \`true\`: route to verify-email. The user must complete verification before accessing account features.
5115
- - If \`false\`: persist the session via your BFF and route to the previous page (or account area).
5154
+ - If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the previous page (or account area).
5116
5155
  4. **Offer OAuth buttons** from \`client.getAvailableOAuthProviders()\`. Render a placeholder region even when no providers are returned \u2014 the region auto-hides today and shows buttons the moment a provider is enabled in the dashboard.
5117
5156
  5. **On error**, render the specific message (invalid credentials, rate limited, account disabled) \u2014 never swallow.`
5118
5157
  },
@@ -5137,15 +5176,24 @@ Build both steps EVEN IF the store has no email provider configured today \u2014
5137
5176
  },
5138
5177
  oauth: {
5139
5178
  title: "OAuth sign-in",
5140
- body: `1. **Render available providers:**
5179
+ body: `1. **Get available provider names:**
5180
+ \`\`\`ts
5181
+ const { providers } = await client.getAvailableOAuthProviders();
5182
+ // providers = ['GOOGLE', 'FACEBOOK', 'GITHUB'] (strings, not objects with authorizationUrl)
5183
+ \`\`\`
5184
+ 2. **For each provider, fetch the authorization URL:**
5185
+ \`\`\`ts
5186
+ const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
5187
+ redirectUrl: \`\${window.location.origin}/auth/callback\`,
5188
+ });
5189
+ window.location.href = authorizationUrl; // full-page redirect, NOT a popup
5190
+ \`\`\`
5191
+ 3. **On the callback page** the URL contains \`token\` + \`oauth_success\` (or \`oauth_error\`) query params. Extract and apply the token:
5141
5192
  \`\`\`ts
5142
- const providers = await client.getAvailableOAuthProviders();
5193
+ const token = new URLSearchParams(location.search).get('token');
5194
+ if (token) client.setCustomerToken(token); // then redirect to account
5143
5195
  \`\`\`
5144
- Each provider has \`{ provider: 'GOOGLE' | 'FACEBOOK' | 'GITHUB', authorizationUrl }\`.
5145
- 2. **Build a button for each** that redirects the browser to \`authorizationUrl\`. Do NOT open a popup; the full-page redirect is required.
5146
- 3. **On the provider's side** the user authenticates, and the provider redirects back to your OAuth callback URL with \`token\` + \`oauth_success\` (or \`oauth_error\`) query params.
5147
- 4. **The callback handler MUST run server-side (BFF).** Extract the token from the query params, exchange it for a session cookie via your BFF, then redirect the browser to the account area. Never let the token land in browser history as a query param or localStorage.
5148
- 5. **On \`oauth_error\`:** redirect to login with an error message.
5196
+ 4. **On \`oauth_error\`:** redirect to login with an error message.
5149
5197
 
5150
5198
  Build the OAuth button region AND the callback handler even when no providers are configured. They auto-hide.`
5151
5199
  },
@@ -5167,7 +5215,7 @@ Build the OAuth button region AND the callback handler even when no providers ar
5167
5215
 
5168
5216
  - **Cart ID persistence:** the SDK stores the cart ID across reloads. You do not need to write cart-to-localStorage code yourself.
5169
5217
  - **Reads:** \`client.getCart()\` returns the current cart. Call it on mount in your cart UI and on any page that shows a cart count (header).
5170
- - **Writes:** use \`client.addToCart\`, \`client.updateCartItem\`, \`client.removeCartItem\`, \`client.applyCoupon\`, \`client.removeCoupon\`. After each mutation the SDK returns the updated cart.
5218
+ - **Writes:** use \`client.addToCart\`, \`client.updateCartItem\`, \`client.removeCartItem\`, \`client.applyCoupon\`, \`client.removeCoupon\`. After each mutation the SDK returns the updated cart. On the **checkout page** use \`client.applyCheckoutCoupon(checkoutId, code)\` / \`client.removeCheckoutCoupon(checkoutId)\` \u2014 these update checkout totals atomically. Never use \`applyCoupon\` after a checkout session exists.
5171
5219
  - **Totals:** call \`getCartTotals(cart)\` \u2014 do NOT read \`cart.total\`. The helper understands taxes, shipping, and discounts.
5172
5220
  - **\`smartGetCart()\`** returns \`CartWithIncludes\` (extends \`Cart\`). All carts are server-side. Pass \`{ include: ['recommendations', 'upgrades', 'bundles'] }\` to fetch extras in one request.
5173
5221
  - **NEVER mutate cart state outside SDK helpers.** Any hand-rolled cart update risks desync with the reservation timer and the checkout flow.`
@@ -5299,7 +5347,7 @@ var FEATURES = [
5299
5347
  id: "browse-products",
5300
5348
  title: "Browse, filter, and search products",
5301
5349
  description: "Users can list products with pagination, apply category / price / attribute / custom-field filters, sort by name / price / newest, and run a search with autocomplete. Custom-field filters surface metafield definitions whose filterable=true (SELECT, MULTI_SELECT, BOOLEAN). Must handle empty states and loading states.",
5302
- sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.searchProducts(query)",
5350
+ sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.getSearchSuggestions(query)",
5303
5351
  mandatory: "mandatory"
5304
5352
  },
5305
5353
  {
@@ -5329,7 +5377,7 @@ var FEATURES = [
5329
5377
  id: "cart-coupons",
5330
5378
  title: "Apply and remove coupon codes",
5331
5379
  description: "Users can enter a coupon code, see it applied to the cart, see the discount amount, and remove it. Build the UI even if no coupons are configured today \u2014 it auto-hides.",
5332
- sdk: "client.applyCoupon(code), client.removeCoupon()",
5380
+ sdk: "client.applyCoupon(cartId, code), client.removeCoupon(cartId) \u2014 on cart page. client.applyCheckoutCoupon(checkoutId, code), client.removeCheckoutCoupon(checkoutId) \u2014 on checkout page (use when checkoutId exists)",
5333
5381
  mandatory: "mandatory",
5334
5382
  capabilityFlag: "hasCoupons",
5335
5383
  whenDisabledNote: "Store has no coupons configured today. Build the UI anyway \u2014 it auto-hides until the store owner creates a coupon."
@@ -5346,7 +5394,7 @@ var FEATURES = [
5346
5394
  id: "checkout",
5347
5395
  title: "Complete a full checkout end-to-end",
5348
5396
  description: "Users can enter their address, pick a shipping rate, pick a payment provider, pay, and land on a confirmation page showing the real order. Displays checkout.lineItems (not cart.items) on the summary.",
5349
- sdk: "client.setShippingAddress, client.setShippingMethod, client.getPaymentProviders, provider-specific confirm, client.handlePaymentSuccess, client.waitForOrder",
5397
+ sdk: "client.setShippingAddress (returns { checkout, rates }), client.selectShippingMethod, client.getPaymentProviders(), provider-specific confirm, client.handlePaymentSuccess, client.waitForOrder",
5350
5398
  flowRef: "checkout",
5351
5399
  mandatory: "mandatory"
5352
5400
  },
@@ -5362,7 +5410,7 @@ var FEATURES = [
5362
5410
  id: "register",
5363
5411
  title: "Register a new account with email verification",
5364
5412
  description: 'Users can create an account (email, password, first + last name). Handles the requiresVerification branch by routing to an email-verification step. Verification step collects a 6-digit code and has a "resend code" action.',
5365
- sdk: "client.register({email, password, firstName, lastName}), client.verifyEmail(code), client.resendVerificationEmail()",
5413
+ sdk: "client.registerCustomer({email, password, firstName, lastName}), client.verifyEmail(code), client.resendVerificationEmail()",
5366
5414
  flowRef: "auth-register",
5367
5415
  mandatory: "mandatory",
5368
5416
  capabilityFlag: "oauth",
@@ -5372,7 +5420,7 @@ var FEATURES = [
5372
5420
  id: "login",
5373
5421
  title: "Log in with email / password and handle verification branch",
5374
5422
  description: "Users can log in. The login form handles the requiresVerification branch by routing to the verify-email step. Specific errors render (bad credentials, rate limited, disabled account).",
5375
- sdk: "client.login({email, password})",
5423
+ sdk: "client.loginCustomer(email, password)",
5376
5424
  flowRef: "auth-login",
5377
5425
  mandatory: "mandatory"
5378
5426
  },
@@ -5405,14 +5453,14 @@ var FEATURES = [
5405
5453
  id: "header",
5406
5454
  title: "Global header with cart count and search",
5407
5455
  description: "A header visible across the experience with the store logo, navigation, a cart icon with live item count, a search input with autocomplete (debounced ~300ms, 2-char minimum), and a login / account action. Below the header, discount banners render when active.",
5408
- sdk: "client.getCart() for the count, client.searchProducts(query) for autocomplete",
5456
+ sdk: "client.getCart() for the count, client.getSearchSuggestions(query) for autocomplete",
5409
5457
  mandatory: "mandatory"
5410
5458
  },
5411
5459
  {
5412
5460
  id: "discount-banners",
5413
5461
  title: "Show active discount banners and badges",
5414
5462
  description: "Active discount rules render as banners (below the header) and as badges on product cards / detail pages. Build the UI even if the store has no active discounts today \u2014 it auto-hides.",
5415
- sdk: "client.getDiscountBanners(), getProductDiscountBadge(product)",
5463
+ sdk: "client.getDiscountBanners(), client.getProductDiscountBadge(productId)",
5416
5464
  mandatory: "mandatory",
5417
5465
  capabilityFlag: "hasDiscountRules",
5418
5466
  whenDisabledNote: "Store has no discount rules active. Build the banner and badge components anyway \u2014 they auto-hide."