@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/bin/http.js +136 -88
- package/dist/bin/stdio.js +136 -88
- package/dist/index.js +136 -88
- package/dist/index.mjs +136 -88
- package/package.json +1 -1
package/dist/bin/http.js
CHANGED
|
@@ -95,7 +95,7 @@ npm install brainerce
|
|
|
95
95
|
import { BrainerceClient } from 'brainerce';
|
|
96
96
|
|
|
97
97
|
export const client = new BrainerceClient({
|
|
98
|
-
|
|
98
|
+
salesChannelId: '${connectionId}',
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
// Cart helpers \u2014 save cart ID to localStorage
|
|
@@ -197,7 +197,7 @@ async function startCheckout() {
|
|
|
197
197
|
### Full Checkout Flow (Multi-Provider)
|
|
198
198
|
|
|
199
199
|
1. Customer fills cart
|
|
200
|
-
2. Customer optionally applies coupon
|
|
200
|
+
2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
|
|
201
201
|
3. Detect payment providers \u2192 \`getPaymentProviders()\`
|
|
202
202
|
4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
|
|
203
203
|
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
|
|
@@ -215,7 +215,7 @@ import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-
|
|
|
215
215
|
|
|
216
216
|
function CheckoutPage() {
|
|
217
217
|
const [paymentData, setPaymentData] = useState<{
|
|
218
|
-
clientSecret: string; provider: string; checkoutId: string;
|
|
218
|
+
clientSecret: string; provider: string; checkoutId: string; clientSdk?: { renderType: string };
|
|
219
219
|
} | null>(null);
|
|
220
220
|
const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe> | null>(null);
|
|
221
221
|
const [paypalClientId, setPaypalClientId] = useState<string | null>(null);
|
|
@@ -277,7 +277,7 @@ function CheckoutPage() {
|
|
|
277
277
|
// saveCard: customerOptedIn, // optional opt-in
|
|
278
278
|
});
|
|
279
279
|
|
|
280
|
-
setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId });
|
|
280
|
+
setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId, clientSdk: paymentIntent.clientSdk });
|
|
281
281
|
|
|
282
282
|
// Step 5: Initialize the correct payment provider
|
|
283
283
|
if (paymentIntent.provider === 'stripe' && stripeProvider) {
|
|
@@ -887,6 +887,10 @@ function ProductPage({ product, storeInfo }: { product: Product; storeInfo: Stor
|
|
|
887
887
|
? getVariantPrice(selectedVariant, product.basePrice).toString()
|
|
888
888
|
: product.salePrice || product.basePrice;
|
|
889
889
|
|
|
890
|
+
// \u2705 For catalog cards: use pre-computed priceMin/priceMax instead of iterating variants
|
|
891
|
+
// product.priceMin / product.priceMax are always set for VARIABLE products with explicit variant prices.
|
|
892
|
+
// product.priceVaries is true when the range should be shown as "\u20AA49 \u2013 \u20AA199".
|
|
893
|
+
|
|
890
894
|
// Build attribute buttons (size, color, etc.)
|
|
891
895
|
const allOptions = product.variants?.map(v => getVariantOptions(v)) || [];
|
|
892
896
|
const attrNames = [...new Set(allOptions.flatMap(opts => opts.map(o => o.name)))];
|
|
@@ -1101,6 +1105,7 @@ const total = cart.total;
|
|
|
1101
1105
|
|
|
1102
1106
|
### Coupons
|
|
1103
1107
|
|
|
1108
|
+
**On the cart page** (before checkout is created):
|
|
1104
1109
|
\`\`\`typescript
|
|
1105
1110
|
// Apply coupon \u2014 returns updated Cart with discountAmount
|
|
1106
1111
|
const updatedCart = await client.applyCoupon(cartId, 'SAVE20');
|
|
@@ -1108,13 +1113,24 @@ console.log(updatedCart.discountAmount); // "10.00"
|
|
|
1108
1113
|
console.log(updatedCart.couponCode); // "SAVE20"
|
|
1109
1114
|
|
|
1110
1115
|
// Remove coupon
|
|
1111
|
-
|
|
1116
|
+
await client.removeCoupon(cartId);
|
|
1112
1117
|
|
|
1113
1118
|
// Calculate totals including discount
|
|
1114
1119
|
const totals = getCartTotals(cart); // { subtotal, discount, shipping, total }
|
|
1115
1120
|
\`\`\`
|
|
1116
1121
|
|
|
1117
|
-
|
|
1122
|
+
**On the checkout page** (after checkout session exists \u2014 ALWAYS use this when checkoutId is available):
|
|
1123
|
+
\`\`\`typescript
|
|
1124
|
+
// Applies to cart AND updates checkout totals in one call
|
|
1125
|
+
const checkout = await client.applyCheckoutCoupon(checkoutId, 'SAVE20');
|
|
1126
|
+
console.log(checkout.discountAmount); // "10.00"
|
|
1127
|
+
console.log(checkout.total); // correctly updated total
|
|
1128
|
+
|
|
1129
|
+
// Remove coupon from checkout
|
|
1130
|
+
await client.removeCheckoutCoupon(checkoutId);
|
|
1131
|
+
\`\`\`
|
|
1132
|
+
|
|
1133
|
+
> \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.
|
|
1118
1134
|
|
|
1119
1135
|
### \u26A0\uFE0F Checkout Order Summary \u2014 Use checkout.lineItems, NOT cart.items!
|
|
1120
1136
|
|
|
@@ -1934,7 +1950,7 @@ subsequent SDK call sends the \`Accept-Language\` header automatically:
|
|
|
1934
1950
|
\`\`\`typescript
|
|
1935
1951
|
import { BrainerceClient } from 'brainerce';
|
|
1936
1952
|
|
|
1937
|
-
const client = new BrainerceClient({
|
|
1953
|
+
const client = new BrainerceClient({ salesChannelId: 'vc_...' });
|
|
1938
1954
|
client.setLocale('he'); // done \u2014 all calls are now in Hebrew
|
|
1939
1955
|
\`\`\`
|
|
1940
1956
|
|
|
@@ -2651,6 +2667,9 @@ interface Product {
|
|
|
2651
2667
|
basePrice: string; // Use parseFloat() for calculations
|
|
2652
2668
|
salePrice?: string | null;
|
|
2653
2669
|
costPrice?: string | null;
|
|
2670
|
+
priceMin?: string | null; // Lowest variant price (VARIABLE products). Use for catalog/JSON-LD range display.
|
|
2671
|
+
priceMax?: string | null; // Highest variant price (VARIABLE products).
|
|
2672
|
+
priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
|
|
2654
2673
|
status: string;
|
|
2655
2674
|
type: 'SIMPLE' | 'VARIABLE';
|
|
2656
2675
|
isDownloadable?: boolean;
|
|
@@ -3221,9 +3240,9 @@ var HELPERS_TYPES = `// ---- Helper Functions (import from 'brainerce') ----
|
|
|
3221
3240
|
// Price helpers
|
|
3222
3241
|
function formatPrice(priceString: string | number | undefined | null, options?: { currency?: string; locale?: string; }): string;
|
|
3223
3242
|
function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice'>): number;
|
|
3224
|
-
function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice'>): {
|
|
3243
|
+
function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'>): {
|
|
3225
3244
|
price: number; originalPrice: number; isOnSale: boolean; discountAmount: number; discountPercent: number;
|
|
3226
|
-
};
|
|
3245
|
+
}; // Falls back to priceMin when basePrice=0 (VARIABLE products)
|
|
3227
3246
|
function getVariantPrice(variant: Pick<ProductVariant, 'price' | 'salePrice'>, productBasePrice: string): number;
|
|
3228
3247
|
|
|
3229
3248
|
// Cart helpers
|
|
@@ -3725,7 +3744,7 @@ function selectVariant(product: Product, selections: Record<string, string>) {
|
|
|
3725
3744
|
import { client } from './brainerce';
|
|
3726
3745
|
|
|
3727
3746
|
// 1. Submit the address + email. Email is REQUIRED on the DTO.
|
|
3728
|
-
const {
|
|
3747
|
+
const { checkout, rates } = await client.setShippingAddress(checkoutId, {
|
|
3729
3748
|
email,
|
|
3730
3749
|
firstName,
|
|
3731
3750
|
lastName,
|
|
@@ -3736,10 +3755,11 @@ const { shippingRates } = await client.setShippingAddress(checkoutId, {
|
|
|
3736
3755
|
postalCode,
|
|
3737
3756
|
country,
|
|
3738
3757
|
});
|
|
3758
|
+
// rates = available shipping rates; checkout = updated checkout object
|
|
3739
3759
|
|
|
3740
3760
|
// 2. Let the customer pick one of the returned rates.
|
|
3741
|
-
const chosen =
|
|
3742
|
-
await client.
|
|
3761
|
+
const chosen = rates[0]; // user selection
|
|
3762
|
+
await client.selectShippingMethod(checkoutId, chosen.id);
|
|
3743
3763
|
|
|
3744
3764
|
// 3. Re-fetch the checkout to get updated totals (tax may change after address).
|
|
3745
3765
|
const checkout = await client.getCheckout(checkoutId);
|
|
@@ -3793,21 +3813,21 @@ if (providers.length === 0) {
|
|
|
3793
3813
|
}
|
|
3794
3814
|
|
|
3795
3815
|
const active = providers[0];`,
|
|
3796
|
-
"checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js
|
|
3797
|
-
// (or the vanilla Stripe.js browser SDK).
|
|
3816
|
+
"checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js.
|
|
3798
3817
|
import { loadStripe } from '@stripe/stripe-js';
|
|
3799
3818
|
import { client } from './brainerce';
|
|
3800
3819
|
|
|
3801
3820
|
const stripe = await loadStripe(stripePublicKey);
|
|
3802
3821
|
if (!stripe) throw new Error('Stripe failed to load');
|
|
3803
3822
|
|
|
3804
|
-
//
|
|
3805
|
-
const
|
|
3806
|
-
|
|
3823
|
+
// Create a payment intent \u2014 returns clientSecret for Stripe Elements.
|
|
3824
|
+
const intent = await client.createPaymentIntent(checkoutId, {
|
|
3825
|
+
successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
|
|
3826
|
+
cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
|
|
3807
3827
|
});
|
|
3808
3828
|
|
|
3809
|
-
// Use Stripe Elements to
|
|
3810
|
-
const result = await stripe.confirmCardPayment(clientSecret, {
|
|
3829
|
+
// Use Stripe Elements to confirm:
|
|
3830
|
+
const result = await stripe.confirmCardPayment(intent.clientSecret, {
|
|
3811
3831
|
payment_method: { card: cardElement },
|
|
3812
3832
|
});
|
|
3813
3833
|
|
|
@@ -3816,21 +3836,21 @@ if (result.error) {
|
|
|
3816
3836
|
return;
|
|
3817
3837
|
}
|
|
3818
3838
|
|
|
3819
|
-
// Success \u2014 redirect to
|
|
3839
|
+
// Success \u2014 redirect to confirmation page.
|
|
3820
3840
|
// The confirmation page runs handlePaymentSuccess + waitForOrder.
|
|
3821
3841
|
window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);`,
|
|
3822
3842
|
"checkout-paypal-confirm": `// PayPal confirm flow. Use the PayPal JS SDK (render a PayPal button component).
|
|
3823
3843
|
import { client } from './brainerce';
|
|
3824
3844
|
|
|
3825
|
-
//
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
});
|
|
3845
|
+
// Create a payment intent first \u2014 for PayPal this returns the PayPal order ID.
|
|
3846
|
+
const intent = await client.createPaymentIntent(checkoutId, {
|
|
3847
|
+
successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
|
|
3848
|
+
cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
|
|
3849
|
+
});
|
|
3831
3850
|
|
|
3832
|
-
|
|
3833
|
-
|
|
3851
|
+
// Render a PayPal button using intent.clientSdk (provider SDK config).
|
|
3852
|
+
// When the PayPal button's onApprove fires, redirect to confirmation:
|
|
3853
|
+
function onPayPalApprove() {
|
|
3834
3854
|
window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);
|
|
3835
3855
|
}`,
|
|
3836
3856
|
"checkout-sandbox-confirm": `// Sandbox payment \u2014 store has sandboxPaymentsEnabled=true. No real charge.
|
|
@@ -3873,7 +3893,7 @@ async function registerAndMaybeVerify(input: {
|
|
|
3873
3893
|
firstName: string;
|
|
3874
3894
|
lastName: string;
|
|
3875
3895
|
}) {
|
|
3876
|
-
const result = await client.
|
|
3896
|
+
const result = await client.registerCustomer(input);
|
|
3877
3897
|
if (result.requiresVerification) {
|
|
3878
3898
|
// Route the user to your verify-email UI. Do NOT treat them as logged in.
|
|
3879
3899
|
return { state: 'needs-verification' as const };
|
|
@@ -3894,7 +3914,7 @@ async function resendCode() {
|
|
|
3894
3914
|
import { client } from './brainerce';
|
|
3895
3915
|
|
|
3896
3916
|
async function login(email: string, password: string) {
|
|
3897
|
-
const result = await client.
|
|
3917
|
+
const result = await client.loginCustomer(email, password);
|
|
3898
3918
|
if (result.requiresVerification) {
|
|
3899
3919
|
return { state: 'needs-verification' as const };
|
|
3900
3920
|
}
|
|
@@ -3929,25 +3949,37 @@ async function submitReset(token: string, newPassword: string) {
|
|
|
3929
3949
|
"oauth-redirect-and-callback": `// OAuth sign-in. Redirect flow, NOT popup.
|
|
3930
3950
|
import { client } from './brainerce';
|
|
3931
3951
|
|
|
3932
|
-
//
|
|
3933
|
-
const providers = await client.getAvailableOAuthProviders();
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3952
|
+
// Step 1: Get available provider names (strings: 'GOOGLE', 'FACEBOOK', 'GITHUB')
|
|
3953
|
+
const { providers } = await client.getAvailableOAuthProviders();
|
|
3954
|
+
|
|
3955
|
+
// Step 2: For each provider, fetch the authorization URL, then redirect.
|
|
3956
|
+
// Call this when the user clicks a provider button:
|
|
3957
|
+
async function redirectToOAuth(provider: string) {
|
|
3958
|
+
const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
|
|
3959
|
+
redirectUrl: \`\${window.location.origin}/auth/callback\`,
|
|
3960
|
+
});
|
|
3961
|
+
window.location.href = authorizationUrl; // full-page redirect, NOT a popup
|
|
3937
3962
|
}
|
|
3938
3963
|
|
|
3939
|
-
// On your
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3964
|
+
// Step 3: On your /auth/callback route, read token from URL params:
|
|
3965
|
+
const params = new URLSearchParams(window.location.search);
|
|
3966
|
+
const oauthError = params.get('oauth_error');
|
|
3967
|
+
const token = params.get('token');
|
|
3968
|
+
if (oauthError) {
|
|
3969
|
+
window.location.href = '/login?error=' + encodeURIComponent(oauthError);
|
|
3970
|
+
} else if (token) {
|
|
3971
|
+
client.setCustomerToken(token);
|
|
3972
|
+
window.location.href = '/account';
|
|
3973
|
+
}`,
|
|
3945
3974
|
"coupon-apply-and-remove": `// Coupon input. Build the UI even if no coupons exist today \u2014 it auto-hides.
|
|
3975
|
+
// IMPORTANT: use applyCheckoutCoupon when a checkoutId is available (checkout page).
|
|
3976
|
+
// Using applyCoupon after checkout creation does NOT update the checkout total.
|
|
3946
3977
|
import { client } from './brainerce';
|
|
3947
3978
|
|
|
3948
|
-
|
|
3979
|
+
// On cart page (no checkout session yet)
|
|
3980
|
+
async function applyCouponToCart(cartId: string, code: string) {
|
|
3949
3981
|
try {
|
|
3950
|
-
const cart = await client.applyCoupon(code);
|
|
3982
|
+
const cart = await client.applyCoupon(cartId, code);
|
|
3951
3983
|
return { ok: true, cart };
|
|
3952
3984
|
} catch (err) {
|
|
3953
3985
|
// Invalid / expired / minimum not met. Surface the specific error.
|
|
@@ -3955,17 +3987,30 @@ async function applyCoupon(code: string) {
|
|
|
3955
3987
|
}
|
|
3956
3988
|
}
|
|
3957
3989
|
|
|
3958
|
-
async function
|
|
3959
|
-
|
|
3960
|
-
|
|
3990
|
+
async function removeCouponFromCart(cartId: string) {
|
|
3991
|
+
return client.removeCoupon(cartId);
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// On checkout page (checkout session already exists \u2014 always prefer this)
|
|
3995
|
+
async function applyCouponToCheckout(checkoutId: string, code: string) {
|
|
3996
|
+
try {
|
|
3997
|
+
const checkout = await client.applyCheckoutCoupon(checkoutId, code);
|
|
3998
|
+
return { ok: true, checkout }; // checkout.total is already updated
|
|
3999
|
+
} catch (err) {
|
|
4000
|
+
return { ok: false, error: (err as Error).message };
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
async function removeCouponFromCheckout(checkoutId: string) {
|
|
4005
|
+
return client.removeCheckoutCoupon(checkoutId);
|
|
3961
4006
|
}`,
|
|
3962
4007
|
"reservation-countdown": `// Reservation countdown. Use the SDK's expiry timestamp \u2014 do NOT invent
|
|
3963
4008
|
// your own timer state.
|
|
3964
4009
|
import { client } from './brainerce';
|
|
3965
4010
|
|
|
3966
|
-
function getRemainingMs(cart: {
|
|
3967
|
-
if (!cart.
|
|
3968
|
-
const expiry = new Date(cart.
|
|
4011
|
+
function getRemainingMs(cart: { reservation?: { expiresAt?: string } }): number {
|
|
4012
|
+
if (!cart.reservation?.expiresAt) return 0;
|
|
4013
|
+
const expiry = new Date(cart.reservation.expiresAt).getTime();
|
|
3969
4014
|
return Math.max(0, expiry - Date.now());
|
|
3970
4015
|
}
|
|
3971
4016
|
|
|
@@ -3992,8 +4037,8 @@ function onSearchChange(query: string, onResults: (items: unknown[]) => void) {
|
|
|
3992
4037
|
return;
|
|
3993
4038
|
}
|
|
3994
4039
|
searchTimer = setTimeout(async () => {
|
|
3995
|
-
const
|
|
3996
|
-
onResults(
|
|
4040
|
+
const { products } = await client.getSearchSuggestions(query);
|
|
4041
|
+
onResults(products);
|
|
3997
4042
|
}, 300);
|
|
3998
4043
|
}`,
|
|
3999
4044
|
"i18n-set-locale": `// i18n \u2014 only if get-store-capabilities reports i18n.enabled.
|
|
@@ -4350,19 +4395,13 @@ import { client } from './brainerce-client';
|
|
|
4350
4395
|
|
|
4351
4396
|
interface CheckoutOptions {
|
|
4352
4397
|
cartId: string;
|
|
4353
|
-
shippingAddress: object;
|
|
4354
|
-
shippingMethodId: string;
|
|
4355
|
-
paymentProviderId: string;
|
|
4356
4398
|
}
|
|
4357
4399
|
|
|
4358
4400
|
async function createCheckoutSafe(opts: CheckoutOptions) {
|
|
4359
4401
|
try {
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
shippingMethodId: opts.shippingMethodId,
|
|
4364
|
-
paymentProviderId: opts.paymentProviderId,
|
|
4365
|
-
});
|
|
4402
|
+
// createCheckout only takes cartId (+ optional customerId, selectedItemIds).
|
|
4403
|
+
// Shipping address, shipping method, and payment are set via separate calls.
|
|
4404
|
+
return await client.createCheckout({ cartId: opts.cartId });
|
|
4366
4405
|
} catch (err: unknown) {
|
|
4367
4406
|
if (!isApiError(err, 'PRICE_DRIFT')) throw err;
|
|
4368
4407
|
|
|
@@ -5037,21 +5076,21 @@ var FLOWS = {
|
|
|
5037
5076
|
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\`.
|
|
5038
5077
|
2. **Submit the address to get shipping rates.**
|
|
5039
5078
|
\`\`\`ts
|
|
5040
|
-
const {
|
|
5079
|
+
const { checkout, rates } = await client.setShippingAddress(checkoutId, {
|
|
5041
5080
|
email,
|
|
5042
5081
|
firstName,
|
|
5043
5082
|
lastName,
|
|
5044
5083
|
line1, line2, city, region, postalCode, country,
|
|
5045
5084
|
});
|
|
5046
5085
|
\`\`\`
|
|
5047
|
-
|
|
5086
|
+
\`rates\` = available shipping rates for that address + the store's zones. \`checkout\` = updated checkout object.
|
|
5048
5087
|
3. **Let the customer pick a rate**, then persist the selection:
|
|
5049
5088
|
\`\`\`ts
|
|
5050
|
-
await client.
|
|
5089
|
+
await client.selectShippingMethod(checkoutId, rateId);
|
|
5051
5090
|
\`\`\`
|
|
5052
|
-
4. **Fetch payment providers
|
|
5091
|
+
4. **Fetch payment providers:**
|
|
5053
5092
|
\`\`\`ts
|
|
5054
|
-
const providers = await client.getPaymentProviders(
|
|
5093
|
+
const providers = await client.getPaymentProviders();
|
|
5055
5094
|
\`\`\`
|
|
5056
5095
|
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.
|
|
5057
5096
|
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.
|
|
@@ -5067,28 +5106,28 @@ Never bypass these steps. Never call \`submitGuestOrder\` / \`createOrder\` \u20
|
|
|
5067
5106
|
"auth-register": {
|
|
5068
5107
|
title: "Registration",
|
|
5069
5108
|
body: `1. **Collect** email, password, first name, last name. Enforce strong passwords client-side: 8+ chars, upper, lower, number, special.
|
|
5070
|
-
2. **Call
|
|
5109
|
+
2. **Call registerCustomer:**
|
|
5071
5110
|
\`\`\`ts
|
|
5072
|
-
const result = await client.
|
|
5111
|
+
const result = await client.registerCustomer({ email, password, firstName, lastName });
|
|
5073
5112
|
\`\`\`
|
|
5074
5113
|
3. **Branch on \`result.requiresVerification\`:**
|
|
5075
|
-
- If \`true\`: route the user to your verify-email UI. Do NOT treat them as logged in yet.
|
|
5076
|
-
- If \`false\`:
|
|
5114
|
+
- 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.
|
|
5115
|
+
- If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the account area.
|
|
5077
5116
|
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()\`.
|
|
5078
|
-
5. **After verifyEmail resolves:** the user is now logged in
|
|
5117
|
+
5. **After verifyEmail resolves:** call \`client.setCustomerToken(result.token)\` \u2014 the user is now logged in \u2014 then route to the account area.
|
|
5079
5118
|
|
|
5080
5119
|
Build the verify-email step EVEN IF the store currently has verification disabled. It auto-hides; store owners enable it later.`
|
|
5081
5120
|
},
|
|
5082
5121
|
"auth-login": {
|
|
5083
5122
|
title: "Login",
|
|
5084
5123
|
body: `1. **Collect** email + password.
|
|
5085
|
-
2. **Call
|
|
5124
|
+
2. **Call loginCustomer:**
|
|
5086
5125
|
\`\`\`ts
|
|
5087
|
-
const result = await client.
|
|
5126
|
+
const result = await client.loginCustomer(email, password);
|
|
5088
5127
|
\`\`\`
|
|
5089
5128
|
3. **Branch on \`result.requiresVerification\`:**
|
|
5090
5129
|
- If \`true\`: route to verify-email. The user must complete verification before accessing account features.
|
|
5091
|
-
- If \`false\`:
|
|
5130
|
+
- If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the previous page (or account area).
|
|
5092
5131
|
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.
|
|
5093
5132
|
5. **On error**, render the specific message (invalid credentials, rate limited, account disabled) \u2014 never swallow.`
|
|
5094
5133
|
},
|
|
@@ -5113,15 +5152,24 @@ Build both steps EVEN IF the store has no email provider configured today \u2014
|
|
|
5113
5152
|
},
|
|
5114
5153
|
oauth: {
|
|
5115
5154
|
title: "OAuth sign-in",
|
|
5116
|
-
body: `1. **
|
|
5155
|
+
body: `1. **Get available provider names:**
|
|
5156
|
+
\`\`\`ts
|
|
5157
|
+
const { providers } = await client.getAvailableOAuthProviders();
|
|
5158
|
+
// providers = ['GOOGLE', 'FACEBOOK', 'GITHUB'] (strings, not objects with authorizationUrl)
|
|
5159
|
+
\`\`\`
|
|
5160
|
+
2. **For each provider, fetch the authorization URL:**
|
|
5161
|
+
\`\`\`ts
|
|
5162
|
+
const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
|
|
5163
|
+
redirectUrl: \`\${window.location.origin}/auth/callback\`,
|
|
5164
|
+
});
|
|
5165
|
+
window.location.href = authorizationUrl; // full-page redirect, NOT a popup
|
|
5166
|
+
\`\`\`
|
|
5167
|
+
3. **On the callback page** the URL contains \`token\` + \`oauth_success\` (or \`oauth_error\`) query params. Extract and apply the token:
|
|
5117
5168
|
\`\`\`ts
|
|
5118
|
-
const
|
|
5169
|
+
const token = new URLSearchParams(location.search).get('token');
|
|
5170
|
+
if (token) client.setCustomerToken(token); // then redirect to account
|
|
5119
5171
|
\`\`\`
|
|
5120
|
-
|
|
5121
|
-
2. **Build a button for each** that redirects the browser to \`authorizationUrl\`. Do NOT open a popup; the full-page redirect is required.
|
|
5122
|
-
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.
|
|
5123
|
-
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.
|
|
5124
|
-
5. **On \`oauth_error\`:** redirect to login with an error message.
|
|
5172
|
+
4. **On \`oauth_error\`:** redirect to login with an error message.
|
|
5125
5173
|
|
|
5126
5174
|
Build the OAuth button region AND the callback handler even when no providers are configured. They auto-hide.`
|
|
5127
5175
|
},
|
|
@@ -5143,7 +5191,7 @@ Build the OAuth button region AND the callback handler even when no providers ar
|
|
|
5143
5191
|
|
|
5144
5192
|
- **Cart ID persistence:** the SDK stores the cart ID across reloads. You do not need to write cart-to-localStorage code yourself.
|
|
5145
5193
|
- **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).
|
|
5146
|
-
- **Writes:** use \`client.addToCart\`, \`client.updateCartItem\`, \`client.removeCartItem\`, \`client.applyCoupon\`, \`client.removeCoupon\`. After each mutation the SDK returns the updated cart.
|
|
5194
|
+
- **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.
|
|
5147
5195
|
- **Totals:** call \`getCartTotals(cart)\` \u2014 do NOT read \`cart.total\`. The helper understands taxes, shipping, and discounts.
|
|
5148
5196
|
- **\`smartGetCart()\`** returns \`CartWithIncludes\` (extends \`Cart\`). All carts are server-side. Pass \`{ include: ['recommendations', 'upgrades', 'bundles'] }\` to fetch extras in one request.
|
|
5149
5197
|
- **NEVER mutate cart state outside SDK helpers.** Any hand-rolled cart update risks desync with the reservation timer and the checkout flow.`
|
|
@@ -5275,7 +5323,7 @@ var FEATURES = [
|
|
|
5275
5323
|
id: "browse-products",
|
|
5276
5324
|
title: "Browse, filter, and search products",
|
|
5277
5325
|
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.",
|
|
5278
|
-
sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.
|
|
5326
|
+
sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.getSearchSuggestions(query)",
|
|
5279
5327
|
mandatory: "mandatory"
|
|
5280
5328
|
},
|
|
5281
5329
|
{
|
|
@@ -5305,7 +5353,7 @@ var FEATURES = [
|
|
|
5305
5353
|
id: "cart-coupons",
|
|
5306
5354
|
title: "Apply and remove coupon codes",
|
|
5307
5355
|
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.",
|
|
5308
|
-
sdk: "client.applyCoupon(code), client.removeCoupon()",
|
|
5356
|
+
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)",
|
|
5309
5357
|
mandatory: "mandatory",
|
|
5310
5358
|
capabilityFlag: "hasCoupons",
|
|
5311
5359
|
whenDisabledNote: "Store has no coupons configured today. Build the UI anyway \u2014 it auto-hides until the store owner creates a coupon."
|
|
@@ -5322,7 +5370,7 @@ var FEATURES = [
|
|
|
5322
5370
|
id: "checkout",
|
|
5323
5371
|
title: "Complete a full checkout end-to-end",
|
|
5324
5372
|
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.",
|
|
5325
|
-
sdk: "client.setShippingAddress, client.
|
|
5373
|
+
sdk: "client.setShippingAddress (returns { checkout, rates }), client.selectShippingMethod, client.getPaymentProviders(), provider-specific confirm, client.handlePaymentSuccess, client.waitForOrder",
|
|
5326
5374
|
flowRef: "checkout",
|
|
5327
5375
|
mandatory: "mandatory"
|
|
5328
5376
|
},
|
|
@@ -5338,7 +5386,7 @@ var FEATURES = [
|
|
|
5338
5386
|
id: "register",
|
|
5339
5387
|
title: "Register a new account with email verification",
|
|
5340
5388
|
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.',
|
|
5341
|
-
sdk: "client.
|
|
5389
|
+
sdk: "client.registerCustomer({email, password, firstName, lastName}), client.verifyEmail(code), client.resendVerificationEmail()",
|
|
5342
5390
|
flowRef: "auth-register",
|
|
5343
5391
|
mandatory: "mandatory",
|
|
5344
5392
|
capabilityFlag: "oauth",
|
|
@@ -5348,7 +5396,7 @@ var FEATURES = [
|
|
|
5348
5396
|
id: "login",
|
|
5349
5397
|
title: "Log in with email / password and handle verification branch",
|
|
5350
5398
|
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).",
|
|
5351
|
-
sdk: "client.
|
|
5399
|
+
sdk: "client.loginCustomer(email, password)",
|
|
5352
5400
|
flowRef: "auth-login",
|
|
5353
5401
|
mandatory: "mandatory"
|
|
5354
5402
|
},
|
|
@@ -5381,14 +5429,14 @@ var FEATURES = [
|
|
|
5381
5429
|
id: "header",
|
|
5382
5430
|
title: "Global header with cart count and search",
|
|
5383
5431
|
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.",
|
|
5384
|
-
sdk: "client.getCart() for the count, client.
|
|
5432
|
+
sdk: "client.getCart() for the count, client.getSearchSuggestions(query) for autocomplete",
|
|
5385
5433
|
mandatory: "mandatory"
|
|
5386
5434
|
},
|
|
5387
5435
|
{
|
|
5388
5436
|
id: "discount-banners",
|
|
5389
5437
|
title: "Show active discount banners and badges",
|
|
5390
5438
|
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.",
|
|
5391
|
-
sdk: "client.getDiscountBanners(), getProductDiscountBadge(
|
|
5439
|
+
sdk: "client.getDiscountBanners(), client.getProductDiscountBadge(productId)",
|
|
5392
5440
|
mandatory: "mandatory",
|
|
5393
5441
|
capabilityFlag: "hasDiscountRules",
|
|
5394
5442
|
whenDisabledNote: "Store has no discount rules active. Build the banner and badge components anyway \u2014 they auto-hide."
|