Checkout extension scenario tutorial
This page collects real-world scenarios based on the Checkout Extension. For basic development flow, see Create a checkout extension; for a quick reference of extension points and APIs, see Checkout extension reference.
Scenario 1: Insert custom content at all extension points
-
Use case: During development and debugging, quickly confirm the actual position of each extension point on the page.
-
Implementation: Iterate over all extension points and render a red label showing its own name at each location.
src/index.js:
import { extend } from 'shoplazza-extension-ui';
const extensionPoints = [
'Checkout::RenderBefore',
'Checkout::RenderAfter',
'Checkout::Head::RenderAfter',
'Checkout::FilledInformation::RenderAfter',
'Checkout::SpecialInstruction::RenderAfter',
'Checkout::Summary::RenderBefore',
'Checkout::Navigate::RenderBefore',
'Checkout::Navigate::RenderAfter',
'Checkout::ContactInformation::RenderBefore',
'Checkout::ContactInformation::RenderAfter',
'Checkout::ShippingLinesTitle::RenderBefore',
'Checkout::ShippingLinesTitle::RenderAfter',
'Checkout::ShippingList::RenderAfter',
'Checkout::ProductList::RenderBefore',
'Checkout::ProductList::RenderAfter',
'Checkout::Reductions::RenderBefore',
'Checkout::Reductions::RenderAfter',
'Checkout::SectionPayment::RenderBefore',
'Checkout::SectionPayment::RenderAfter',
'Checkout::ThankyouHeader::RenderBefore',
'Checkout::ThankyouContent::RenderBefore',
];
function renderLabel(extensionPoint) {
return `
<div style="color:#dc2626;font-size:12px;">
${extensionPoint}
</div>
`;
}
extensionPoints.forEach(extensionPoint => {
extend({
extensionPoint,
component: renderLabel(extensionPoint),
});
});
After confirming the positions of each extension point, replace the HTML returned by renderLabel with the actual business content.
Example screenshot:

Scenario 2: API calls and event listening
-
Use case: Gain a comprehensive understanding of the available methods and events in
CheckoutAPI. It serves as a debugging tool and a starting reference when developing new extensions. -
Implementation: Render a set of action buttons at the top of the page to trigger various APIs; simultaneously listen for price, address, and step changes and output them in the console. When the page finishes loading, print the initial values of all APIs.
src/index.html:
<style>
.checkout-btn {
margin: 4px;
padding: 2px 4px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
background-color: #f0f0f0;
color: #333;
font-size: 12px;
line-height: 1.4;
transition: all 0.3s ease;
}
.checkout-btn:hover {
background-color: #e0e0e0;
}
</style>
<div data-coe-toolbar>
<button class="checkout-btn" data-action="stepNavToInformation">Jump to contact info</button>
<button class="checkout-btn" data-action="stepNavToShipping">Jump to shipping info</button>
<button class="checkout-btn" data-action="stepNavToPayment">Jump to payment info</button>
<button class="checkout-btn" data-action="doLogin">Login</button>
<button class="checkout-btn" data-action="doRegister">Register</button>
<button class="checkout-btn" data-action="doLogout">Logout</button>
<button class="checkout-btn" data-action="goToHomePage">Go to homepage</button>
<button class="checkout-btn" data-action="locationHref">Navigate to URL</button>
</div>
src/index.js:
import { extend } from 'shoplazza-extension-ui';
import template from './index.html';
extend({
extensionPoint: 'Checkout::RenderBefore',
component: template,
});
// ⚠️ 以下 log / console.log 仅用于演示,生产请删除或改为可关闭的日志开关
const LOG = '[checkout-event-api]';
const REGISTRY_KEY = '__CHECKOUT_EVENT_API__';
function log(message, ...args) {
console.log(`${LOG} ${message}`, ...args);
}
function getRegistry() {
if (!window[REGISTRY_KEY]) {
window[REGISTRY_KEY] = { shippingAddress: {} };
}
return window[REGISTRY_KEY];
}
function getApi() {
return window.CheckoutAPI;
}
function makeAction(name, fn) {
return async () => {
log(`[action] ${name} → 开始执行...`);
await fn();
log(`[action] ${name} → 完成`);
};
}
const actions = {
stepNavToInformation: makeAction('跳转至联系信息步骤', () => getApi().step.stepNavToInformation()),
stepNavToShipping: makeAction('跳转至运输信息步骤', () => getApi().step.stepNavToShipping()),
stepNavToPayment: makeAction('跳转至支付信息步骤', () => getApi().step.stepNavToPayment()),
doLogin: makeAction('登录', () => getApi().user.doLogin()),
doRegister: makeAction('注册', () => getApi().user.doRegister()),
doLogout: makeAction('退出登录', () => getApi().user.doLogout()),
goToHomePage: makeAction('跳转至首页', () => getApi().step.goToHomePage()),
locationHref: makeAction('跳转至指定路径', () => getApi().step.locationHref('/')),
};
// 用事件委托替代 inline onclick,避免全局命名空间污染
document.addEventListener('click', (event) => {
const btn = event.target.closest('[data-action]');
if (!btn || !btn.closest('[data-coe-toolbar]')) return;
const action = actions[btn.dataset.action];
if (action) action();
});
const MOUNT_INTERVAL_MS = 100;
const MOUNT_MAX_ATTEMPTS = 50; // 100ms * 50 ≈ 5 秒
function mount(attempt = 0) {
const api = getApi();
if (!api) {
if (attempt >= MOUNT_MAX_ATTEMPTS) {
console.warn(`${LOG} CheckoutAPI 未在预期时间内挂载,已放弃等待`);
return;
}
// 用 setTimeout 而不是 requestAnimationFrame,避免 60fps 忙等占用结账页 CPU
window.setTimeout(() => mount(attempt + 1), MOUNT_INTERVAL_MS);
return;
}
const reg = getRegistry();
if (reg.handlePricesChange) api.store.removePricesChangeCb(reg.handlePricesChange);
if (reg.handleAddressChange) api.address.removeShippingAddressChangeCb(reg.handleAddressChange);
if (reg.handleStepChange) api.step.removeStepChangeCb(reg.handleStepChange);
reg.handlePricesChange = (prices) => log('[prices-change]', prices);
reg.handleAddressChange = (patch) => {
reg.shippingAddress = { ...reg.shippingAddress, ...patch };
log('[address-change]', patch);
};
reg.handleStepChange = () => log('[step-change]', api.step.getStep());
api.store.onPricesChange(reg.handlePricesChange);
api.address.onShippingAddressChange(reg.handleAddressChange);
api.step.onStepChange(reg.handleStepChange);
console.group(`${LOG} 初始化完成`);
log('step:', api.step.getStep());
log('orderInfo:', api.store.getOrderInfo());
log('orderStatus:', api.store.getOrderStatus());
log('referInfo:', api.store.getReferInfo());
log('prices:', api.store.getPrices());
log('products:', api.summary.getProductList());
log('user.isLogin:', api.user.isLogin());
log('user.info:', api.user.getUserInfo());
log('shippingAddress:', api.address.getShippingAddress());
console.groupEnd();
}
mount();
Key Points:
-
Event delegation instead of inline onclick: All buttons use
data-action="xxx", and a singleclickevent listener ondocumentlocates the target viaclosest('[data-action]'). -
makeAction: Wraps async operations uniformly, logging before and after execution. -
mount: UsessetTimeoutwith a maximum retry count (~5 seconds) to wait forCheckoutAPIto mount, avoiding the 60fps busy-wait caused byrequestAnimationFrame. -
Before registering event listeners, calls the corresponding
remove*method to clear old callbacks and prevent duplicate registration during hot reload. -
getRegistry: Stores callback function references on thewindowglobal object to ensure the same function reference is passed whenremove*is called.
Scenario 3: Auto-fill address into credit card name
-
Applicable scenario: After the buyer fills in the shipping address during the contact information step, when they proceed to the payment step, automatically fill the name into the credit card holder name input to reduce duplicate entry.
-
Checkout layout explanation: Shoplazza supports three checkout layouts, which can be switched in the store admin panel under "Checkout Page Editor → Basic Checkout Configuration → Checkout Layout". This extension listens to both
onShippingAddressChangeandonStepChangeevents simultaneously, and triggers auto-fill at mount time based on the current step, covering different fill timings across all three layouts without requiring individual adaptation.
Core logic:
-
Listen for shipping address changes, merging each changed field into the local cache.
-
Listen for step changes, triggering fill when the step switches to
payment_method. -
At mount time, if the current step is already
payment_methodorcontact_information, attempt fill directly (covers scenarios where the page reloads into these steps). -
When filling, query the DOM for the credit card name input. If the input is not yet rendered, retry every 100ms for up to 5 seconds.
⚠️ DOM selector warning:
#card_first_name/#card_last_nameare internal DOM elements of the current version of Shoplazza's checkout page, which may change as the platform upgrades. For production extensions: (1) try to fulfill requirements via public APIs likeCheckoutAPI; (2) when DOM manipulation is unavoidable, add version detection and graceful degradation, and perform periodic regression testing.
-
tryFillCardholderName: Finds the credit card name input fields. If the fields are not rendered yet, retries every 100ms up to 50 times (approximately 5 seconds). Supports both ID selectors andnameattribute selectors for different DOM structures. -
setInputValue: After filling the input value, manually dispatchesinputandchangeevents to ensure the page framework detects the value update. -
handleAddressChange: Every time the address changes, merges the latest patch and immediately attempts to fill (covers single-step checkout layout scenarios). -
handleStepChange: Triggers filling when the step switches to the payment page (covers multi-step checkout layout scenarios). -
Initial fill in
mount: Handles filling when the page loads directly on a specific step (covers scenarios where the user enters the payment page directly). -
This extension does not need to render any UI, does not call
extend, and only runs logic in the background.