Shopify 如何部署代码
关于Shopify应用集成配置,请参阅Shopify集成。
Shopify电商埋码指南
以下提供的脚本方便Shopify电商客户进行快速埋码设置
1. 代码中找到theme.liquid模板,设置基础埋码
Tips: xxxxx位置具体参考
<!-- Ptengine Tag -->
<script src="https://js.ptengine.com/xxxxx.js"></script>
<!-- End Ptengine Tag -->2. 事件采集部署
打开设置中的“客户事件”,将下列代码中的sid和步骤一一样,替换为sid
并全部粘贴进自定义像素中。
保存并连接。 请务必将id替换为您的sid
// 请务必将 sid 替换为您的档案 id
const sid = "请输入档案id";
const area = "com";
// 沙箱路径形态由 web pixel 的 runtimeContext 决定
const SANDBOX_RE =
/\/(?:wpm|web-pixels)@[^/]+\/(?:app\/|custom\/)?web-pixel-[^/]+\/sandbox\/modern/;
let ptenginePromise = null;
/**
* 取真实的店铺页面 URL。
* 即带页面路径,剥掉沙箱段后可还原出真实页面 URL。但事件上下文更可靠,
* 因此优先从 event.context 取,正则仅作回退。
*/
function getPageUrl(event) {
return (
event?.context?.document?.location?.href ||
event?.context?.window?.location?.href ||
window.location.href.replace(SANDBOX_RE, "")
);
}
// sid / area 直接用模块常量,不再作为形参重复传递
function loadPtengineScript(pageUrl) {
// 如果ptengine已经存在,则立即解决
if (window.ptengine) return Promise.resolve();
// 已有加载在飞行中,复用,不重复注入
if (ptenginePromise) return ptenginePromise;
ptenginePromise = new Promise((resolve, reject) => {
// 使用原始url进行发包;SDK 按 ',' 解析 setPVTag(严格取第 3 段判定 replace),
// URL 内的逗号会导致整段错位,必须转义
window._pt_sp_2 = window._pt_sp_2 || [];
window._pt_sp_2.push("setAccount," + sid);
window._pt_sp_2.push(
"setPVTag," + String(pageUrl).replace(/,/g, "%2C") + ",replace",
);
// 结构性限制:setPVTag 仅在首次加载时被 SDK 消费,因此同一沙箱生命周期内
// 后续事件都沿用第一个事件的 URL 做页面归属。多页 Shopify 站每次导航都会重建
// 沙箱,无影响;若主题是 SPA 式导航、或 checkout 单页走完多步,后续事件会挂在
// 第一个 URL 上 —— 精确归属需依赖事件属性里的 url 字段。
// 动态加载ptengine脚本
const script = document.createElement("script");
// 判断是否在checkout页面(必须用真实页面 URL,沙箱 URL 里不含 /checkouts/)
const isCheckoutPage =
/\/checkouts?\//.test(pageUrl) || /thank[_-]you/.test(pageUrl);
const sandboxQuery = isCheckoutPage ? "" : "?sandbox"; // checkout页面不添加'?sandbox'
script.src =
"https://js.ptengine." + area + "/" + sid + ".js" + sandboxQuery;
script.onload = () => resolve(); // 加载成功
script.onerror = () => {
ptenginePromise = null; // 允许后续事件重试
reject(new Error("Script loading failed")); // 加载失败
};
document.head.appendChild(script);
});
return ptenginePromise;
}
/**
* 加载完成后拿到可用的 ptengine 实例再执行 fn。
* SDK 可能因 domain 不匹配 / 抽样 / URL 排除 / 重复布码被中断,此时 onload 已触发
* 但 window.ptengine 不存在,必须显式告警,否则事件被静默丢弃。
* @param {string} pageUrl
* @param {function} fn
* @param {string} [label] 事件名,出现问题时写进告警便于定位
*/
function withPtengine(pageUrl, fn, label) {
return loadPtengineScript(pageUrl)
.then(() => {
if (!window.ptengine) {
console.warn(
"ptengine 未初始化(可能命中 URL 排除 / 抽样 / 重复布码),事件丢弃" +
(label ? ":" + label : ""),
);
return;
}
fn(window.ptengine);
})
.catch((error) => {
console.error(
"ptengine脚本加载失败" + (label ? ",事件丢弃:" + label : ""),
error,
);
});
}
function trackEvent(eventType, eventProperties, event) {
const options = eventProperties || {};
return withPtengine(
getPageUrl(event),
(pt) => pt.track(eventType, options),
eventType,
);
}
// 去除空值属性,避免发送 null/undefined/'' 字段
function removeEmptyKeys(obj) {
for (let key in obj) {
if (
obj.hasOwnProperty(key) &&
(obj[key] === null || obj[key] === undefined || obj[key] === "")
) {
delete obj[key];
}
}
return obj;
}
// 订单完成:1 条订单级 checkout_completed + 每个商品 1 条 checkout_completed_order
analytics.subscribe("checkout_completed", (event) => {
const checkout = event?.data?.checkout;
if (!checkout) return;
// 从订单客户 ID(gid://shopify/Customer/xxx)中提取数字 ID 作为 uid
const rawCustomerId = checkout?.order?.customer?.id;
const numericCustomerId = rawCustomerId ? rawCustomerId.split("/").pop(): null;
const uid = numericCustomerId;
// ⚠️ 口径待定:此处用 totalPrice(含税费运费的成交总额),而 checkout_started
// 与 payment_info_submitted 用的是 subtotalPrice(小计)。同一漏斗里两种口径
// 金额不可比,需与客户确认统一到哪一个,或在两端都补上另一个字段。
const totalPrice = checkout?.totalPrice?.amount;
const currencyCode = checkout?.totalPrice?.currencyCode;
const orderId = checkout?.order?.id;
const items = checkout?.lineItems || [];
const eventProperties = removeEmptyKeys({
totalPrice: totalPrice,
currencyCode: currencyCode,
totalorderid: orderId,
productCount: items.length,
});
withPtengine(
getPageUrl(event),
(pt) => {
// 1) identify:整单一次,把订单信息写到用户档案。
// 必须在 track 之前发,否则后面的事件归不到已识别用户身上。
if (uid) {
pt.identify(uid, {
totalPrice: totalPrice,
totalorderid: orderId,
});
}
// 2) track:只发一次订单级 checkout_completed(不含商品明细)
pt.track("checkout_completed", eventProperties);
// 3) 商品明细:每个商品单独发 1 条 checkout_completed_order,
// 带 totalorderid 以便与订单级事件关联
items.forEach((item) => {
const itemProperties = removeEmptyKeys({
totalorderid: orderId,
sku: item?.variant?.sku,
name: item?.title,
spu: item?.variant?.product?.type,
// ⚠️ 口径待定:item.id 是「行项 ID」,每单都不同,拿它做商品维度聚合会完全散开。
// 商品维度应改用 item.variant.product.id,规格维度用 item.variant.id。
// 改字段含义会影响已有报表,需客户确认后再动。
id: item?.id,
quantity: item?.quantity,
price: item?.finalLinePrice?.amount,
currencyCode: currencyCode,
});
pt.track("checkout_completed_order", itemProperties);
});
},
"checkout_completed",
);
});
analytics.subscribe("search_submitted", (event) => {
const eventProperties = removeEmptyKeys({
keyword: event?.data?.searchResult?.query,
resultCount: event?.data?.searchResult?.productVariants?.length,
});
trackEvent("search_submitted", eventProperties, event);
});
analytics.subscribe("collection_viewed", (event) => {
const eventProperties = removeEmptyKeys({
id: event?.data?.collection?.id,
name: event?.data?.collection?.title,
productCount: event?.data?.collection?.productVariants?.length,
});
trackEvent("collection_viewed", eventProperties, event);
});
analytics.subscribe("product_viewed", (event) => {
const origin = event?.context?.window?.origin || window.location.origin;
const variant = event?.data?.productVariant;
const eventProperties = removeEmptyKeys({
sku: variant?.sku,
name: variant?.product?.title,
// ⚠️ 口径待定:type 与 spu 取的是同一个值,重复占用事件属性配额且语义不清。
// 保留是因为可能已有报表在用其中之一,需客户确认后再合并。
type: variant?.product?.type,
spu: variant?.product?.type,
price: variant?.price?.amount,
currencyCode: variant?.price?.currencyCode,
url: variant?.product?.url ? origin + variant.product.url : undefined,
imageUrl: variant?.image?.src,
});
trackEvent("product_viewed", eventProperties, event);
});
analytics.subscribe("product_added_to_cart", (event) => {
const origin = event?.context?.window?.origin || window.location.origin;
const merchandise = event?.data?.cartLine?.merchandise;
const eventProperties = removeEmptyKeys({
url: merchandise?.product?.url ? origin + merchandise.product.url: undefined,
name: merchandise?.product?.title,
sku: merchandise?.sku,
quantity: event?.data?.cartLine?.quantity,
price: merchandise?.price?.amount,
currencyCode: merchandise?.price?.currencyCode,
spu: merchandise?.product?.type,
});
trackEvent("product_added_to_cart", eventProperties, event);
});
analytics.subscribe("product_removed_from_cart", (event) => {
const origin = event?.context?.window?.origin || window.location.origin;
const merchandise = event?.data?.cartLine?.merchandise;
const eventProperties = removeEmptyKeys({
url: merchandise?.product?.url ? origin + merchandise.product.url : undefined,
name: merchandise?.product?.title,
sku: merchandise?.sku,
quantity: event?.data?.cartLine?.quantity,
price: merchandise?.price?.amount,
currencyCode: merchandise?.price?.currencyCode,
spu: merchandise?.product?.type,
});
trackEvent("product_removed_from_cart", eventProperties, event);
});
analytics.subscribe("cart_viewed", (event) => {
const cart = event?.data?.cart;
if (!cart) {
trackEvent("cart_viewed", {}, event);
return;
}
const origin = event?.context?.window?.origin || window.location.origin;
const lines = cart?.lines || [];
// ⚠️ 口径提醒:此处对购物车里每个商品各发一条 cart_viewed,
// 3 件商品就是 3 条同名事件 —— 事件配额成倍消耗,转化率分母也会膨胀。
// 保留此行为以免打断已有报表;若要改成「1 条购物车级 + N 条商品级」,
// 那是口径变更,需先与客户确认并同步调整报表。
if (lines.length) {
lines.forEach((line) => {
const merchandise = line?.merchandise;
const eventProperties = removeEmptyKeys({
totalQuantity: cart?.totalQuantity,
url: merchandise?.product?.url ? origin + merchandise.product.url : undefined,
name: merchandise?.product?.title,
sku: merchandise?.sku,
spu: merchandise?.product?.type,
quantity: line?.quantity,
// 有意选择:line.cost.totalAmount 是行小计,merchandise.price 是单价,
// 两者语义不同,不做兜底混填 —— cost 缺失时宁可丢字段,也不让 price 含义不明。
price: line?.cost?.totalAmount?.amount,
currencyCode: line?.cost?.totalAmount?.currencyCode,
});
trackEvent("cart_viewed", eventProperties, event);
});
} else {
trackEvent(
"cart_viewed",
removeEmptyKeys({ totalQuantity: cart?.totalQuantity }),
event,
);
}
});
analytics.subscribe("checkout_started", (event) => {
const checkout = event?.data?.checkout;
const origin = event?.context?.window?.origin || window.location.origin;
const checkoutItems = checkout?.lineItems;
const totalPrice = checkout?.subtotalPrice?.amount;
const currencyCode = checkout?.subtotalPrice?.currencyCode;
// ⚠️ 同 cart_viewed:此处每个商品发一条 checkout_started,同样会膨胀。改法见上。
if (checkoutItems && checkoutItems.length) {
checkoutItems.forEach((item) => {
const eventProperties = removeEmptyKeys({
totalPrice: totalPrice,
currencyCode: currencyCode,
url: item?.variant?.product?.url ? origin + item.variant.product.url : undefined,
name: item?.variant?.product?.title,
sku: item?.variant?.sku,
spu: item?.variant?.product?.type,
quantity: item?.quantity,
price: item?.finalLinePrice?.amount,
});
trackEvent("checkout_started", eventProperties, event);
});
} else {
trackEvent(
"checkout_started",
removeEmptyKeys({ totalPrice, currencyCode }),
event,
);
}
});
analytics.subscribe("checkout_contact_info_submitted", (event) => {
trackEvent("checkout_contact_info_submitted", {}, event);
});
analytics.subscribe("checkout_address_info_submitted", (event) => {
trackEvent("checkout_address_info_submitted", {}, event);
});
analytics.subscribe("checkout_shipping_info_submitted", (event) => {
trackEvent("checkout_shipping_info_submitted", {}, event);
});
analytics.subscribe("payment_info_submitted", (event) => {
const checkout = event?.data?.checkout;
const eventProperties = removeEmptyKeys({
totalPrice: checkout?.subtotalPrice?.amount,
currencyCode: checkout?.subtotalPrice?.currencyCode,
});
trackEvent("payment_info_submitted", eventProperties, event);
});
3.事件定义:
search_submitted:使用搜索功能
collection_viewed:列表页访问
product_viewed:商品详情页访问
product_added_to_cart:添加购物车
product_removed_from_cart:移除购物车
cart_viewed:购物车页面访问
checkout_started:进入checkout页面
checkout_contact_info_submitted:输入联系方式:邮箱
checkout_address_info_submitted:输入地址
checkout_shipping_info_submitted:选择快递信息
payment_info_submitted:选择支付方式
checkout_completed:支付成功(订单级,一单一条)
checkout_completed_order:支付成功的商品明细(商品级,一单内每个商品一条,通过totalorderid与订单级事件关联)
最后更新于