@brainerce/mcp-server 3.12.0 → 3.12.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 +105 -10
- package/dist/bin/stdio.js +105 -10
- package/dist/index.js +105 -10
- package/dist/index.mjs +105 -10
- package/package.json +2 -1
package/dist/bin/http.js
CHANGED
|
@@ -721,17 +721,19 @@ function getPaymentProvidersSection() {
|
|
|
721
721
|
return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
|
|
722
722
|
|
|
723
723
|
\`\`\`typescript
|
|
724
|
-
const { hasPayments, providers } = await client.getPaymentProviders();
|
|
724
|
+
const { hasPayments, providers, defaultProvider } = await client.getPaymentProviders();
|
|
725
725
|
if (!hasPayments) {
|
|
726
726
|
// Show error: "Payment is not configured for this store"
|
|
727
727
|
return;
|
|
728
728
|
}
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
const
|
|
729
|
+
// Primary vs additive (Shopify-parity): render wallets as express buttons ABOVE the card form.
|
|
730
|
+
const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal (WALLET)
|
|
731
|
+
const primary = defaultProvider && !defaultProvider.isAdditive ? defaultProvider : undefined;
|
|
732
732
|
\`\`\`
|
|
733
733
|
|
|
734
|
-
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault
|
|
734
|
+
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault\`, plus the taxonomy fields \`methodType\` (\`'CREDIT_CARD'\` = the single primary card processor that settles the order; \`'WALLET'\`/other = additive), \`isAdditive\`, and \`presentation\` (\`'card_form'\` | \`'express_button'\` | \u2026).
|
|
735
|
+
|
|
736
|
+
**Primary vs. additive:** there is one primary card processor (the \`defaultProvider\`, \`isAdditive: false\`) plus any additive methods (\`isAdditive: true\`, e.g. PayPal). Render additive methods as accelerated-checkout **express buttons above** the card form \u2014 they sit alongside the primary, never replace it. Create the intent with the tapped provider's \`id\`. (Exception: a wallet-only store has no card processor, so its wallet becomes the \`defaultProvider\` and stands alone.)
|
|
735
737
|
|
|
736
738
|
The payment intent returned from \`createPaymentIntent()\` includes a \`clientSdk\` object whose \`renderType\` tells you exactly how to render \u2014 **branch on THAT, not on the provider name**:
|
|
737
739
|
|
|
@@ -1119,11 +1121,22 @@ client-side overlay needed. Swatch colors are language-agnostic.
|
|
|
1119
1121
|
function ProductDescription({ product }: { product: Product }) {
|
|
1120
1122
|
const content = getDescriptionContent(product);
|
|
1121
1123
|
if (!content) return null;
|
|
1122
|
-
|
|
1124
|
+
// Descriptions are merchant HTML and may contain <video> + host-locked
|
|
1125
|
+
// YouTube/Vimeo <iframe> embeds. NEVER render unsanitized \u2014 sanitize and
|
|
1126
|
+
// allowlist video/source + iframe (restricted to youtube/vimeo hosts).
|
|
1127
|
+
if ('html' in content) {
|
|
1128
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitizeProductHtml(content.html) }} />;
|
|
1129
|
+
}
|
|
1123
1130
|
return <p>{content.text}</p>;
|
|
1124
1131
|
}
|
|
1125
1132
|
\`\`\`
|
|
1126
1133
|
|
|
1134
|
+
**Sanitize descriptions.** \`sanitizeProductHtml\` is the host-locked sanitizer the
|
|
1135
|
+
\`create-brainerce-store\` scaffold ships at \`src/lib/sanitize-html.ts\`. If you roll
|
|
1136
|
+
your own, allow \`video\`/\`source\` and \`iframe\` **restricted** to \`www.youtube.com\`,
|
|
1137
|
+
\`www.youtube-nocookie.com\`, \`player.vimeo.com\` (never arbitrary iframes), and add
|
|
1138
|
+
those hosts to your CSP \`frame-src\` or embeds render blank.
|
|
1139
|
+
|
|
1127
1140
|
### Metafields (Custom Product Fields) \u2014 display on product detail!
|
|
1128
1141
|
|
|
1129
1142
|
**IMPORTANT: Render metafield values based on \`mf.type\`!** Don't just display \`mf.value\` as text for all types.
|
|
@@ -1206,7 +1219,28 @@ const { url } = await client.uploadCustomizationFile(file);
|
|
|
1206
1219
|
// Then use the URL in metadata: metadata: { logo: url }
|
|
1207
1220
|
\`\`\`
|
|
1208
1221
|
|
|
1209
|
-
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues
|
|
1222
|
+
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues\` (array of \`{label, value, swatchColor?, swatchImageUrl?}\`), \`defaultValue\`, \`position\`
|
|
1223
|
+
|
|
1224
|
+
**SELECT / MULTI_SELECT \u2014 use \`option.value\` to submit, \`option.label\` to display:**
|
|
1225
|
+
|
|
1226
|
+
\`\`\`typescript
|
|
1227
|
+
for (const option of field.enumValues ?? []) {
|
|
1228
|
+
// option.value \u2192 what to submit in metadata
|
|
1229
|
+
// option.label \u2192 what to show the customer
|
|
1230
|
+
// option.swatchColor \u2192 optional hex color for color swatches
|
|
1231
|
+
}
|
|
1232
|
+
\`\`\`
|
|
1233
|
+
|
|
1234
|
+
**Display customizations in cart / checkout \u2014 no extra API call needed:**
|
|
1235
|
+
|
|
1236
|
+
CartItem and CheckoutLineItem both include a \`customizations\` object alongside \`metadata\`. Use \`customizations\` for display \u2014 it contains resolved labels, not raw keys.
|
|
1237
|
+
|
|
1238
|
+
\`\`\`typescript
|
|
1239
|
+
// Works for CartItem, CheckoutLineItem, and OrderItem \u2014 same shape
|
|
1240
|
+
const lines = Object.values(item.customizations ?? {})
|
|
1241
|
+
.map(c => \`\${c.label}: \${Array.isArray(c.value) ? c.value.join(', ') : c.value}\`);
|
|
1242
|
+
// e.g. ["Frame color: Gold", "Add-ons: Gift wrap"]
|
|
1243
|
+
\`\`\`
|
|
1210
1244
|
|
|
1211
1245
|
### Downloadable / Digital Products
|
|
1212
1246
|
|
|
@@ -3238,6 +3272,44 @@ by device) and can also be asked of the AI assistant ("how many visits did I get
|
|
|
3238
3272
|
this month and from where?").
|
|
3239
3273
|
`;
|
|
3240
3274
|
}
|
|
3275
|
+
function getShippingAppSection() {
|
|
3276
|
+
return `## App Store Shipping (Shippo)
|
|
3277
|
+
|
|
3278
|
+
Merchants install Shippo from the Brainerce App Store. After connecting their own Shippo account
|
|
3279
|
+
(OAuth), live carrier rates appear automatically at checkout alongside any manually configured
|
|
3280
|
+
zone rates. **Billing goes directly to the merchant's Shippo account \u2014 Brainerce never charges.**
|
|
3281
|
+
|
|
3282
|
+
### Checkout \u2014 live carrier rates
|
|
3283
|
+
|
|
3284
|
+
No SDK changes needed. Once Shippo is installed and configured, the existing
|
|
3285
|
+
\`getShippingRates()\` call returns both manual zone rates and live Shippo quotes:
|
|
3286
|
+
|
|
3287
|
+
\`\`\`typescript
|
|
3288
|
+
const { rates } = await client.setShippingAddress(checkoutId, { ... });
|
|
3289
|
+
// rates may include:
|
|
3290
|
+
// { id: 'shippo:rate_8f123abc', name: 'USPS Priority Mail', price: '8.50', currency: 'USD', source: 'carrier' }
|
|
3291
|
+
\`\`\`
|
|
3292
|
+
|
|
3293
|
+
Rates prefixed \`shippo:\` are live Shippo quotes. Pass the full \`id\` as \`shippingRateId\`
|
|
3294
|
+
when calling \`selectShippingMethod()\`.
|
|
3295
|
+
|
|
3296
|
+
### Admin \u2014 purchase a shipping label
|
|
3297
|
+
|
|
3298
|
+
After the order is placed, the merchant purchases a label by passing the Shippo rate object_id:
|
|
3299
|
+
|
|
3300
|
+
\`\`\`typescript
|
|
3301
|
+
const label = await admin.createShippingLabel(orderId, {
|
|
3302
|
+
shippoRateObjectId: 'rate_8f123456789abcdef',
|
|
3303
|
+
});
|
|
3304
|
+
console.log(label.labelUrl); // PDF ready to print
|
|
3305
|
+
console.log(label.trackingNumber); // auto-populated on the order
|
|
3306
|
+
\`\`\`
|
|
3307
|
+
|
|
3308
|
+
The \`trackingNumber\` is stored on the \`Order\` and returned in \`getMyOrders()\`
|
|
3309
|
+
so customers can track their shipment automatically.
|
|
3310
|
+
|
|
3311
|
+
**SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
|
|
3312
|
+
}
|
|
3241
3313
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
3242
3314
|
const cid = connectionId || "vc_YOUR_CONNECTION_ID";
|
|
3243
3315
|
const cur = currency || "USD";
|
|
@@ -3297,6 +3369,9 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
3297
3369
|
case "analytics":
|
|
3298
3370
|
case "traffic-analytics":
|
|
3299
3371
|
return getStorefrontAnalyticsSection(cid);
|
|
3372
|
+
case "shipping-app":
|
|
3373
|
+
case "shippo":
|
|
3374
|
+
return getShippingAppSection();
|
|
3300
3375
|
case "all":
|
|
3301
3376
|
return [
|
|
3302
3377
|
"# Brainerce SDK \u2014 full topic dump",
|
|
@@ -3579,11 +3654,18 @@ interface ProductCustomizationField {
|
|
|
3579
3654
|
maxLength?: number | null;
|
|
3580
3655
|
minValue?: number | null; // NUMBER bounds
|
|
3581
3656
|
maxValue?: number | null;
|
|
3582
|
-
enumValues?:
|
|
3657
|
+
enumValues?: CustomizationFieldOption[]; // REQUIRED for SELECT / MULTI_SELECT
|
|
3583
3658
|
defaultValue?: string | null;
|
|
3584
3659
|
position: number; // Render order
|
|
3585
3660
|
}
|
|
3586
3661
|
|
|
3662
|
+
interface CustomizationFieldOption {
|
|
3663
|
+
label: string; // Display label (e.g., "Rose Gold")
|
|
3664
|
+
value: string; // Value stored in CartItem.metadata
|
|
3665
|
+
swatchColor?: string | null; // Optional hex color (e.g., "#B76E79")
|
|
3666
|
+
swatchImageUrl?: string | null; // Optional swatch image URL
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3587
3669
|
interface ProductQueryParams {
|
|
3588
3670
|
page?: number;
|
|
3589
3671
|
limit?: number;
|
|
@@ -3677,6 +3759,7 @@ interface CartItem {
|
|
|
3677
3759
|
image?: ProductImage | string | null;
|
|
3678
3760
|
} | null;
|
|
3679
3761
|
metadata?: Record<string, unknown> | null; // Buyer-submitted customization values, keyed by ProductCustomizationField.key
|
|
3762
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 use this to display "Color: Silver" without extra getProduct() call
|
|
3680
3763
|
createdAt: string;
|
|
3681
3764
|
updatedAt: string;
|
|
3682
3765
|
}
|
|
@@ -3776,6 +3859,7 @@ interface CheckoutLineItem {
|
|
|
3776
3859
|
product: { id: string; name: string; sku: string; images?: ProductImage[]; };
|
|
3777
3860
|
variant?: { id: string; name?: string | null; sku?: string | null; image?: ProductImage | string | null; } | null;
|
|
3778
3861
|
metadata?: Record<string, unknown> | null; // Copied from CartItem.metadata \u2014 buyer customization values
|
|
3862
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 same shape as OrderItem.customizations
|
|
3779
3863
|
}
|
|
3780
3864
|
|
|
3781
3865
|
interface CheckoutAddress {
|
|
@@ -4012,7 +4096,10 @@ interface PaymentProviderConfig {
|
|
|
4012
4096
|
stripeAccountId?: string; // Stripe Connect only
|
|
4013
4097
|
supportedMethods: string[];
|
|
4014
4098
|
testMode: boolean;
|
|
4015
|
-
isDefault: boolean;
|
|
4099
|
+
isDefault: boolean; // only a primary (CREDIT_CARD) is ever the default
|
|
4100
|
+
methodType: 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
4101
|
+
presentation: 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
4102
|
+
isAdditive: boolean; // wallet/alt method shown ALONGSIDE the primary (e.g. PayPal)
|
|
4016
4103
|
}
|
|
4017
4104
|
type PaymentProvider = PaymentProviderConfig;
|
|
4018
4105
|
|
|
@@ -5060,7 +5147,15 @@ const checkout = await client.getCheckout(checkoutId);
|
|
|
5060
5147
|
import { client } from './brainerce';
|
|
5061
5148
|
|
|
5062
5149
|
const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
5063
|
-
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5150
|
+
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5151
|
+
// isDefault, methodType, presentation, isAdditive, clientSdk? }
|
|
5152
|
+
//
|
|
5153
|
+
// methodType splits providers into ONE primary card processor (CREDIT_CARD, the
|
|
5154
|
+
// defaultProvider that settles the order) and additive methods (isAdditive:true,
|
|
5155
|
+
// e.g. PayPal as a WALLET). Render additive methods as express buttons ABOVE the
|
|
5156
|
+
// card form \u2014 they sit alongside the primary, never replace it. Call
|
|
5157
|
+
// createPaymentIntent({ providerId }) with the tapped provider's id. (Wallet-only
|
|
5158
|
+
// store: with no card processor, the wallet becomes defaultProvider and stands alone.)
|
|
5064
5159
|
//
|
|
5065
5160
|
// After createPaymentIntent, the response carries a clientSdk.renderType that
|
|
5066
5161
|
// tells your UI how to render the payment step. The 5 possible values:
|
package/dist/bin/stdio.js
CHANGED
|
@@ -718,17 +718,19 @@ function getPaymentProvidersSection() {
|
|
|
718
718
|
return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
|
|
719
719
|
|
|
720
720
|
\`\`\`typescript
|
|
721
|
-
const { hasPayments, providers } = await client.getPaymentProviders();
|
|
721
|
+
const { hasPayments, providers, defaultProvider } = await client.getPaymentProviders();
|
|
722
722
|
if (!hasPayments) {
|
|
723
723
|
// Show error: "Payment is not configured for this store"
|
|
724
724
|
return;
|
|
725
725
|
}
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
const
|
|
726
|
+
// Primary vs additive (Shopify-parity): render wallets as express buttons ABOVE the card form.
|
|
727
|
+
const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal (WALLET)
|
|
728
|
+
const primary = defaultProvider && !defaultProvider.isAdditive ? defaultProvider : undefined;
|
|
729
729
|
\`\`\`
|
|
730
730
|
|
|
731
|
-
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault
|
|
731
|
+
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault\`, plus the taxonomy fields \`methodType\` (\`'CREDIT_CARD'\` = the single primary card processor that settles the order; \`'WALLET'\`/other = additive), \`isAdditive\`, and \`presentation\` (\`'card_form'\` | \`'express_button'\` | \u2026).
|
|
732
|
+
|
|
733
|
+
**Primary vs. additive:** there is one primary card processor (the \`defaultProvider\`, \`isAdditive: false\`) plus any additive methods (\`isAdditive: true\`, e.g. PayPal). Render additive methods as accelerated-checkout **express buttons above** the card form \u2014 they sit alongside the primary, never replace it. Create the intent with the tapped provider's \`id\`. (Exception: a wallet-only store has no card processor, so its wallet becomes the \`defaultProvider\` and stands alone.)
|
|
732
734
|
|
|
733
735
|
The payment intent returned from \`createPaymentIntent()\` includes a \`clientSdk\` object whose \`renderType\` tells you exactly how to render \u2014 **branch on THAT, not on the provider name**:
|
|
734
736
|
|
|
@@ -1116,11 +1118,22 @@ client-side overlay needed. Swatch colors are language-agnostic.
|
|
|
1116
1118
|
function ProductDescription({ product }: { product: Product }) {
|
|
1117
1119
|
const content = getDescriptionContent(product);
|
|
1118
1120
|
if (!content) return null;
|
|
1119
|
-
|
|
1121
|
+
// Descriptions are merchant HTML and may contain <video> + host-locked
|
|
1122
|
+
// YouTube/Vimeo <iframe> embeds. NEVER render unsanitized \u2014 sanitize and
|
|
1123
|
+
// allowlist video/source + iframe (restricted to youtube/vimeo hosts).
|
|
1124
|
+
if ('html' in content) {
|
|
1125
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitizeProductHtml(content.html) }} />;
|
|
1126
|
+
}
|
|
1120
1127
|
return <p>{content.text}</p>;
|
|
1121
1128
|
}
|
|
1122
1129
|
\`\`\`
|
|
1123
1130
|
|
|
1131
|
+
**Sanitize descriptions.** \`sanitizeProductHtml\` is the host-locked sanitizer the
|
|
1132
|
+
\`create-brainerce-store\` scaffold ships at \`src/lib/sanitize-html.ts\`. If you roll
|
|
1133
|
+
your own, allow \`video\`/\`source\` and \`iframe\` **restricted** to \`www.youtube.com\`,
|
|
1134
|
+
\`www.youtube-nocookie.com\`, \`player.vimeo.com\` (never arbitrary iframes), and add
|
|
1135
|
+
those hosts to your CSP \`frame-src\` or embeds render blank.
|
|
1136
|
+
|
|
1124
1137
|
### Metafields (Custom Product Fields) \u2014 display on product detail!
|
|
1125
1138
|
|
|
1126
1139
|
**IMPORTANT: Render metafield values based on \`mf.type\`!** Don't just display \`mf.value\` as text for all types.
|
|
@@ -1203,7 +1216,28 @@ const { url } = await client.uploadCustomizationFile(file);
|
|
|
1203
1216
|
// Then use the URL in metadata: metadata: { logo: url }
|
|
1204
1217
|
\`\`\`
|
|
1205
1218
|
|
|
1206
|
-
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues
|
|
1219
|
+
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues\` (array of \`{label, value, swatchColor?, swatchImageUrl?}\`), \`defaultValue\`, \`position\`
|
|
1220
|
+
|
|
1221
|
+
**SELECT / MULTI_SELECT \u2014 use \`option.value\` to submit, \`option.label\` to display:**
|
|
1222
|
+
|
|
1223
|
+
\`\`\`typescript
|
|
1224
|
+
for (const option of field.enumValues ?? []) {
|
|
1225
|
+
// option.value \u2192 what to submit in metadata
|
|
1226
|
+
// option.label \u2192 what to show the customer
|
|
1227
|
+
// option.swatchColor \u2192 optional hex color for color swatches
|
|
1228
|
+
}
|
|
1229
|
+
\`\`\`
|
|
1230
|
+
|
|
1231
|
+
**Display customizations in cart / checkout \u2014 no extra API call needed:**
|
|
1232
|
+
|
|
1233
|
+
CartItem and CheckoutLineItem both include a \`customizations\` object alongside \`metadata\`. Use \`customizations\` for display \u2014 it contains resolved labels, not raw keys.
|
|
1234
|
+
|
|
1235
|
+
\`\`\`typescript
|
|
1236
|
+
// Works for CartItem, CheckoutLineItem, and OrderItem \u2014 same shape
|
|
1237
|
+
const lines = Object.values(item.customizations ?? {})
|
|
1238
|
+
.map(c => \`\${c.label}: \${Array.isArray(c.value) ? c.value.join(', ') : c.value}\`);
|
|
1239
|
+
// e.g. ["Frame color: Gold", "Add-ons: Gift wrap"]
|
|
1240
|
+
\`\`\`
|
|
1207
1241
|
|
|
1208
1242
|
### Downloadable / Digital Products
|
|
1209
1243
|
|
|
@@ -3235,6 +3269,44 @@ by device) and can also be asked of the AI assistant ("how many visits did I get
|
|
|
3235
3269
|
this month and from where?").
|
|
3236
3270
|
`;
|
|
3237
3271
|
}
|
|
3272
|
+
function getShippingAppSection() {
|
|
3273
|
+
return `## App Store Shipping (Shippo)
|
|
3274
|
+
|
|
3275
|
+
Merchants install Shippo from the Brainerce App Store. After connecting their own Shippo account
|
|
3276
|
+
(OAuth), live carrier rates appear automatically at checkout alongside any manually configured
|
|
3277
|
+
zone rates. **Billing goes directly to the merchant's Shippo account \u2014 Brainerce never charges.**
|
|
3278
|
+
|
|
3279
|
+
### Checkout \u2014 live carrier rates
|
|
3280
|
+
|
|
3281
|
+
No SDK changes needed. Once Shippo is installed and configured, the existing
|
|
3282
|
+
\`getShippingRates()\` call returns both manual zone rates and live Shippo quotes:
|
|
3283
|
+
|
|
3284
|
+
\`\`\`typescript
|
|
3285
|
+
const { rates } = await client.setShippingAddress(checkoutId, { ... });
|
|
3286
|
+
// rates may include:
|
|
3287
|
+
// { id: 'shippo:rate_8f123abc', name: 'USPS Priority Mail', price: '8.50', currency: 'USD', source: 'carrier' }
|
|
3288
|
+
\`\`\`
|
|
3289
|
+
|
|
3290
|
+
Rates prefixed \`shippo:\` are live Shippo quotes. Pass the full \`id\` as \`shippingRateId\`
|
|
3291
|
+
when calling \`selectShippingMethod()\`.
|
|
3292
|
+
|
|
3293
|
+
### Admin \u2014 purchase a shipping label
|
|
3294
|
+
|
|
3295
|
+
After the order is placed, the merchant purchases a label by passing the Shippo rate object_id:
|
|
3296
|
+
|
|
3297
|
+
\`\`\`typescript
|
|
3298
|
+
const label = await admin.createShippingLabel(orderId, {
|
|
3299
|
+
shippoRateObjectId: 'rate_8f123456789abcdef',
|
|
3300
|
+
});
|
|
3301
|
+
console.log(label.labelUrl); // PDF ready to print
|
|
3302
|
+
console.log(label.trackingNumber); // auto-populated on the order
|
|
3303
|
+
\`\`\`
|
|
3304
|
+
|
|
3305
|
+
The \`trackingNumber\` is stored on the \`Order\` and returned in \`getMyOrders()\`
|
|
3306
|
+
so customers can track their shipment automatically.
|
|
3307
|
+
|
|
3308
|
+
**SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
|
|
3309
|
+
}
|
|
3238
3310
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
3239
3311
|
const cid = connectionId || "vc_YOUR_CONNECTION_ID";
|
|
3240
3312
|
const cur = currency || "USD";
|
|
@@ -3294,6 +3366,9 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
3294
3366
|
case "analytics":
|
|
3295
3367
|
case "traffic-analytics":
|
|
3296
3368
|
return getStorefrontAnalyticsSection(cid);
|
|
3369
|
+
case "shipping-app":
|
|
3370
|
+
case "shippo":
|
|
3371
|
+
return getShippingAppSection();
|
|
3297
3372
|
case "all":
|
|
3298
3373
|
return [
|
|
3299
3374
|
"# Brainerce SDK \u2014 full topic dump",
|
|
@@ -3576,11 +3651,18 @@ interface ProductCustomizationField {
|
|
|
3576
3651
|
maxLength?: number | null;
|
|
3577
3652
|
minValue?: number | null; // NUMBER bounds
|
|
3578
3653
|
maxValue?: number | null;
|
|
3579
|
-
enumValues?:
|
|
3654
|
+
enumValues?: CustomizationFieldOption[]; // REQUIRED for SELECT / MULTI_SELECT
|
|
3580
3655
|
defaultValue?: string | null;
|
|
3581
3656
|
position: number; // Render order
|
|
3582
3657
|
}
|
|
3583
3658
|
|
|
3659
|
+
interface CustomizationFieldOption {
|
|
3660
|
+
label: string; // Display label (e.g., "Rose Gold")
|
|
3661
|
+
value: string; // Value stored in CartItem.metadata
|
|
3662
|
+
swatchColor?: string | null; // Optional hex color (e.g., "#B76E79")
|
|
3663
|
+
swatchImageUrl?: string | null; // Optional swatch image URL
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3584
3666
|
interface ProductQueryParams {
|
|
3585
3667
|
page?: number;
|
|
3586
3668
|
limit?: number;
|
|
@@ -3674,6 +3756,7 @@ interface CartItem {
|
|
|
3674
3756
|
image?: ProductImage | string | null;
|
|
3675
3757
|
} | null;
|
|
3676
3758
|
metadata?: Record<string, unknown> | null; // Buyer-submitted customization values, keyed by ProductCustomizationField.key
|
|
3759
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 use this to display "Color: Silver" without extra getProduct() call
|
|
3677
3760
|
createdAt: string;
|
|
3678
3761
|
updatedAt: string;
|
|
3679
3762
|
}
|
|
@@ -3773,6 +3856,7 @@ interface CheckoutLineItem {
|
|
|
3773
3856
|
product: { id: string; name: string; sku: string; images?: ProductImage[]; };
|
|
3774
3857
|
variant?: { id: string; name?: string | null; sku?: string | null; image?: ProductImage | string | null; } | null;
|
|
3775
3858
|
metadata?: Record<string, unknown> | null; // Copied from CartItem.metadata \u2014 buyer customization values
|
|
3859
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 same shape as OrderItem.customizations
|
|
3776
3860
|
}
|
|
3777
3861
|
|
|
3778
3862
|
interface CheckoutAddress {
|
|
@@ -4009,7 +4093,10 @@ interface PaymentProviderConfig {
|
|
|
4009
4093
|
stripeAccountId?: string; // Stripe Connect only
|
|
4010
4094
|
supportedMethods: string[];
|
|
4011
4095
|
testMode: boolean;
|
|
4012
|
-
isDefault: boolean;
|
|
4096
|
+
isDefault: boolean; // only a primary (CREDIT_CARD) is ever the default
|
|
4097
|
+
methodType: 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
4098
|
+
presentation: 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
4099
|
+
isAdditive: boolean; // wallet/alt method shown ALONGSIDE the primary (e.g. PayPal)
|
|
4013
4100
|
}
|
|
4014
4101
|
type PaymentProvider = PaymentProviderConfig;
|
|
4015
4102
|
|
|
@@ -5057,7 +5144,15 @@ const checkout = await client.getCheckout(checkoutId);
|
|
|
5057
5144
|
import { client } from './brainerce';
|
|
5058
5145
|
|
|
5059
5146
|
const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
5060
|
-
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5147
|
+
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5148
|
+
// isDefault, methodType, presentation, isAdditive, clientSdk? }
|
|
5149
|
+
//
|
|
5150
|
+
// methodType splits providers into ONE primary card processor (CREDIT_CARD, the
|
|
5151
|
+
// defaultProvider that settles the order) and additive methods (isAdditive:true,
|
|
5152
|
+
// e.g. PayPal as a WALLET). Render additive methods as express buttons ABOVE the
|
|
5153
|
+
// card form \u2014 they sit alongside the primary, never replace it. Call
|
|
5154
|
+
// createPaymentIntent({ providerId }) with the tapped provider's id. (Wallet-only
|
|
5155
|
+
// store: with no card processor, the wallet becomes defaultProvider and stands alone.)
|
|
5061
5156
|
//
|
|
5062
5157
|
// After createPaymentIntent, the response carries a clientSdk.renderType that
|
|
5063
5158
|
// tells your UI how to render the payment step. The 5 possible values:
|
package/dist/index.js
CHANGED
|
@@ -744,17 +744,19 @@ function getPaymentProvidersSection() {
|
|
|
744
744
|
return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
|
|
745
745
|
|
|
746
746
|
\`\`\`typescript
|
|
747
|
-
const { hasPayments, providers } = await client.getPaymentProviders();
|
|
747
|
+
const { hasPayments, providers, defaultProvider } = await client.getPaymentProviders();
|
|
748
748
|
if (!hasPayments) {
|
|
749
749
|
// Show error: "Payment is not configured for this store"
|
|
750
750
|
return;
|
|
751
751
|
}
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
const
|
|
752
|
+
// Primary vs additive (Shopify-parity): render wallets as express buttons ABOVE the card form.
|
|
753
|
+
const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal (WALLET)
|
|
754
|
+
const primary = defaultProvider && !defaultProvider.isAdditive ? defaultProvider : undefined;
|
|
755
755
|
\`\`\`
|
|
756
756
|
|
|
757
|
-
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault
|
|
757
|
+
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault\`, plus the taxonomy fields \`methodType\` (\`'CREDIT_CARD'\` = the single primary card processor that settles the order; \`'WALLET'\`/other = additive), \`isAdditive\`, and \`presentation\` (\`'card_form'\` | \`'express_button'\` | \u2026).
|
|
758
|
+
|
|
759
|
+
**Primary vs. additive:** there is one primary card processor (the \`defaultProvider\`, \`isAdditive: false\`) plus any additive methods (\`isAdditive: true\`, e.g. PayPal). Render additive methods as accelerated-checkout **express buttons above** the card form \u2014 they sit alongside the primary, never replace it. Create the intent with the tapped provider's \`id\`. (Exception: a wallet-only store has no card processor, so its wallet becomes the \`defaultProvider\` and stands alone.)
|
|
758
760
|
|
|
759
761
|
The payment intent returned from \`createPaymentIntent()\` includes a \`clientSdk\` object whose \`renderType\` tells you exactly how to render \u2014 **branch on THAT, not on the provider name**:
|
|
760
762
|
|
|
@@ -1142,11 +1144,22 @@ client-side overlay needed. Swatch colors are language-agnostic.
|
|
|
1142
1144
|
function ProductDescription({ product }: { product: Product }) {
|
|
1143
1145
|
const content = getDescriptionContent(product);
|
|
1144
1146
|
if (!content) return null;
|
|
1145
|
-
|
|
1147
|
+
// Descriptions are merchant HTML and may contain <video> + host-locked
|
|
1148
|
+
// YouTube/Vimeo <iframe> embeds. NEVER render unsanitized \u2014 sanitize and
|
|
1149
|
+
// allowlist video/source + iframe (restricted to youtube/vimeo hosts).
|
|
1150
|
+
if ('html' in content) {
|
|
1151
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitizeProductHtml(content.html) }} />;
|
|
1152
|
+
}
|
|
1146
1153
|
return <p>{content.text}</p>;
|
|
1147
1154
|
}
|
|
1148
1155
|
\`\`\`
|
|
1149
1156
|
|
|
1157
|
+
**Sanitize descriptions.** \`sanitizeProductHtml\` is the host-locked sanitizer the
|
|
1158
|
+
\`create-brainerce-store\` scaffold ships at \`src/lib/sanitize-html.ts\`. If you roll
|
|
1159
|
+
your own, allow \`video\`/\`source\` and \`iframe\` **restricted** to \`www.youtube.com\`,
|
|
1160
|
+
\`www.youtube-nocookie.com\`, \`player.vimeo.com\` (never arbitrary iframes), and add
|
|
1161
|
+
those hosts to your CSP \`frame-src\` or embeds render blank.
|
|
1162
|
+
|
|
1150
1163
|
### Metafields (Custom Product Fields) \u2014 display on product detail!
|
|
1151
1164
|
|
|
1152
1165
|
**IMPORTANT: Render metafield values based on \`mf.type\`!** Don't just display \`mf.value\` as text for all types.
|
|
@@ -1229,7 +1242,28 @@ const { url } = await client.uploadCustomizationFile(file);
|
|
|
1229
1242
|
// Then use the URL in metadata: metadata: { logo: url }
|
|
1230
1243
|
\`\`\`
|
|
1231
1244
|
|
|
1232
|
-
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues
|
|
1245
|
+
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues\` (array of \`{label, value, swatchColor?, swatchImageUrl?}\`), \`defaultValue\`, \`position\`
|
|
1246
|
+
|
|
1247
|
+
**SELECT / MULTI_SELECT \u2014 use \`option.value\` to submit, \`option.label\` to display:**
|
|
1248
|
+
|
|
1249
|
+
\`\`\`typescript
|
|
1250
|
+
for (const option of field.enumValues ?? []) {
|
|
1251
|
+
// option.value \u2192 what to submit in metadata
|
|
1252
|
+
// option.label \u2192 what to show the customer
|
|
1253
|
+
// option.swatchColor \u2192 optional hex color for color swatches
|
|
1254
|
+
}
|
|
1255
|
+
\`\`\`
|
|
1256
|
+
|
|
1257
|
+
**Display customizations in cart / checkout \u2014 no extra API call needed:**
|
|
1258
|
+
|
|
1259
|
+
CartItem and CheckoutLineItem both include a \`customizations\` object alongside \`metadata\`. Use \`customizations\` for display \u2014 it contains resolved labels, not raw keys.
|
|
1260
|
+
|
|
1261
|
+
\`\`\`typescript
|
|
1262
|
+
// Works for CartItem, CheckoutLineItem, and OrderItem \u2014 same shape
|
|
1263
|
+
const lines = Object.values(item.customizations ?? {})
|
|
1264
|
+
.map(c => \`\${c.label}: \${Array.isArray(c.value) ? c.value.join(', ') : c.value}\`);
|
|
1265
|
+
// e.g. ["Frame color: Gold", "Add-ons: Gift wrap"]
|
|
1266
|
+
\`\`\`
|
|
1233
1267
|
|
|
1234
1268
|
### Downloadable / Digital Products
|
|
1235
1269
|
|
|
@@ -3261,6 +3295,44 @@ by device) and can also be asked of the AI assistant ("how many visits did I get
|
|
|
3261
3295
|
this month and from where?").
|
|
3262
3296
|
`;
|
|
3263
3297
|
}
|
|
3298
|
+
function getShippingAppSection() {
|
|
3299
|
+
return `## App Store Shipping (Shippo)
|
|
3300
|
+
|
|
3301
|
+
Merchants install Shippo from the Brainerce App Store. After connecting their own Shippo account
|
|
3302
|
+
(OAuth), live carrier rates appear automatically at checkout alongside any manually configured
|
|
3303
|
+
zone rates. **Billing goes directly to the merchant's Shippo account \u2014 Brainerce never charges.**
|
|
3304
|
+
|
|
3305
|
+
### Checkout \u2014 live carrier rates
|
|
3306
|
+
|
|
3307
|
+
No SDK changes needed. Once Shippo is installed and configured, the existing
|
|
3308
|
+
\`getShippingRates()\` call returns both manual zone rates and live Shippo quotes:
|
|
3309
|
+
|
|
3310
|
+
\`\`\`typescript
|
|
3311
|
+
const { rates } = await client.setShippingAddress(checkoutId, { ... });
|
|
3312
|
+
// rates may include:
|
|
3313
|
+
// { id: 'shippo:rate_8f123abc', name: 'USPS Priority Mail', price: '8.50', currency: 'USD', source: 'carrier' }
|
|
3314
|
+
\`\`\`
|
|
3315
|
+
|
|
3316
|
+
Rates prefixed \`shippo:\` are live Shippo quotes. Pass the full \`id\` as \`shippingRateId\`
|
|
3317
|
+
when calling \`selectShippingMethod()\`.
|
|
3318
|
+
|
|
3319
|
+
### Admin \u2014 purchase a shipping label
|
|
3320
|
+
|
|
3321
|
+
After the order is placed, the merchant purchases a label by passing the Shippo rate object_id:
|
|
3322
|
+
|
|
3323
|
+
\`\`\`typescript
|
|
3324
|
+
const label = await admin.createShippingLabel(orderId, {
|
|
3325
|
+
shippoRateObjectId: 'rate_8f123456789abcdef',
|
|
3326
|
+
});
|
|
3327
|
+
console.log(label.labelUrl); // PDF ready to print
|
|
3328
|
+
console.log(label.trackingNumber); // auto-populated on the order
|
|
3329
|
+
\`\`\`
|
|
3330
|
+
|
|
3331
|
+
The \`trackingNumber\` is stored on the \`Order\` and returned in \`getMyOrders()\`
|
|
3332
|
+
so customers can track their shipment automatically.
|
|
3333
|
+
|
|
3334
|
+
**SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
|
|
3335
|
+
}
|
|
3264
3336
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
3265
3337
|
const cid = connectionId || "vc_YOUR_CONNECTION_ID";
|
|
3266
3338
|
const cur = currency || "USD";
|
|
@@ -3320,6 +3392,9 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
3320
3392
|
case "analytics":
|
|
3321
3393
|
case "traffic-analytics":
|
|
3322
3394
|
return getStorefrontAnalyticsSection(cid);
|
|
3395
|
+
case "shipping-app":
|
|
3396
|
+
case "shippo":
|
|
3397
|
+
return getShippingAppSection();
|
|
3323
3398
|
case "all":
|
|
3324
3399
|
return [
|
|
3325
3400
|
"# Brainerce SDK \u2014 full topic dump",
|
|
@@ -3602,11 +3677,18 @@ interface ProductCustomizationField {
|
|
|
3602
3677
|
maxLength?: number | null;
|
|
3603
3678
|
minValue?: number | null; // NUMBER bounds
|
|
3604
3679
|
maxValue?: number | null;
|
|
3605
|
-
enumValues?:
|
|
3680
|
+
enumValues?: CustomizationFieldOption[]; // REQUIRED for SELECT / MULTI_SELECT
|
|
3606
3681
|
defaultValue?: string | null;
|
|
3607
3682
|
position: number; // Render order
|
|
3608
3683
|
}
|
|
3609
3684
|
|
|
3685
|
+
interface CustomizationFieldOption {
|
|
3686
|
+
label: string; // Display label (e.g., "Rose Gold")
|
|
3687
|
+
value: string; // Value stored in CartItem.metadata
|
|
3688
|
+
swatchColor?: string | null; // Optional hex color (e.g., "#B76E79")
|
|
3689
|
+
swatchImageUrl?: string | null; // Optional swatch image URL
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3610
3692
|
interface ProductQueryParams {
|
|
3611
3693
|
page?: number;
|
|
3612
3694
|
limit?: number;
|
|
@@ -3700,6 +3782,7 @@ interface CartItem {
|
|
|
3700
3782
|
image?: ProductImage | string | null;
|
|
3701
3783
|
} | null;
|
|
3702
3784
|
metadata?: Record<string, unknown> | null; // Buyer-submitted customization values, keyed by ProductCustomizationField.key
|
|
3785
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 use this to display "Color: Silver" without extra getProduct() call
|
|
3703
3786
|
createdAt: string;
|
|
3704
3787
|
updatedAt: string;
|
|
3705
3788
|
}
|
|
@@ -3799,6 +3882,7 @@ interface CheckoutLineItem {
|
|
|
3799
3882
|
product: { id: string; name: string; sku: string; images?: ProductImage[]; };
|
|
3800
3883
|
variant?: { id: string; name?: string | null; sku?: string | null; image?: ProductImage | string | null; } | null;
|
|
3801
3884
|
metadata?: Record<string, unknown> | null; // Copied from CartItem.metadata \u2014 buyer customization values
|
|
3885
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 same shape as OrderItem.customizations
|
|
3802
3886
|
}
|
|
3803
3887
|
|
|
3804
3888
|
interface CheckoutAddress {
|
|
@@ -4035,7 +4119,10 @@ interface PaymentProviderConfig {
|
|
|
4035
4119
|
stripeAccountId?: string; // Stripe Connect only
|
|
4036
4120
|
supportedMethods: string[];
|
|
4037
4121
|
testMode: boolean;
|
|
4038
|
-
isDefault: boolean;
|
|
4122
|
+
isDefault: boolean; // only a primary (CREDIT_CARD) is ever the default
|
|
4123
|
+
methodType: 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
4124
|
+
presentation: 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
4125
|
+
isAdditive: boolean; // wallet/alt method shown ALONGSIDE the primary (e.g. PayPal)
|
|
4039
4126
|
}
|
|
4040
4127
|
type PaymentProvider = PaymentProviderConfig;
|
|
4041
4128
|
|
|
@@ -5083,7 +5170,15 @@ const checkout = await client.getCheckout(checkoutId);
|
|
|
5083
5170
|
import { client } from './brainerce';
|
|
5084
5171
|
|
|
5085
5172
|
const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
5086
|
-
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5173
|
+
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5174
|
+
// isDefault, methodType, presentation, isAdditive, clientSdk? }
|
|
5175
|
+
//
|
|
5176
|
+
// methodType splits providers into ONE primary card processor (CREDIT_CARD, the
|
|
5177
|
+
// defaultProvider that settles the order) and additive methods (isAdditive:true,
|
|
5178
|
+
// e.g. PayPal as a WALLET). Render additive methods as express buttons ABOVE the
|
|
5179
|
+
// card form \u2014 they sit alongside the primary, never replace it. Call
|
|
5180
|
+
// createPaymentIntent({ providerId }) with the tapped provider's id. (Wallet-only
|
|
5181
|
+
// store: with no card processor, the wallet becomes defaultProvider and stands alone.)
|
|
5087
5182
|
//
|
|
5088
5183
|
// After createPaymentIntent, the response carries a clientSdk.renderType that
|
|
5089
5184
|
// tells your UI how to render the payment step. The 5 possible values:
|
package/dist/index.mjs
CHANGED
|
@@ -712,17 +712,19 @@ function getPaymentProvidersSection() {
|
|
|
712
712
|
return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
|
|
713
713
|
|
|
714
714
|
\`\`\`typescript
|
|
715
|
-
const { hasPayments, providers } = await client.getPaymentProviders();
|
|
715
|
+
const { hasPayments, providers, defaultProvider } = await client.getPaymentProviders();
|
|
716
716
|
if (!hasPayments) {
|
|
717
717
|
// Show error: "Payment is not configured for this store"
|
|
718
718
|
return;
|
|
719
719
|
}
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
const
|
|
720
|
+
// Primary vs additive (Shopify-parity): render wallets as express buttons ABOVE the card form.
|
|
721
|
+
const expressMethods = providers.filter(p => p.isAdditive); // e.g. PayPal (WALLET)
|
|
722
|
+
const primary = defaultProvider && !defaultProvider.isAdditive ? defaultProvider : undefined;
|
|
723
723
|
\`\`\`
|
|
724
724
|
|
|
725
|
-
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault
|
|
725
|
+
Each provider has: \`id\`, \`provider\` (flexible string \u2014 \`'stripe'\`, \`'grow'\`, \`'paypal'\`, \`'cardcom'\`, \`'morning'\`, \`'takbull'\`, \`'sandbox'\`, and future providers), \`name\`, \`publicKey\`, \`stripeAccountId\` (Stripe only), \`supportedMethods\`, \`testMode\`, \`isDefault\`, plus the taxonomy fields \`methodType\` (\`'CREDIT_CARD'\` = the single primary card processor that settles the order; \`'WALLET'\`/other = additive), \`isAdditive\`, and \`presentation\` (\`'card_form'\` | \`'express_button'\` | \u2026).
|
|
726
|
+
|
|
727
|
+
**Primary vs. additive:** there is one primary card processor (the \`defaultProvider\`, \`isAdditive: false\`) plus any additive methods (\`isAdditive: true\`, e.g. PayPal). Render additive methods as accelerated-checkout **express buttons above** the card form \u2014 they sit alongside the primary, never replace it. Create the intent with the tapped provider's \`id\`. (Exception: a wallet-only store has no card processor, so its wallet becomes the \`defaultProvider\` and stands alone.)
|
|
726
728
|
|
|
727
729
|
The payment intent returned from \`createPaymentIntent()\` includes a \`clientSdk\` object whose \`renderType\` tells you exactly how to render \u2014 **branch on THAT, not on the provider name**:
|
|
728
730
|
|
|
@@ -1110,11 +1112,22 @@ client-side overlay needed. Swatch colors are language-agnostic.
|
|
|
1110
1112
|
function ProductDescription({ product }: { product: Product }) {
|
|
1111
1113
|
const content = getDescriptionContent(product);
|
|
1112
1114
|
if (!content) return null;
|
|
1113
|
-
|
|
1115
|
+
// Descriptions are merchant HTML and may contain <video> + host-locked
|
|
1116
|
+
// YouTube/Vimeo <iframe> embeds. NEVER render unsanitized \u2014 sanitize and
|
|
1117
|
+
// allowlist video/source + iframe (restricted to youtube/vimeo hosts).
|
|
1118
|
+
if ('html' in content) {
|
|
1119
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitizeProductHtml(content.html) }} />;
|
|
1120
|
+
}
|
|
1114
1121
|
return <p>{content.text}</p>;
|
|
1115
1122
|
}
|
|
1116
1123
|
\`\`\`
|
|
1117
1124
|
|
|
1125
|
+
**Sanitize descriptions.** \`sanitizeProductHtml\` is the host-locked sanitizer the
|
|
1126
|
+
\`create-brainerce-store\` scaffold ships at \`src/lib/sanitize-html.ts\`. If you roll
|
|
1127
|
+
your own, allow \`video\`/\`source\` and \`iframe\` **restricted** to \`www.youtube.com\`,
|
|
1128
|
+
\`www.youtube-nocookie.com\`, \`player.vimeo.com\` (never arbitrary iframes), and add
|
|
1129
|
+
those hosts to your CSP \`frame-src\` or embeds render blank.
|
|
1130
|
+
|
|
1118
1131
|
### Metafields (Custom Product Fields) \u2014 display on product detail!
|
|
1119
1132
|
|
|
1120
1133
|
**IMPORTANT: Render metafield values based on \`mf.type\`!** Don't just display \`mf.value\` as text for all types.
|
|
@@ -1197,7 +1210,28 @@ const { url } = await client.uploadCustomizationFile(file);
|
|
|
1197
1210
|
// Then use the URL in metadata: metadata: { logo: url }
|
|
1198
1211
|
\`\`\`
|
|
1199
1212
|
|
|
1200
|
-
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues
|
|
1213
|
+
**Customization field properties:** \`key\`, \`name\` (display label), \`type\`, \`required\`, \`minLength\`, \`maxLength\`, \`minValue\`, \`maxValue\`, \`enumValues\` (array of \`{label, value, swatchColor?, swatchImageUrl?}\`), \`defaultValue\`, \`position\`
|
|
1214
|
+
|
|
1215
|
+
**SELECT / MULTI_SELECT \u2014 use \`option.value\` to submit, \`option.label\` to display:**
|
|
1216
|
+
|
|
1217
|
+
\`\`\`typescript
|
|
1218
|
+
for (const option of field.enumValues ?? []) {
|
|
1219
|
+
// option.value \u2192 what to submit in metadata
|
|
1220
|
+
// option.label \u2192 what to show the customer
|
|
1221
|
+
// option.swatchColor \u2192 optional hex color for color swatches
|
|
1222
|
+
}
|
|
1223
|
+
\`\`\`
|
|
1224
|
+
|
|
1225
|
+
**Display customizations in cart / checkout \u2014 no extra API call needed:**
|
|
1226
|
+
|
|
1227
|
+
CartItem and CheckoutLineItem both include a \`customizations\` object alongside \`metadata\`. Use \`customizations\` for display \u2014 it contains resolved labels, not raw keys.
|
|
1228
|
+
|
|
1229
|
+
\`\`\`typescript
|
|
1230
|
+
// Works for CartItem, CheckoutLineItem, and OrderItem \u2014 same shape
|
|
1231
|
+
const lines = Object.values(item.customizations ?? {})
|
|
1232
|
+
.map(c => \`\${c.label}: \${Array.isArray(c.value) ? c.value.join(', ') : c.value}\`);
|
|
1233
|
+
// e.g. ["Frame color: Gold", "Add-ons: Gift wrap"]
|
|
1234
|
+
\`\`\`
|
|
1201
1235
|
|
|
1202
1236
|
### Downloadable / Digital Products
|
|
1203
1237
|
|
|
@@ -3229,6 +3263,44 @@ by device) and can also be asked of the AI assistant ("how many visits did I get
|
|
|
3229
3263
|
this month and from where?").
|
|
3230
3264
|
`;
|
|
3231
3265
|
}
|
|
3266
|
+
function getShippingAppSection() {
|
|
3267
|
+
return `## App Store Shipping (Shippo)
|
|
3268
|
+
|
|
3269
|
+
Merchants install Shippo from the Brainerce App Store. After connecting their own Shippo account
|
|
3270
|
+
(OAuth), live carrier rates appear automatically at checkout alongside any manually configured
|
|
3271
|
+
zone rates. **Billing goes directly to the merchant's Shippo account \u2014 Brainerce never charges.**
|
|
3272
|
+
|
|
3273
|
+
### Checkout \u2014 live carrier rates
|
|
3274
|
+
|
|
3275
|
+
No SDK changes needed. Once Shippo is installed and configured, the existing
|
|
3276
|
+
\`getShippingRates()\` call returns both manual zone rates and live Shippo quotes:
|
|
3277
|
+
|
|
3278
|
+
\`\`\`typescript
|
|
3279
|
+
const { rates } = await client.setShippingAddress(checkoutId, { ... });
|
|
3280
|
+
// rates may include:
|
|
3281
|
+
// { id: 'shippo:rate_8f123abc', name: 'USPS Priority Mail', price: '8.50', currency: 'USD', source: 'carrier' }
|
|
3282
|
+
\`\`\`
|
|
3283
|
+
|
|
3284
|
+
Rates prefixed \`shippo:\` are live Shippo quotes. Pass the full \`id\` as \`shippingRateId\`
|
|
3285
|
+
when calling \`selectShippingMethod()\`.
|
|
3286
|
+
|
|
3287
|
+
### Admin \u2014 purchase a shipping label
|
|
3288
|
+
|
|
3289
|
+
After the order is placed, the merchant purchases a label by passing the Shippo rate object_id:
|
|
3290
|
+
|
|
3291
|
+
\`\`\`typescript
|
|
3292
|
+
const label = await admin.createShippingLabel(orderId, {
|
|
3293
|
+
shippoRateObjectId: 'rate_8f123456789abcdef',
|
|
3294
|
+
});
|
|
3295
|
+
console.log(label.labelUrl); // PDF ready to print
|
|
3296
|
+
console.log(label.trackingNumber); // auto-populated on the order
|
|
3297
|
+
\`\`\`
|
|
3298
|
+
|
|
3299
|
+
The \`trackingNumber\` is stored on the \`Order\` and returned in \`getMyOrders()\`
|
|
3300
|
+
so customers can track their shipment automatically.
|
|
3301
|
+
|
|
3302
|
+
**SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
|
|
3303
|
+
}
|
|
3232
3304
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
3233
3305
|
const cid = connectionId || "vc_YOUR_CONNECTION_ID";
|
|
3234
3306
|
const cur = currency || "USD";
|
|
@@ -3288,6 +3360,9 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
3288
3360
|
case "analytics":
|
|
3289
3361
|
case "traffic-analytics":
|
|
3290
3362
|
return getStorefrontAnalyticsSection(cid);
|
|
3363
|
+
case "shipping-app":
|
|
3364
|
+
case "shippo":
|
|
3365
|
+
return getShippingAppSection();
|
|
3291
3366
|
case "all":
|
|
3292
3367
|
return [
|
|
3293
3368
|
"# Brainerce SDK \u2014 full topic dump",
|
|
@@ -3570,11 +3645,18 @@ interface ProductCustomizationField {
|
|
|
3570
3645
|
maxLength?: number | null;
|
|
3571
3646
|
minValue?: number | null; // NUMBER bounds
|
|
3572
3647
|
maxValue?: number | null;
|
|
3573
|
-
enumValues?:
|
|
3648
|
+
enumValues?: CustomizationFieldOption[]; // REQUIRED for SELECT / MULTI_SELECT
|
|
3574
3649
|
defaultValue?: string | null;
|
|
3575
3650
|
position: number; // Render order
|
|
3576
3651
|
}
|
|
3577
3652
|
|
|
3653
|
+
interface CustomizationFieldOption {
|
|
3654
|
+
label: string; // Display label (e.g., "Rose Gold")
|
|
3655
|
+
value: string; // Value stored in CartItem.metadata
|
|
3656
|
+
swatchColor?: string | null; // Optional hex color (e.g., "#B76E79")
|
|
3657
|
+
swatchImageUrl?: string | null; // Optional swatch image URL
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3578
3660
|
interface ProductQueryParams {
|
|
3579
3661
|
page?: number;
|
|
3580
3662
|
limit?: number;
|
|
@@ -3668,6 +3750,7 @@ interface CartItem {
|
|
|
3668
3750
|
image?: ProductImage | string | null;
|
|
3669
3751
|
} | null;
|
|
3670
3752
|
metadata?: Record<string, unknown> | null; // Buyer-submitted customization values, keyed by ProductCustomizationField.key
|
|
3753
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 use this to display "Color: Silver" without extra getProduct() call
|
|
3671
3754
|
createdAt: string;
|
|
3672
3755
|
updatedAt: string;
|
|
3673
3756
|
}
|
|
@@ -3767,6 +3850,7 @@ interface CheckoutLineItem {
|
|
|
3767
3850
|
product: { id: string; name: string; sku: string; images?: ProductImage[]; };
|
|
3768
3851
|
variant?: { id: string; name?: string | null; sku?: string | null; image?: ProductImage | string | null; } | null;
|
|
3769
3852
|
metadata?: Record<string, unknown> | null; // Copied from CartItem.metadata \u2014 buyer customization values
|
|
3853
|
+
customizations?: Record<string, { label: string; value: string | string[]; type: string }>; // Resolved labels \u2014 same shape as OrderItem.customizations
|
|
3770
3854
|
}
|
|
3771
3855
|
|
|
3772
3856
|
interface CheckoutAddress {
|
|
@@ -4003,7 +4087,10 @@ interface PaymentProviderConfig {
|
|
|
4003
4087
|
stripeAccountId?: string; // Stripe Connect only
|
|
4004
4088
|
supportedMethods: string[];
|
|
4005
4089
|
testMode: boolean;
|
|
4006
|
-
isDefault: boolean;
|
|
4090
|
+
isDefault: boolean; // only a primary (CREDIT_CARD) is ever the default
|
|
4091
|
+
methodType: 'CREDIT_CARD' | 'WALLET' | 'OFFSITE' | 'LOCAL' | 'REDEEMABLE';
|
|
4092
|
+
presentation: 'card_form' | 'express_button' | 'redirect' | 'local_option' | 'redeemable_field';
|
|
4093
|
+
isAdditive: boolean; // wallet/alt method shown ALONGSIDE the primary (e.g. PayPal)
|
|
4007
4094
|
}
|
|
4008
4095
|
type PaymentProvider = PaymentProviderConfig;
|
|
4009
4096
|
|
|
@@ -5051,7 +5138,15 @@ const checkout = await client.getCheckout(checkoutId);
|
|
|
5051
5138
|
import { client } from './brainerce';
|
|
5052
5139
|
|
|
5053
5140
|
const { providers, hasPayments, defaultProvider } = await client.getPaymentProviders();
|
|
5054
|
-
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5141
|
+
// each provider: { id, provider, name, publicKey, supportedMethods, testMode,
|
|
5142
|
+
// isDefault, methodType, presentation, isAdditive, clientSdk? }
|
|
5143
|
+
//
|
|
5144
|
+
// methodType splits providers into ONE primary card processor (CREDIT_CARD, the
|
|
5145
|
+
// defaultProvider that settles the order) and additive methods (isAdditive:true,
|
|
5146
|
+
// e.g. PayPal as a WALLET). Render additive methods as express buttons ABOVE the
|
|
5147
|
+
// card form \u2014 they sit alongside the primary, never replace it. Call
|
|
5148
|
+
// createPaymentIntent({ providerId }) with the tapped provider's id. (Wallet-only
|
|
5149
|
+
// store: with no card processor, the wallet becomes defaultProvider and stands alone.)
|
|
5055
5150
|
//
|
|
5056
5151
|
// After createPaymentIntent, the response carries a clientSdk.renderType that
|
|
5057
5152
|
// tells your UI how to render the payment step. The 5 possible values:
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainerce/mcp-server",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.1",
|
|
4
|
+
"packageManager": "pnpm@9.0.0",
|
|
4
5
|
"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
6
|
"bin": {
|
|
6
7
|
"brainerce-mcp": "dist/bin/stdio.js"
|