Checkout extension 参考
本页讲 Checkout Extension 的运行机制:extend 函数、它渲染的 HTML 模板,以及怎么隐藏结账页的原生模块。扩展点清单见扩展点,数据与事件接口见 CheckoutAPI 参考。开发流程见 创建一个结账扩展;实战场景见 Checkout extension 场景教程。
扩展点(Extension Points)
扩展点决定自定义内容插入到页面的具体位置。在 extend() 的 extensionPoint 字段中指定对应名称,内容即会渲染在该位置。
结账页和感谢页各有一组固定的扩展点,按所在的页面区域分组。其中一部分是动态扩展点:它们跟着数据走,每个商品行、每个物流方案、每个弹窗各渲染一次,名字里带着那一行的 id,要在运行时拼出来。
完整清单、每个扩展点所属的区域,以及可以配合隐藏的原生模块,见扩展点。
隐藏原生模块(deleteTarget)
deleteTarget 用于隐藏结账页的原生模块,比如各区块标题、地址簿、步骤面包屑。
在扩展目录下新建 extension.json,与 shoplazza.extension.toml 同级:
extensions/
└── my-checkout/
├── shoplazza.extension.toml
├── extension.json # 你只需要写 deleteTarget
├── package.json
└── src/
extension.json:
{ "deleteTarget": ["contactInformationHeader", "shippingAddressHeader"] }
让配置生效:
- 把 Shoplazza CLI 升级到 2.0.10 或更高版本。
- 重启
shoplazza app dev预览,或执行shoplazza app deploy发布。shoplazza app dev不监听extension.json,每次改完该文件都要重启。 - 恢复显示:把数组清空成
"deleteTarget": [],再部署一次。 - CLI 会自动往
extension.json回写extensionId和version。这两个字段保留即可,不用手写,也不要删。
可隐藏的模块
| Target | 位置 | 说明 |
|---|---|---|
header | 页头 | 顶部店名 / logo 区 |
navigate | 页头 | 步骤面包屑,如 Information、Shipping、Payment |
loginOrLogout | 页头 | 登录 / 退出链接 |
returnBtn | 页头 | 返回链接,与提交按钮并列渲染 |
contactInformation | 联系信息 | 整个联系信息模块 |
contactInformationHeader | 联系信息 | 「Contact」标题 |
contactEmail | 联系信息 | 邮箱输入框 |
contactPhone | 联系信息 | 手机号输入框 |
emailOrPhone | 联系信息 | 邮箱与手机号合并输入框,店铺配成二选一时使用 |
contactSubscribe | 联系信息 | 订阅营销邮件的勾选项 |
shippingAddress | 地址 | 整个收货地址模块 |
shippingAddressHeader | 地址 | 「Shipping address」标题 |
shippingAddressBook | 地址 | 地址簿,用于选择已存地址 |
securityIdentifier | 地址 | 安全与隐私标识 |
addressCard | 地址 | 已填信息卡片,出现在配送步和支付步 |
billingAddress | 地址 | 支付步的账单地址模块 |
deliveryMethod | 配送与自提 | 配送方式模块,用于切换快递和自提 |
deliveryMethodHeader | 配送与自提 | 配送方式标题 |
shippingList | 配送与自提 | 物流方案列表 |
shippingLinesTitle | 配送与自提 | 物流方案列表标题 |
pickupInformation | 配送与自提 | 自提信息模块 |
pickupInformationHeader | 配送与自提 | 自提信息标题 |
pickupAddress | 配送与自提 | 自提点地址列表 |
pickupAddressHeader | 配送与自提 | 自提点地址标题 |
paymentHeader | 支付 | 支付方式列表标题 |
payPalExpress | 支付 | PayPal 快捷支付按钮 |
infoSubmit | 提交按钮 | 信息步的「继续到配送」 |
shippingSubmit | 提交按钮 | 配送步的「继续到支付」 |
paymentSubmit | 提交按钮 | 支付步的「提交订单」 |
orderSummary | 订单摘要 | 整个订单摘要,桌面端在右栏,移动端在顶部 |
orderSummaryHeader | 订单摘要 | 订单摘要标题,移动端可折叠 |
productList | 订单摘要 | 商品行列表 |
ProductListCover | 订单摘要 | 商品行缩略图 |
productListSkuProperties | 订单摘要 | 商品行的自定义属性,properties 非空才渲染 |
priceList | 价格明细 | 整个价格明细区 |
priceListSubTotal | 价格明细 | 小计 |
priceListShippingTotal | 价格明细 | 运费 |
priceListTaxTotal | 价格明细 | 税费 |
priceListShippingTaxTotal | 价格明细 | 运费税 |
priceListGiftCard | 价格明细 | 礼品卡抵扣 |
totalPrice | 价格明细 | 合计 |
reductions | 优惠与备注 | 优惠码与礼品卡输入区 |
mobileCoupon | 优惠与备注 | 移动端的优惠码输入框,桌面端隐藏 |
giftCardTag | 优惠与备注 | 已应用的优惠码或礼品卡标签,带移除按钮 |
specialInstruction | 优惠与备注 | 配送步的订单备注输入框 |
thankyouHeader | 感谢页 | 结账完成页头部 |
thankyouContent | 感谢页 | 结账完成页内容 |
thankyouFooter | 感谢页 | 结账完成页底部 |
thankyouShippingInfo | 感谢页 | 物流信息块,有物流的订单才展示 |
thankyouFailOrderStatus | 感谢页 | 订单取消或失败时展示的失败信息 |
thankyouPageGiftCardAddress | 感谢页 | 虚拟商品订单展示的账单地址与礼品卡信息 |
thankyouPagePickupAddress | 感谢页 | 自提订单展示的自提地址 |
CheckoutAPI 参考
CheckoutAPI 是结账页平台挂在 window.CheckoutAPI 上的全局对象,结账页和感谢页都有。扩展通过它读取订单和顾客已经做出的选择、监听这些数据的变化,以及驱动结账页的部分界面。
它的方法按命名空间分组:address、base、config、coupon、exception、extension、order、payment、pickup、step、store、summary、track、user、utils。
全部命名空间、类型定义,以及调用之前要知道的几条规矩——对象什么时候才挂上、on* 和 remove* 怎么配对、register* 为什么不是监听器——见 CheckoutAPI 参考。
extend 函数与 HTML 模板
extend 函数
extend 是将内容注册到扩展点的核心函数,从 shoplazza-extension-ui 引入:
import { extend } from 'shoplazza-extension-ui';
extend({
extensionPoint: 'Checkout::Navigate::RenderBefore',
component: '<h1>Hello, Shoplazza!</h1>',
});
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
extensionPoint | string | 是 | 内容渲染在哪个扩展点,清单见扩展点 |
component | string 或 Promise<string> | 是 | 要渲染的 HTML。普通字符串可以单独使用;传 Promise<string> 需要同时设 type: 'spz' |
type | 'spz' | 否 | 最小形式不传这个参数。设成 'spz' 会启用 LessJS 组件库和下面说的占位符替换 |
localeMap | object | 否 | 按语言分组的文案,用来替换 component 里的 ${i18n('key')} 占位符。只在 type 为 'spz' 时生效 |
id | string | 否 | 扩展 id。传 __EXTENSION_ID__,CLI 构建时会把它替换成扩展名 |
不传 type 时,component 就是一段 HTML 字符串,原样渲染。这是上面那个最小形式,多数扩展只需要它。
设了 type: 'spz' 之后,SDK 在渲染前多做三件事:加载 LessJS 组件库、接受 Promise<string> 形式的 component,以及用 localeMap 替换 HTML 里的占位符:
import { extend } from 'shoplazza-extension-ui';
extend({
type: 'spz',
extensionPoint: 'Checkout::Navigate::RenderBefore',
component: Promise.resolve(
"<div>${i18n('greeting')} - ${i18n('promo.line')}</div>"
),
localeMap: {
'en-US': { greeting: 'Hello', promo: { line: 'Free returns within 30 days' } },
'zh-CN': { greeting: '你好', promo: { line: '30 天内免费退货' } },
},
id: __EXTENSION_ID__,
});
${i18n(...)}里的 key 必须带引号:${i18n('greeting')}会被替换,${i18n(greeting)}渲染出来是undefined。- 嵌套 key 用点号打平,
promo: { line: … }要写成${i18n('promo.line')}。 - 用哪一份文案取决于顾客的完整语言标记,也就是
localeMap每一项的 key。 - HTML 写成普通引号字符串,不要写成模板字符串,否则
${…}会被 JavaScript 先求值,轮不到 SDK 替换。 - 占位符
{EXTENSION_POINT}会被替换成由扩展点名派生的 id,例如Checkout::Summary::RenderBefore对应Checkout-Summary-RenderBefore-ID。
安全提示(XSS 风险): component 是直接渲染的 HTML 字符串,不要把未转义的用户输入或外部 API 返回值拼接进去,否则会导致 XSS 注入。任何动态内容先做 HTML 转义,或改用 createElement / textContent 等 DOM API 构造。
多次调用同一扩展点: 同一个 extensionPoint 可以被多次 extend,内容按调用顺序追加渲染,而不是覆盖。
HTML 模板
对于内容较复杂的扩展,推荐将 HTML 拆分到独立文件中管理。脚手架默认生成以下文件结构:
src/index.html(可选,组装入口):
<div>
import './style.html'
import './content.html'
import './script.html'
</div>
import './xxx.html' 不是 ES Module 语法。 它是 shoplazza-cli 在构建时识别的文本拼接指令:在打包阶段,CLI 会把 import './xxx.html' 这一行原地替换为 ./xxx.html 文件的内容(按出现顺序内联),而不是运行时动态加载。
- 该语法只能写在
src/index.html顶层作为组装入口;写在<script>内部或 JS 文件里都不会被识别。 - 被 import 的 HTML 片段不应再写
import,避免递归。 - 内容简单或需要动态生成 HTML 时,可以跳过模板文件,直接在
index.js中拼接字符串传给component。
src/style.html(可选,样式):
<style>
.my-block {
padding: 12px;
background: #f5f5f5;
}
</style>
src/content.html(可选,页面内容):
<div class="my-block">
<h2>Hello, Shoplazza!</h2>
</div>
src/script.html(可选):脚手架占位,若要在 HTML 模板片段内编写脚本,直接使用普通 <script> 标签即可:
<script>
document.querySelector('.my-block').addEventListener('click', function() {
window.CheckoutAPI.step.goToHomePage();
});
</script>
也可以不使用 HTML 模板,直接在 index.js 中构造 HTML 字符串传入 component,适合内容简单或需要动态生成的场景。
结账页性能直接影响转化率,扩展 JS / CSS 应尽量精简:单个扩展 gzip 后建议 < 30KB,避免引入大型依赖;优先用 setTimeout / IntersectionObserver 等被动方式而不是高频轮询。