venveo / craft-bigcommerce
BigCommerce for Craft CMS
Requires
- php: ^8.0.2
- craftcms/cms: ^4.3.0
- shopify/shopify-api: ^4.1
Requires (Dev)
- craftcms/ecs: dev-main
- craftcms/phpstan: dev-main
- craftcms/rector: dev-main
- craftcms/redactor: *
- vlucas/phpdotenv: ^3.4
This package is auto-updated.
Last update: 2024-09-17 16:25:37 UTC
README
BigCommerce for Craft CMS
通过将BigCommerce BigCommerce产品集成到Craft CMS,构建一个内容驱动的店面。
主题
安装
BigCommerce插件需要Craft CMS 4.0.0或更高版本。
要安装插件,请从Craft项目访问插件商店,或按照以下说明操作。
-
在新终端中导航到您的Craft项目
cd /path/to/project
-
使用Composer要求包
composer require venveo/craft-bigcommerce -w
-
在控制面板中,转到 设置 → 插件,然后单击BigCommerce的“安装”按钮,或运行
php craft plugin/install bigcommerce
创建BigCommerce API帐户
注意
您必须是商店的所有者才能生成API帐户。
登录到您的BigCommerce管理面板,然后转到设置 → API帐户 → 创建API帐户。按照以下步骤操作
-
令牌类型:V2/V3 API令牌
-
名称:一个描述性的名称。为不同环境使用不同的凭据是个好主意 - 例如,“[Prod] Craft CMS”
-
OAuth作用域:以下作用域对于插件正确运行是必需的
- 内容:
只读
- 结账内容:
无
- 客户:
修改
- 客户登录:
登录
- 信息与设置:
修改
- 营销:
修改
- 订单:
修改
- 订单交易:
只读
- 创建支付:
无
- 获取支付方式:
无
- 存储支付工具:
无
- 产品:
修改
- 主题:
只读
- 购物车:
修改
- 结账:
修改
- 网站和路由:
修改
- 渠道设置:
修改
- 渠道列表:
修改
- 店面API令牌:
管理
- 店面API客户伪装令牌:
管理
- 商店日志:
只读
- 商店库存:
只读
- 内容:
点击保存。
- 客户端ID:显示并复制此值到您的
.env
文件中,作为BC_CLIENT_ID
。 - 客户端密钥:显示并复制此值到您的
.env
文件中,作为BC_CLIENT_SECRET
。 - 访问令牌:显示并复制此值到您的
.env
文件中,作为BC_ACCESS_TOKEN
。 - 商店散列值:此值位于您的商店URL中:
https://store-**store hash**.mybigcommerce.com/
将此值复制到您的.env
文件中,作为BC_STORE_HASH
。 - 销售渠道:如果您使用的是默认的BigCommerce店面,此值是
1
;否则,在BigCommerce的渠道管理器中找到您的销售渠道ID。将此值复制到您的.env
文件中,作为BC_SALES_CHANNEL
。
连接插件
现在您已经拥有了自定义应用的凭证,是时候将它们添加到Craft中。
- 访问项目控制面板中的 BigCommerce → 设置 屏幕。
- 使用特殊的 配置语法 将四个环境变量分配到相应的设置中。
- API客户端ID:
$BC_CLIENT_ID
- API密钥:
$BC_CLIENT_SECRET
- 访问令牌:
$BC_ACCESS_TOKEN
- 销售渠道:
$BC_SALES_CHANNEL
- API客户端ID:
- 点击 保存。
设置Webhooks
将凭证添加到Craft后,控制面板中的BigCommerce部分将出现一个新的 Webhooks 选项卡。
在Webhooks屏幕上点击 创建 以将所需的webhooks添加到BigCommerce。插件将使用您刚刚配置的凭证执行此操作——这同时也充当了一个初始通信测试。
警告
您需要为部署插件的每个环境添加webhooks,因为每个webhook都与特定的URL相关联。
注意
如果您需要在开发中测试实时同步,我们建议使用 ngrok 创建到您本地环境的隧道。DDEV使这变得简单,使用 ddev share 命令。在webhook创建屏幕上的 基本URL覆盖 选项中提供此值。完成测试后,请务必删除测试webhooks!
产品元素
您BigCommerce商店中的产品在Craft中表示为产品 元素,可以在控制面板中通过访问 BigCommerce → 产品 来找到。
同步
将通过 webhooks 自动创建、更新和删除产品——但是Craft直到发生变化才知道产品。
配置插件后,通过命令行执行初始同步
php craft bigcommerce/sync/products
注意
您还可以使用控制面板中的 BigCommerce同步 工具同步产品。请注意,大型商店(产品超过一百个)可能需要一些时间来同步,并且可能会迅速耗尽PHP的 max_execution_time。
原生属性
除了标准的元素属性(如 id
、title
和 status
)之外,每个BigCommerce产品元素都包含以下映射到其规范 BigCommerce产品资源 的映射。
所有这些属性在您的工作 模板 中都是可用的。
注意
有关这些属性可能返回的值类型的更多信息,请参阅BigCommerce文档中的产品资源。
方法
产品元素有几个可能在您的 模板 中找到的有用的方法。
Product::getVariants()
返回属于产品的 变体。
{% set variants = product.getVariants() %} <select name="variantId"> {% for variant in variants %} <option value="{{ variant.id }}">{{ variant.sku }}</option> {% endfor %} </select>
Product::getDefaultVariant()
获取产品属于的第一个/默认 变体 的快捷方式。
{% set products = craft.bigcommerceProducts.all() %} <ul> {% for product in products %} {% set defaultVariant = product.getDefaultVariant() %} <li> <a href="{{ product.url }}">{{ product.title }}</a> <span>{{ defaultVariant.price|currency }}</span> </li> {% endfor %} </ul>
Product::getCheapestVariant()
获取产品属于的最低价 变体 的快捷方式。
{% set cheapestVariant = product.getCheapestVariant() %} Starting at {{ cheapestVariant.price|currency }}!
Product::getBigCommerceUrl()
{# Get a link to the product’s page on BigCommerce: #} <a href="{{ product.getBigCommerceUrl() }}">View on our store</a> {# Link to a product with a specific variant pre-selected: #} <a href="{{ product.getBigCommerceUrl({ variant: variant.id }) }}">Buy now</a>
Product::getBigCommerceEditUrl()
对于您的管理员,您甚至可以直接链接到BigCommerce管理员
{# Assuming you’ve created a custom group for BigCommerce admin: #} {% if currentUser and currentUser.isInGroup('clerks') %} <a href="{{ product.getBigCommerceEditUrl() }}">Edit product on BigCommerce</a> {% endif %}
自定义字段
从BigCommerce同步的产品具有专用字段布局,这意味着它们支持Craft的全套内容工具。
可以通过访问 BigCommerce → 设置 → 产品 来编辑产品字段布局,然后滚动到 字段布局。
路由
您可以为同步的产品设置自己的站内URL。要设置URI格式(以及当请求产品URL时将加载的模板),请转到 BigCommerce → 设置 → 产品。
如果您希望客户在BigCommerce上查看单个产品,请在设置页面清除 产品URI格式 字段,并在模板中使用 product.bigcommerceUrl
而不是 product.url
。
注意
Craft中的状态通常是多个属性的合成。例如,具有 待处理 状态的条目仅表示它启用
并且 有一个未来的postDate
。
查询产品
产品可以像系统中的任何其他元素一样进行查询。
新的查询从 craft.bigcommerceProducts
工厂函数开始
{% set products = craft.bigcommerceProducts.all() %}
查询参数
除了 Craft的标准集 外,还支持以下元素查询参数。
注意 存储为JSON的字段(如
options
和metadata
)只能作为纯文本查询。如果您需要进行高级组织或筛选,我们建议在产品的 字段布局 中使用自定义分类或标签字段。
bigcommerceId
按BigCommerce产品ID进行筛选。
{# Watch out—these aren't the same as element IDs! #} {% set singleProduct = craft.bigcommerceProducts .bigcommerceId(123456789) .one() %}
handle
通过产品在BigCommerce中的handle进行查询。
{% set product = craft.bigcommerceProducts .handle('worlds-tallest-socks') .all() %}
🚨 这不是获取特定产品的可靠方法,因为值可能在同步期间更改。如果您需要一个产品的永久引用,请考虑使用BigCommerce的 产品字段。
productType
通过BigCommerce中的“类型”查找产品。
{% set upSells = craft.bigcommerceProducts .productType(['physical', 'digital']) .all() %}
publishedScope
仅显示发布到匹配销售渠道的产品。
{# Only web-ready products: #} {% set webProducts = craft.bigcommerceProducts .publishedScope('web') .all() %} {# Everything: #} {% set inStoreProducts = craft.bigcommerceProducts .publishedScope('global') .all() %}
tags
标签以逗号分隔的列表形式存储。您可能可以通过使用 .search() 参数 获得更好的结果。
{# Find products whose tags include the term in any position, with variations on casing: #} {% set clogs = craft.bigcommerceProducts .tags(['*clog*', '*Clog*']) .all() %}
vendor
通过BigCommerce中的供应商信息进行筛选。
{# Find products with a vendor matching either option: #} {% set fancyBags = craft.bigcommerceProducts .vendor(['Louis Vuitton', 'Jansport']) .all() %}
images
图片以JSON blob的形式存储,并且仅用于与已加载产品一起在模板中使用。直接通过 图片资源 值进行筛选可能很困难且不可预测——您可能可以通过使用 .search() 参数 获得更好的结果。
{# Find products that have an image resource mentioning "stripes": #} {% set clogs = craft.bigcommerceProducts .images('*stripes*') .all() %}
options
选项 以JSON blob的形式存储,并且仅用于与已加载产品一起在模板中使用。您可能可以通过使用 .search() 参数 获得更好的结果。
{# Find products that use a "color" option: #} {% set clogs = craft.bigcommerceProducts .options('"Color"') .all() %}
上述内容包含引号("
)字面量,因为它是尝试在JSON数组中定位特定的键,该键始终被双引号包围。
模板化
产品数据
在Twig中,产品表现得就像任何其他元素一样。一旦您通过查询(或在其模板中对一个产品的引用)加载了一个产品,您就可以输出其原生的BigCommerce属性和自定义字段数据。
注意
一些属性以JSON格式存储,这限制了嵌套属性的类型。因此,处理日期可能会稍微困难一些。
{# Standard element title: #} {{ product.title }} {# -> Root Beer #} {# BigCommerce HTML content: #} {{ product.bodyHtml|raw }} {# -> <p>...</p> #} {# Tags, as list: #} {{ product.tags|join(', ') }} {# -> sweet, spicy, herbal #} {# Tags, as filter links: #} {% for tag in tags %} <a href="{{ siteUrl('products', { tag: tag }) }}">{{ tag|title }}</a> {# -> <a href="https://mydomain.com/products?tag=herbal">Herbal</a> #} {% endfor %} {# Images: #} {% for image in product.images %} <img src="{{ image.src }}" alt="{{ image.alt }}"> {# -> <img src="https://cdn.bigcommerce.com/..." alt="Bubbly Soda"> #} {% endfor %} {# Variants: #} <select name="variantId"> {% for variant in product.getVariants() %} <option value="{{ variant.id }}">{{ variant.title }} ({{ variant.price|currency }})</option> {% endfor %} </select>
变体和定价
尽管BigCommerce UI可能暗示了不同,但产品没有价格——相反,每个产品至少有一个变体。
您可以通过调用product.getVariants()
获取一个产品的变体对象数组。产品元素还提供了获取默认和最便宜变体的便利方法,但您可以使用Craft的collect()
Twig函数以任何您喜欢的方式进行筛选。
与产品不同,Craft中的变体...
- ...与API返回的完全一致;
- ...使用BigCommerce在属性名称中使用下划线而不是暴露驼峰式命名等效;
- …是普通的关联数组;
- …没有自己的方法;
一旦您获得了一个变体的引用,您就可以输出其属性
{% set defaultVariant = product.getDefaultVariant() %} {{ defaultVariant.price|currency }}
注意
内置的currency
Twig过滤器是一种格式化货币值的好方法。
使用选项
选项是BigCommerce在多个轴向上区分变体的方式。
如果您想允许客户从选项中选择而不是直接选择变体,您将需要解析给定组合指向哪个变体。
表单
<form id="add-to-cart" method="post" action="{{ craft.bigcommerce.store.getUrl('cart/add') }}"> {# Create a hidden input to send the resolved variant ID to BigCommerce: #} {{ hiddenInput('id', null, { id: 'variant', data: { variants: product.variants, }, }) }} {# Create a dropdown for each set of options: #} {% for option in product.options %} <label> {{ option.name }} {# The dropdown includes the option’s `position`, which helps match it with the variant, later: #} <select data-option="{{ option.position }}"> {% for val in option.values %} <option value="{{ val }}">{{ val }}</option> {% endfor %} </select> </label> {% endfor %} <button>Add to Cart</button> </form>
脚本
以下代码可以添加到{% js %}
标签中,与表单代码一起使用。
// Store references to <form> elements: const $form = document.getElementById("add-to-cart"); const $variantInput = document.getElementById("variant"); const $optionInputs = document.querySelectorAll("[data-option]"); // Create a helper function to test a map of options against known variants: const findVariant = (options) => { const variants = JSON.parse($variantInput.dataset.variants); // Use labels for the inner and outer loop so we can break out early: variant: for (const v in variants) { option: for (const o in options) { // Option values are stored as `option1`, `option2`, or `option3` on each variant: if (variants[v][`option${o}`] !== options[o]) { // Didn't match one of the options? Bail: continue variant; } } // Nice, all options matched this variant! Return it: return variants[v]; } }; // Listen for change events on the form, rather than the individual option menus: $form.addEventListener("change", (e) => { const selectedOptions = {}; // Loop over option menus and build an object of selected values: $optionInputs.forEach(($input) => { // Add the value under the "position" key selectedOptions[$input.dataset.option] = $input.value; }); // Use our helper function to resolve a variant: const variant = findVariant(selectedOptions); if (!variant) { console.warn("No variant exists for options:", selectedOptions); return; } // Assign the resolved variant’s ID to the hidden input: $variantInput.value = variant.id; }); // Trigger an initial `change` event to simulate a selection: $form.dispatchEvent(new Event("change"));
购物车
您的客户可以直接从您的Craft网站将产品添加到他们的购物车中
{% set product = craft.bigcommerceProducts.one() %} <form action="{{ craft.bigcommerce.store.getUrl('cart/add') }}" method="post"> <select name="id"> {% for variant in product.getVariants() %} <option value="{{ variant.id }}">{{ variant.title }}</option> {% endfor %} </select> {{ hiddenInput('qty', 1) }} <button>Add to Cart</button> </form>
JS Buy SDK
目前还没有以原生的方式支持购物车管理和结账。
然而,BigCommerce维护着Javascript Buy SDK,作为一种与他们的店面API交互以创建完全定制的购物体验的手段。
注意
使用店面API需要一个不同的访问密钥,并假设您已经将您的产品发布到了店面应用的销售渠道。您的公共店面API令牌可以与您的其他凭证一起存储在
.env
中,并在前端使用{{ getenv('...') }}
Twig助手输出,或者直接嵌入到JavaScript包中。确保您的其他秘密安全!这是唯一可以公开的。
该插件不对您如何在前端使用产品数据做出任何假设,但提供了连接SDK所需的工具。以下是如何在Twig中渲染产品列表并连接自定义客户端购物车的示例...
商店模板:templates/shop.twig
{# Include the Buy SDK on this page: #} {% do view.registerJsFile('https://sdks.bigcommercecdn.com/js-buy-sdk/v2/latest/index.umd.min.js') %} {# Register your own script file (see “Custom Script,” below): #} {% do view.registerJsFile('/assets/js/shop.js') %} {# Load some products: #} {% set products = craft.bigcommerceProducts().all() %} <ul> {% for product in products %} {# For now, we’re only handling a single variant: #} {% set defaultVariant = product.getVariants()|first %} <li> {{ product.title }} <button class="buy-button" data-default-variant-id="{{ defaultVariant.id }}">Add to Cart</button> </li> {% endfor %} </ul>
自定义脚本:assets/js/shop.js
// Initialize a client: const client = BigCommerceBuy.buildClient({ domain: "my-storefront.mybigcommerce.com", storefrontAccessToken: "...", }); // Create a simple logger for the cart’s state: const logCart = (c) => { console.log(c.lineItems); console.log(`Checkout URL: ${c.webUrl}`); }; // Create a cart or “checkout” (or perhaps load one from `localStorage`): client.checkout.create().then((checkout) => { const $buyButtons = document.querySelectorAll(".buy-button"); // Add a listener to each button: $buyButtons.forEach(($b) => { $b.addEventListener("click", (e) => { // Read the variant ID off the product: client.checkout .addLineItems(checkout.id, [ { // Build the Storefront-style resource identifier: variantId: `gid://bigcommerce/ProductVariant/${$b.dataset.defaultVariantId}`, quantity: 1, }, ]) .then(logCart); // <- Log the changes! }); }); });
购买按钮JS
上述示例可以用购买按钮JS简化,它提供了一些现成的UI组件,如功能齐全的购物车。原理是相同的。
- 在BigCommerce中通过适当的销售渠道提供产品;
- 在前端输出同步的产品数据;
- 根据步骤#2中的BigCommerce特定标识符,初始化、附加或触发SDK功能以响应事件;
结账
虽然存在创建定制购物体验的解决方案,但结账总是在BigCommerce平台上进行。这与其说是技术限制,不如说是政策——BigCommerce的结账流程快速、可靠、安全,且许多购物者都很熟悉。
如果您希望客户的整个旅程都在站内进行,我们鼓励您尝试我们的强大电子商务插件Commerce。
辅助工具
除了产品元素方法外,插件通过craft.bigcommerce
将API暴露给Twig。
API服务
警告
在Twig块渲染中使用API调用,并根据流量,可能会因为速率限制而导致超时和/或失败。考虑使用带有键和特定过期时间的{% cache %}
标签以避免每次渲染模板时都发出请求。{% cache using key "bigcommerce:collections" for 10 minutes %} {# API calls + output... #} {% endcache %}
通过craft.bigcommerce.api
向BigCommerce管理员API发送请求。
{% set req = craft.bigcommerce.api.get('custom_collections') %} {% set collections = req.response.custom_collections %}
每个API资源的模式将不同。有关更多信息,请参阅BigCommerce API文档。
商店服务
通过craft.bigcommerce.store
提供了一个简单的URL生成器。您可能在上面的购物车示例中注意到它,但它比这更灵活!
{# Create a link to add a product/variant to the cart: #} {{ tag('a', { href: craft.bigcommerce.store.getUrl('cart/add', { id: variant.id }), text: 'Add to Cart', target: '_blank', }) }}
可以将相同的参数传递给产品元素的getBigCommerceUrl()
方法。
{% for variant in product.getVariants() %} <a href="{{ product.getBigCommerceUrl({ id: variant.id }) }}">{{ variant.title }}</a> {% endfor %}
产品字段
插件提供了一个BigCommerce产品字段,它使用熟悉的关系字段UI,允许作者选择产品元素。
使用BigCommerce产品字段定义的关系在内部使用稳定的元素ID。当BigCommerce产品被存档或删除时,相应的元素也会在Craft中更新,并且自然地从查询结果中过滤出来,包括通过BigCommerce产品字段明确附加的元素。
注意
升级?查看有关迁移的说明以获取更多信息。
从v2.x迁移
如果您正在将Craft 3项目升级到Craft 4,并且有现有的“BigCommerce产品”字段,您需要向插件展示如何将存储为JSON数组的纯BigCommerce ID转换为元素ID,在Craft的关系系统中。
警告
在开始字段数据迁移之前,请确保您已经同步了您的产品目录。
您可以安全地从您的composer.json
中删除旧的插件包(nmaier95/bigcommerce-product-fetcher
)——但不要使用控制面板来卸载它。我们希望字段的数据保留下来,但不需要旧的字段类来与其一起工作。
注意
在此过程中,您可能会在字段布局中看到“缺少字段”——这是正常的!数据仍然在那里。
对于项目中每个遗留的BigCommerce产品字段,执行以下操作:
- 创建一个新的BigCommerce产品字段,为其提供一个新的处理程序和名称;
- 将字段添加到任何出现旧字段的位置的布局中;
重新保存数据
运行以下命令(替换适当的值),为在步骤#2中添加字段的地方执行
-
resave/entries
→ 与字段布局相关联的元素类型的重新保存命令; -
mySectionHandle
→ 用于应用于您正在重新保存的元素类型所需应用的任何标准的占位符; -
oldBigCommerceField
→ 从旧插件版本中的字段处理程序(用于--to
参数闭包内部); -
newBigCommerceField
→ 在步骤#1中创建的新字段处理程序;php craft resave/entries \ --section=mySectionHandle \ --set=newBigCommerceField \ --to="fn(\$entry) => collect(json_decode(\$entry->oldBigCommerceField))->map(fn (\$id) => \venveo\bigcommerce\Plugin::getInstance()->getProducts()->getProductIdByBigCommerceId(\$id))->unique()->all()"
更新模板
在内容重新保存后,更新您的模板
之前
在v2.x中,您需要在模板中查找产品详情
{# Product references were stored as a list of IDs: #} {% set productIds = entry.oldBigCommerceField %} <ul> {% for productId in productIds %} {# Query BigCommerce API for Product using ID: #} {% set bigcommerceProduct = craft.bigcommerce.getProductById({ id: productId }) %} <li>{{ product.productType }}: {{ product.title }}</li> {% endfor %} </ul>
之后
在您的模板中渲染产品详情无需查询BigCommerce API——所有数据都可在返回的元素中找到!
{# Execute element query from your new relational field: #} {% set relatedProducts = entry.newBigCommerceField.all() %} <ul> {% for product in products %} {# Output product data directly: #} <li>{{ product.productType }}: {{ product.title }}</li> {% endfor %} </ul>
更进一步
事件
venveo\bigcommerce\services\Products::EVENT_BEFORE_SYNCHRONIZE_PRODUCT
在产品元素使用新的BigCommerce数据保存之前发出。由于venveo\bigcommerce\events\BigCommerceProductSyncEvent
扩展了craft\events\CancelableEvent
,因此设置$event->isValid
可以防止新数据被保存。
事件对象有三个属性
element
:正在更新的产品元素。source
:应用了BigCommerce产品对象的BigCommerce产品对象。metafields
:插件在执行同步时发现的BigCommerce附加元字段。
use venveo\bigcommerce\events\BigCommerceProductSyncEvent; use venveo\bigcommerce\services\Products; use yii\base\Event; Event::on( Products::class, Products::EVENT_BEFORE_SYNCHRONIZE_PRODUCT, function(BigCommerceProductSyncEvent $event) { // Example 1: Cancel the sync if a flag is set via a BigCommerce metafield: if ($event->metafields['do_not_sync'] ?? false) { $event->isValid = false; } // Example 2: Set a field value from metafield data: $event->element->setFieldValue('myNumberFieldHandle', $event->metafields['cool_factor']); } );
警告 不要在事件处理器中手动保存更改。插件将为您处理!
元素API
您可以将同步的产品发布到Element API端点,就像任何其他元素类型一样。这允许您设置带有Craft中添加的任何内容的本地JSON产品源。
use venveo\bigcommerce\elements\Product; return [ 'endpoints' => [ 'products.json' => function() { return [ 'elementType' => Product::class, 'criteria' => [ 'publishedScope' => 'web', 'with' => [ ['myImageField'] ], ], 'transformer' => function(Product $product) { $image = $product->myImageField->one(); return [ 'title' => $product->title, 'variants' => $product->getVariants(), 'image' => $image ? $image->getUrl() : null, ]; }, ]; }, ], ];
致谢
- 非常有用的参考存储库:https://github.com/labbydev/fingerprint