chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import { z, IntegrationDefinitionProps } from '@botpress/sdk'
|
||||
import { storefrontProductSchema, collectionSchema, cartSchema, pageInfoSchema } from './schemas'
|
||||
|
||||
export const actions = {
|
||||
searchProducts: {
|
||||
title: 'Search Products',
|
||||
description: 'Search public-facing products via the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
query: z.string().title('Search Query').describe('Search term to find products'),
|
||||
first: z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(250)
|
||||
.default(50)
|
||||
.optional()
|
||||
.title('Limit')
|
||||
.describe('Number of products to return'),
|
||||
after: z.string().optional().title('After Cursor').describe('Cursor for pagination'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
products: z.array(storefrontProductSchema).title('Products').describe('List of matching products'),
|
||||
pageInfo: pageInfoSchema.title('Page Info').describe('Pagination info'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
getProduct: {
|
||||
title: 'Get Product',
|
||||
description: 'Get a single product from the Shopify Storefront API by handle or ID',
|
||||
input: {
|
||||
schema: z.object({
|
||||
handle: z.string().optional().title('Handle').describe('The URL-friendly handle of the product'),
|
||||
productId: z
|
||||
.string()
|
||||
.optional()
|
||||
.title('Product ID')
|
||||
.describe('The Storefront GID of the product (e.g. gid://shopify/Product/12345)'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
product: storefrontProductSchema.title('Product').describe('The product details'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
listCollections: {
|
||||
title: 'List Collections',
|
||||
description: 'List collections from the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
first: z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(250)
|
||||
.default(50)
|
||||
.optional()
|
||||
.title('Limit')
|
||||
.describe('Number of collections to return'),
|
||||
after: z.string().optional().title('After Cursor').describe('Cursor for pagination'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
collections: z.array(collectionSchema).title('Collections').describe('List of collections'),
|
||||
pageInfo: pageInfoSchema.title('Page Info').describe('Pagination info'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
getCollection: {
|
||||
title: 'Get Collection',
|
||||
description: 'Get a single collection with its products from the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
handle: z.string().optional().title('Handle').describe('The URL-friendly handle of the collection'),
|
||||
collectionId: z
|
||||
.string()
|
||||
.optional()
|
||||
.title('Collection ID')
|
||||
.describe('The Storefront GID of the collection (e.g. gid://shopify/Collection/12345)'),
|
||||
productsFirst: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(250)
|
||||
.default(50)
|
||||
.optional()
|
||||
.title('Products Limit')
|
||||
.describe('Number of products to include'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
collection: collectionSchema.title('Collection').describe('The collection details'),
|
||||
products: z.array(storefrontProductSchema).title('Products').describe('Products in the collection'),
|
||||
pageInfo: pageInfoSchema.title('Page Info').describe('Pagination info for products'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
createCart: {
|
||||
title: 'Create Cart',
|
||||
description: 'Create a new cart via the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
lines: z
|
||||
.array(
|
||||
z.object({
|
||||
merchandiseId: z.string().title('Merchandise ID').describe('The Storefront GID of the product variant'),
|
||||
quantity: z.number().min(1).title('Quantity').describe('The quantity to add'),
|
||||
})
|
||||
)
|
||||
.title('Lines')
|
||||
.describe('Cart line items to add'),
|
||||
buyerEmail: z.string().optional().title('Buyer Email').describe('Email of the buyer'),
|
||||
countryCode: z
|
||||
.string()
|
||||
.optional()
|
||||
.title('Country Code')
|
||||
.describe('ISO 3166-1 alpha-2 country code for the buyer'),
|
||||
discountCodes: z.array(z.string()).optional().title('Discount Codes').describe('Discount codes to apply'),
|
||||
note: z.string().optional().title('Note').describe('A note for the cart'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
cart: cartSchema.title('Cart').describe('The created cart'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
getCart: {
|
||||
title: 'Get Cart',
|
||||
description: 'Retrieve a cart by ID from the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
cartId: z.string().title('Cart ID').describe('The Storefront GID of the cart'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
cart: cartSchema.title('Cart').describe('The cart details'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
addCartLines: {
|
||||
title: 'Add Cart Lines',
|
||||
description: 'Add line items to an existing cart via the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
cartId: z.string().title('Cart ID').describe('The Storefront GID of the cart'),
|
||||
lines: z
|
||||
.array(
|
||||
z.object({
|
||||
merchandiseId: z.string().title('Merchandise ID').describe('The Storefront GID of the product variant'),
|
||||
quantity: z.number().min(1).title('Quantity').describe('The quantity to add'),
|
||||
})
|
||||
)
|
||||
.title('Lines')
|
||||
.describe('Line items to add to the cart'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
cart: cartSchema.title('Cart').describe('The updated cart'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
applyCartDiscount: {
|
||||
title: 'Apply Cart Discount',
|
||||
description: 'Apply or update discount codes on a cart via the Shopify Storefront API',
|
||||
input: {
|
||||
schema: z.object({
|
||||
cartId: z.string().title('Cart ID').describe('The Storefront GID of the cart'),
|
||||
discountCodes: z.array(z.string()).title('Discount Codes').describe('Discount codes to apply to the cart'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
cart: cartSchema.title('Cart').describe('The updated cart'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
} satisfies IntegrationDefinitionProps['actions']
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
|
||||
export { actions } from './actions'
|
||||
export { states } from './states'
|
||||
export * as schemas from './schemas'
|
||||
|
||||
export const configuration = {
|
||||
identifier: {
|
||||
linkTemplateScript: 'linkTemplate.vrl',
|
||||
},
|
||||
schema: z.object({}),
|
||||
}
|
||||
|
||||
export const secrets = {
|
||||
SHOPIFY_CLIENT_ID: { description: 'The Client ID of the Shopify app' },
|
||||
SHOPIFY_CLIENT_SECRET: { description: 'The Client Secret of the Shopify app' },
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
|
||||
export const pageInfoSchema = z.object({
|
||||
hasNextPage: z.boolean().title('Has Next Page').describe('Whether there are more results'),
|
||||
endCursor: z.string().optional().title('End Cursor').describe('Cursor for the next page'),
|
||||
})
|
||||
|
||||
export const moneySchema = z.object({
|
||||
amount: z.string().title('Amount').describe('Decimal money amount'),
|
||||
currencyCode: z.string().title('Currency Code').describe('ISO 4217 currency code'),
|
||||
})
|
||||
|
||||
export const storefrontVariantSchema = z.object({
|
||||
id: z.string().title('Variant ID').describe(`The Storefront GID of the product
|
||||
variant. This is the unique identifier for a variant within a product. This
|
||||
id, in the format "gid://shopify/ProductVariant/{number}", can be used for
|
||||
adding a product to a cart`),
|
||||
title: z.string().title('Title').describe('The title of the variant'),
|
||||
availableForSale: z.boolean().title('Available for Sale').describe('Whether the variant is available for purchase'),
|
||||
price: moneySchema.title('Price').describe('The price of the variant'),
|
||||
})
|
||||
|
||||
export const storefrontProductSchema = z.object({
|
||||
id: z.string().title('Product ID').describe(`The Storefront GID of the
|
||||
product. Don't offer the user to add products to the chart. Only offer the
|
||||
user to add specific variants to the cart`),
|
||||
title: z.string().title('Title').describe('The title of the product'),
|
||||
handle: z.string().title('Handle').describe('The URL-friendly handle of the product'),
|
||||
description: z.string().optional().title('Description').describe('Plain-text description of the product'),
|
||||
productType: z.string().optional().title('Product Type').describe('The product type'),
|
||||
vendor: z.string().optional().title('Vendor').describe('The vendor of the product'),
|
||||
availableForSale: z.boolean().title('Available for Sale').describe('Whether the product is available for purchase'),
|
||||
priceRange: z
|
||||
.object({
|
||||
minVariantPrice: moneySchema.title('Min Variant Price'),
|
||||
maxVariantPrice: moneySchema.title('Max Variant Price'),
|
||||
})
|
||||
.optional()
|
||||
.title('Price Range')
|
||||
.describe('The price range across all variants'),
|
||||
variants: z.array(storefrontVariantSchema).title('Variants').describe('The product variants'),
|
||||
imageUrl: z.string().optional().title('Image URL').describe('URL of the primary product image'),
|
||||
storefrontUrl: z
|
||||
.string()
|
||||
.title('Storefront URL')
|
||||
.describe("Canonical storefront URL on the shop's myshopify.com domain — always populated"),
|
||||
onlineStoreUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.title('Online Store URL')
|
||||
.describe(
|
||||
'Published Online Store URL (may use a custom domain). Undefined if the product is not published to the Online Store sales channel'
|
||||
),
|
||||
})
|
||||
|
||||
export const collectionSchema = z.object({
|
||||
id: z.string().title('Collection ID').describe('The Storefront GID of the collection'),
|
||||
title: z.string().title('Title').describe('The title of the collection'),
|
||||
handle: z.string().title('Handle').describe(`The URL-friendly handle of the
|
||||
collection. Do not offer to add a collection to the cart. Collections are
|
||||
groups of products. Ask the user if they want to see the procuts belonging to
|
||||
a collection`),
|
||||
description: z.string().optional().title('Description').describe('Plain-text description of the collection'),
|
||||
imageUrl: z.string().optional().title('Image URL').describe('URL of the collection image'),
|
||||
})
|
||||
|
||||
export const cartLineSchema = z.object({
|
||||
lineId: z.string().title('Line ID').describe('The GID of the cart line'),
|
||||
quantity: z.number().title('Quantity').describe('The quantity of this line item'),
|
||||
merchandiseId: z.string().title('Merchandise ID').describe('The GID of the product variant'),
|
||||
title: z.string().title('Title').describe('The product title'),
|
||||
variantTitle: z.string().optional().title('Variant Title').describe('The variant title'),
|
||||
price: moneySchema.title('Price').describe('The unit price of the line item'),
|
||||
})
|
||||
|
||||
export const cartSchema = z.object({
|
||||
cartId: z.string().title('Cart ID').describe('The Storefront GID of the cart'),
|
||||
checkoutUrl: z
|
||||
.string()
|
||||
.title('Checkout URL')
|
||||
.describe(
|
||||
`Final checkout URL returned by Shopify, in the form
|
||||
"https://{shop}.myshopify.com/checkouts/cn/{token}". Use this value
|
||||
verbatim when linking the buyer to checkout - do not modify it, shorten
|
||||
it, replace it, or construct your own URL from the shop domain. When
|
||||
rendering an affordance, it must be a link (not a button) targeting
|
||||
exactly this string.`
|
||||
),
|
||||
totalQuantity: z.number().title('Total Quantity').describe('Total number of items in the cart'),
|
||||
totalAmount: moneySchema.title('Total Amount').describe('The estimated total cost'),
|
||||
subtotalAmount: moneySchema.title('Subtotal Amount').describe('The estimated subtotal before taxes and shipping'),
|
||||
lines: z.array(cartLineSchema).title('Lines').describe('The cart line items'),
|
||||
discountCodes: z
|
||||
.array(
|
||||
z.object({
|
||||
code: z.string().title('Code').describe('The discount code'),
|
||||
applicable: z.boolean().title('Applicable').describe('Whether the discount code is currently applicable'),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.title('Discount Codes')
|
||||
.describe('Applied discount codes'),
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z, IntegrationDefinitionProps } from '@botpress/sdk'
|
||||
|
||||
// Admin access token is intentionally not persisted: Shopify expiring offline tokens
|
||||
// (April 2026) make stored values useless after 60 minutes. The token is used in-memory
|
||||
// inside the OAuth wizard to provision the Storefront API token, then discarded.
|
||||
// If a future feature needs admin access, run it inside the OAuth callback or trigger
|
||||
// a re-auth wizard step.
|
||||
export const states = {
|
||||
credentials: {
|
||||
type: 'integration',
|
||||
schema: z.object({
|
||||
shopDomain: z.string().optional().title('Shop Domain').describe('The myshopify.com domain of the store'),
|
||||
storefrontAccessToken: z
|
||||
.string()
|
||||
.optional()
|
||||
.title('Storefront Access Token')
|
||||
.describe('Storefront API access token used to authenticate Storefront GraphQL requests'),
|
||||
}),
|
||||
},
|
||||
} satisfies IntegrationDefinitionProps['states']
|
||||
Reference in New Issue
Block a user