该能力目前处于灰度阶段,如需使用请联系您的客户经理
简介
Cart Flex Function 允许开发者更改购物车&结账中商品的价格和展示方式。例如更新标题和图片、更改价格以及组合商品或者扩展商品。
Shopline Functions 允许您自定义 Shopline 的后端逻辑。Cart Flex Function 将此逻辑集成到结账流程中。
支持的操作:
- Expand操作:将购物车&结账的某一个商品行扩展为多个商品。
- Merge操作:将多行购物车&结账的商品合并为一个代表捆绑商品的行。
- Update操作:更新购物车&结账中商品行的显示方式,以覆盖其价格、标题或图片。
Expand、Merge操作的套装,在结账页展示的效果如下:
开发并发布 Cart Flex Function
要求
- 确保已创建 SHOPLINE 应用,并完成审核、上架。
- 确保已安装 SHOPLINE CLI。
备注:
步骤一:账号登录
在终端执行sl login登录到开发商店。
- 在终端指定要登录的商店。
sl login --store={example.myshopline.com}
- 根据浏览器提示完成登录操作。
步骤二:拉取 Cart Flex Function 模版
如果你是首次创建 cart flex function,需要先拉取 Cart Flex Function 模版:
- 在终端输入该命令 sl extension create。
user % sl extension create
? What type of extension are you creating?
(1) Theme App Extension
(2) Checkout UI Customizer
(3) Post Purchase Customizer
(4) Shopline Pixels Extension
(5) Shopline Shopper Extension
(6) payment-customizations - Function
(7) discounts-customizations - Function
(8) product-discount-customizations - Function
> (9) cart-flex - Function
Press ↑↓ arrows to select, enter to confirm
- 选择 cart-flex - Function。
- 选择SHOPLINE合作伙伴组织账号。
- 在终端展示的应用列表中,选择需要关联的应用。
? What type of extension are you creating?
✔ cart-flex - Function
? Select partner organization?
✔ SHOPLINE-partner-name
? Which app would like to register this extension with?
✔ YouAppName
- 等待依赖安装完毕则可进行开发。
✔ Dependencies installed
✔ .env saved to project root
╭─ success ────────────────────────────────────────────────────────────────────╮
│ │
│ Your extension was created in cart-flex. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
步骤三:开发 Function
目录结构
Function 主要由以下部分组成:
cart-flex
+-- module 模型定义,input/output go 结构定义此处
|
+-- .env.prod 环境信息
|
+-- input.graphql input声明文件,声明需要哪些输入
|
+-- main.go function 核心逻辑入口,函数的业务逻辑要写在这里
|
+-- schema.gql 完整 input 的结构定义
定义输入
在 input.graphql 中声明你的函数期望的输入,可定义的输入内容需要是 schema.gql 中定义的结构的子集。只有在 input.graphql 中定义有定义的字段,才能在函数执行时获取到;
备注:
为保证 Function 执行的性能,请尽量只声明你的 Function 中会消费的字段;
Function 逻辑
在 main.go 中编写你的 Cart Flex Function 的核心业务逻辑。
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
// 在这里编写你的核心业务逻辑,通过具体某个购物车/结账的信息,判断是否需要调整商品行
// req 中,只有你在 input.graphql 有声明的字段,才有值
// 如果需要调整,返回具体的调整操作
return module.CartFlexFunctionResponse{}
}
var _ = fmt.Printf
var _ = function.Log
var _ = easyjson.Marshal
关键信息:
-
CartFlexFunction方法:在此处编写你的核心逻辑,根据 购物车/结账的信息,判断是否需要调整商品行信息:
- 函数入参:只有你在 input.graphql 有声明的字段,才有值。
- 函数返回:如果需要调整,返回具体的调整操作。
-
function.Log方法:使用该方法打印日志的日志信息,最终会在 「SHOPLINE 合作伙伴后台-应用-应用详情-函数日志」中展示:
-
easyjson.Marshal:函数中,只支持使用 easyjson 进行 json 操作。
步骤四:推送 Function
当你编写完你的业务逻辑后,在 cart-flex 目录下,使用 sl extension push 命令将 Cart Flex Function 推送到 SHOPLINE:
user % sl extension push
╭─ success ────────────────────────────────────────────────────────────────────╮
│ │
│ Pushed cart-flex to Shopline! │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
推送成功后,.env.prod中将回填 SHOPLINE 为该函数分配的 functionId:
PARTNER_ID=yourPartnerId
SHOPLINE_APP_KEY=yourAppKey
SHOPLINE_APP_SECRET=yourAppSecret
EXTENSION_TITLE=cart-flex
EXTENSION_UUID=${functionId}
步骤五:发布 Function
在 cli 将 Cart Flex Function 推送到 SHOPLINE 后,需要去 「SHOPLINE合作伙伴后台-应用-应用详情-应用版本」点击「创建版本」 「创建并提交审核」
待 cart-flex 审核通过后,再点击「发布」,最新的 cart flex function 代码即可生效
Cart Flex Function 业务逻辑开发
本节只说明 main.go 中业务逻辑需要消费的输入、需要返回的输出,以及常见操作写法。扩展创建、目录结构、推送发布、日志和 JSON 工具在上一节已经说明,这里不再重复。
业务逻辑入口
开发者只需要实现模板中的入口函数:
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
return module.CartFlexFunctionResponse{}
}
req 是当前购物车/结账上下文。result 是本次函数希望平台执行的商品行操作。没有命中业务规则时,返回 module.CartFlexFunctionResponse{},或返回 Operations 为空的 response。
Function Input
只有在 input.graphql 中声明的字段,运行时才会出现在 req 中。建议只声明业务判断真正需要的字段。
输入对象的主要层级是:CartFlexFunctionRequest 包含购物车、实例配置、本地化、汇率和店铺信息;购物车包含商品行、买家身份、购物车自定义属性和元数据;商品行再关联金额、商品变体、商品和商品行自定义属性。
元数据声明方式
元数据不是默认全量传入的字段。开发者必须先在 input.graphql 的 cart(…) 查询参数中声明要读取哪些实体的元数据,运行时对应实体的 metafields 字段才会携带这些元数据。
schema.gql 中 cart 支持的元数据查询参数如下:
| 查询参数 | 类型 | 对应返回位置 | 业务含义 |
|---|---|---|---|
| cartMetafields | []MetafieldInput | cart.metafields | 查询购物车级元数据。 |
| customerMetafields | []MetafieldInput | cart.buyerIdentity.customer.metafields | 查询客户级元数据。 |
| companyMetafields | []MetafieldInput | cart.buyerIdentity.purchasingCompany.company.metafields | 查询 B2B 公司级元数据。 |
| productMetafields | []MetafieldInput | cart.lines[].merchandise.product.metafields | 查询商品级元数据。 |
| productVariantMetafields | []MetafieldInput | cart.lines[].merchandise.metafields | 查询商品变体级元数据。 |
MetafieldInput 每一项包含 namespace 和 key。只声明需要消费的元数据,避免无关数据进入 Function 输入。
query Input {
cart(
customerMetafields: [
{ namespace: "loyalty", key: "member_level" }
],
productMetafields: [
{ namespace: "cart_flex", key: "bundle_config" }
]
) {
buyerIdentity {
customer {
metafields {
key
namespace
type
value
}
}
}
lines {
id
merchandise {
product {
metafields {
key
namespace
type
value
}
}
}
}
}
}
业务代码中也只能消费已声明并返回的元数据。例如读取客户会员等级和商品套装配置:
var memberLevel string
for _, m := range req.GetCart().GetBuyerIdentity().GetCustomer().GetMetafields() {
if m.Key == "member_level" && m.Namespace != nil && *m.Namespace == "loyalty" {
memberLevel = m.Value
break
}
}
var bundleConfig string
for _, line := range req.GetCart().GetLines() {
for _, m := range line.Merchandise.Product.GetMetafields() {
if m.Key == "bundle_config" && m.Namespace != nil && *m.Namespace == "cart_flex" {
bundleConfig = m.Value
break
}
}
if bundleConfig != "" {
break
}
}
_ = memberLevel
_ = bundleConfig
cartFlex.metafield 不是通过这些查询参数声明的实体元数据,它来自 CartFlex 规则的配置,会在 Function 执行时随 CartFlex 入参传入。
CartFlexFunctionRequest
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| cart | Cart | 是 | 当前购物车/结账上下文,是判断商品行、买家、购物车自定义属性和购物车级配置的主要来源。 |
| cartFlex | CartFlex | 是 | 当前 Cart Flex Function 实例配置,可用于读取商家在实例上保存的规则。 |
| localization | Localization | 是 | 当前购物车的国家地区与语言,可用于区域和语言差异化规则。 |
| presentmentCurrencyRate | ExchangeRate | 是 | 店铺币种到展示币种的汇率,可用于按展示币种计算固定价或阈值。 |
| shop | Shop | 否 | 店铺上下文,目前包含店铺时区下的本地时间,可用于限时活动。 |
Cart
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lines | []LineItem | 是 | 当前所有商品行。多数业务会遍历该数组,找到要展开、合并或更新的行。 |
| buyerIdentity | BuyerIdentity | 否 | 买家身份信息,可用于会员、邮箱、手机号、B2B 公司等分群判断。 |
| attributes | []Attribute | 否 | 购物车级自定义属性,可承载渠道、活动标记或前端传入的业务开关。 |
| metafields | []Metafield | 否 | 购物车级元数据,可读取购物车维度的扩展配置。 |
LineItem
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 商品行 ID。返回商品行展开、合并或更新操作时使用这个值定位原商品行。 |
| quantity | Int | 是 | 当前商品行数量,可用于判断是否达到套装、批量购买或阶梯价规则。 |
| cost | LineItemCost | 是 | 商品行金额信息,可用于按单价、小计、总价或最终价判断规则。 |
| merchandise | ProductVariant | 是 | 商品变体信息,用于识别 SKU、变体 ID、所属商品和变体级元数据。 |
| properties | []Property | 否 | 商品行维度的自定义属性,用于承载外部写入的扩展属性信息,例如定制文本、图片、链接等,并在购物车、结账、订单链路中传递。 |
LineItemCost
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| price | Money | 是 | 商品行单价,可用于原始价格判断。 |
| subtotalAmount | Money | 是 | 商品行小计金额,可用于按行小计判断门槛。 |
| totalAmount | Money | 是 | 商品行总金额,可用于按总金额判断规则。 |
| finalPrice | Money | 是 | 商品行最终价格,可用于判断最终展示价相关规则。 |
Money
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| amount | String | 是 | 金额值,使用字符串承载,避免浮点精度问题。 |
| currencyCode | String | 是 | 币种代码。 |
ProductVariant
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 商品变体 ID,用于识别当前变体,也可作为输出中变体 ID 的来源。 |
| product | Product | 是 | 所属商品信息,用于按商品维度继续匹配规则。 |
| requiresShipping | Boolean | 是 | 是否需要发货,可用于区分虚拟商品、服务商品和实物商品。 |
| sku | String | 否 | SKU,可用于匹配业务规则。 |
| title | String | 否 | 变体标题,可用于展示判断或生成新的标题。 |
| price | Money | 否 | 变体当前售价,可用于价格计算。 |
| compareAtPrice | Money | 否 | 变体划线价,可用于促销展示逻辑。 |
| weight | Float | 否 | 变体重量,可用于重量相关规则。 |
| weightUnit | String | 否 | 变体重量单位。 |
| isCustom | Boolean | 否 | 是否自定义商品,可用于定制品规则。 |
| metafields | []Metafield | 否 | 变体级元数据,可存放套装组成、展示标题、价格规则等。 |
Product
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 商品 ID,可用于匹配指定商品。 |
| handle | String | 是 | 商品 handle,可用于稳定识别商品。 |
| isGiftCard | Boolean | 是 | 是否礼品卡,可用于排除或特殊处理礼品卡。 |
| category | Int | 否 | 商品类目编号,可用于按标准类目生效。 |
| customCategory | String | 否 | 自定义商品类目,可用于按商家自定义类目生效。 |
| title | String | 否 | 商品标题,可用于生成展示标题。 |
| inSalesPlanGroup | Boolean | 否 | 是否在订阅计划组,可用于订阅商品规则。 |
| metafields | []Metafield | 否 | 商品级元数据,常用于保存套装、合并、改价等业务规则。 |
BuyerIdentity
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| customer | Customer | 否 | 登录客户信息,可用于会员身份、客户标签或客户元数据判断。 |
| String | 否 | 买家邮箱,可用于特定客户或渠道识别。 | |
| phone | String | 否 | 买家手机号,可用于特定客户或渠道识别。 |
| purchasingCompany | PurchasingCompany | 否 | B2B 公司身份,可用于公司、联系人、地点维度的价格或展示规则。 |
Customer
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 客户 ID,可用于识别登录客户。 |
| displayName | String | 是 | 客户展示名。 |
| String | 否 | 客户邮箱。 | |
| phone | String | 否 | 客户手机号。 |
| metafields | []Metafield | 否 | 客户级元数据,可用于会员等级、偏好、客群等规则。 |
PurchasingCompany
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| company | Company | 否 | B2B 公司信息。 |
| companyContact | CompanyContact | 否 | B2B 公司联系人。 |
| companyLocation | CompanyLocation | 否 | B2B 公司地点。 |
Company
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 公司 ID,可用于按公司匹配规则。 |
| name | String | 否 | 公司名称。 |
| metafields | []Metafield | 否 | 公司级元数据,可用于 B2B 专属规则。 |
CompanyContact
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 公司联系人 ID。 |
CompanyLocation
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | ID | 是 | 公司地点 ID。 |
| name | String | 否 | 公司地点名称。 |
Attribute
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| key | String | 否 | 购物车自定义属性 key。 |
| value | String | 否 | 购物车自定义属性值。 |
CartFlex
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| metafield | Metafield | 否 | Function 实例级元数据,适合保存商家在后台配置的 JSON 规则。 |
Metafield
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| key | String | 是 | 元数据 key,用于定位具体配置项。 |
| namespace | String | 否 | 元数据命名空间,用于区分不同业务域的配置。 |
| type | String | 是 | 元数据类型,例如 JSON 或文本类型。 |
| value | String | 是 | 元数据内容,常见为 JSON 字符串。 |
Localization
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| country | String | 是 | 国家地区代码,用于区域化规则。 |
| language | String | 是 | 语言代码,用于本地化标题、图片或文案。 |
ExchangeRate
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| currencyCode | String | 否 | 展示币种代码。 |
| rate | String | 是 | 店铺币种到展示币种的汇率,用于币种换算。 |
Shop
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| localTime | String | 否 | 店铺本地时间,可用于按日期、星期、小时控制规则。 |
Property
properties 是商品行维度的扩展属性信息,用于承载外部写入的定制信息,并在购物车、结账、订单链路中传递。Cart Flex Function 读取输入或在输出中生成 properties 时,都应遵循同一套字段含义。
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| name | String | 否 | 自定义属性名称,例如定制项名称、礼品卡接收人、祝福语、套装组件标识等。 |
| value | String | 否 | 自定义属性值,用于承载该定制项的具体内容。 |
| type | String | 否 | 定制信息类型。有效值包括 text、picture、link。 |
| urls | []String | 否 | URL 资源列表。type 为 picture 时可用于渲染缩略图;type 为 link 时通常使用第一个 URL 作为超链接。 |
| show | Boolean | 否 | 旧版可见性字段。仅当 roleVisibility 为空或不是有效枚举值时作为降级判断;true 表示买家和商家均可见,false 表示买家不可见、商家可见。 |
| roleVisibility | String | 否 | 定制信息可见对象。有效值包括 customer、merchant、all、none,分别表示买家侧可见、商家侧可见、双方可见、双方都不可见。 |
| additional | String | 否 | 定制商品附加字段,可保存定制项的补充信息。例如 name 表示文字定制时,这里可保存字号、字体、颜色等。 |
| extInfo | String | 否 | 扩展字段,用于保存业务自定义的附加信息。 |
Function Output
输出对象的主要层级是:CartFlexFunctionResponse 包含操作列表;每个 LineOperation 只选择一种操作;具体操作再引用商品行 ID、变体 ID、价格调整、图片和自定义属性。
CartFlexFunctionResponse
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| operations | []LineOperation | 是 | 希望平台执行的商品行操作列表。数组为空表示不调整购物车。 |
LineOperation
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lineExpand | LineExpandOperation | 否 | 将一个商品行展开为多个子商品行,适合展示套装、组合装明细。 |
| linesMerge | LinesMergeOperation | 否 | 将多个商品行合并为一个父商品行,适合把多件商品展示成一个套装。 |
| lineUpdate | LineUpdateOperation | 否 | 更新单个商品行的标题、图片或价格。 |
每个 LineOperation 只设置一种操作。不要在同一个元素里同时设置 lineExpand、linesMerge 和 lineUpdate。
LineExpandOperation
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lineItemId | String | 是 | 被展开的商品行 ID,必须来自输入中的商品行 ID。 |
| expandedLineItems | []ExpandedLineItem | 是 | 展开后展示的子商品行列表。 |
| image | Image | 否 | 展开后父商品行展示图。 |
| price | PriceAdjustment | 否 | 展开后父商品行价格调整。 |
| title | String | 否 | 展开后父商品行展示标题。 |
ExpandedLineItem
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| variantId | String | 是 | 子商品变体 ID。 |
| quantity | Int | 是 | 子商品数量。 |
| price | PriceAdjustment | 否 | 子商品价格调整。 |
| properties | []Property | 否 | 展开出的子商品行自定义属性,遵循商品行维度 properties 规范,可传递子商品定制信息、资源链接或可见性设置。 |
LinesMergeOperation
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lineItems | []MergeLineItem | 是 | 参与合并的商品行和数量。 |
| parentVariantId | String | 是 | 合并后父商品对应的商品变体 ID。 |
| properties | []Property | 否 | 合并后父商品行自定义属性,遵循商品行维度 properties 规范,可传递合并套装的定制信息、资源链接或可见性设置。 |
| image | Image | 否 | 合并后父商品行展示图。 |
| price | PriceAdjustment | 否 | 合并后父商品行价格调整。 |
| title | String | 否 | 合并后父商品行展示标题。 |
MergeLineItem
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lineItemId | String | 是 | 参与合并的商品行 ID,必须来自输入中的商品行 ID。 |
| quantity | Int | 是 | 该商品行参与合并的数量规则。 |
LineUpdateOperation
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| lineItemId | String | 是 | 被更新的商品行 ID,必须来自输入中的商品行 ID。 |
| image | Image | 否 | 更新后的展示图片。 |
| price | PriceAdjustment | 否 | 更新后的价格。 |
| title | String | 否 | 更新后的展示标题。 |
Image
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| url | String | 是 | 展示图片地址。仅接受shopline CDN的图片,不可识别的图片链接将不会展示。 支持的域名:“.cloudfront.”, “img.myshopline.com”, “img-.*.myshopline.com” |
PriceAdjustment
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| percentageAdjustment | PercentageAdjustment | 否 | 百分比调价。 |
| fixedPricePerUnit | FixedPricePerUnit | 否 | 固定单价调价。 |
percentageAdjustment 和 fixedPricePerUnit 只能二选一,不要同时返回。
PercentageAdjustment
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| value | String | 否 | 百分比值,调价范围(-100,200]。正数表示涨价,例如 30 表示价格乘以 130%;负数表示降价,例如 -20 表示价格乘以 80%。 |
FixedPricePerUnit
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| amount | String | 否 | 固定单价金额,调价范围(0,999999999]。 |
JSON 示例
下面的示例直接对应 module.CartFlexFunctionRequest 和 module.CartFlexFunctionResponse。示例尽量覆盖当前 schema 中的可用字段;实际运行时,未在 input.graphql 声明的字段不会出现在输入中,未命中的规则也不需要在返回中输出对应操作。
全量输入示例
{
"cart": {
"attributes": [
{
"key": "campaign",
"value": "summer_bundle"
}
],
"buyerIdentity": {
"customer": {
"displayName": "Alice Chen",
"email": "alice@example.com",
"id": "123",
"metafields": [
{
"key": "member_level",
"namespace": "loyalty",
"type": "single_line_text_field",
"value": "gold"
}
],
"phone": "+8613800000000"
},
"email": "alice@example.com",
"phone": "+8613800000000",
"purchasingCompany": {
"company": {
"id": "123",
"metafields": [
{
"key": "contract_tier",
"namespace": "b2b",
"type": "single_line_text_field",
"value": "enterprise"
}
],
"name": "Example Trading Co."
},
"companyContact": {
"id": "123"
},
"companyLocation": {
"id": "123",
"name": "Shanghai Office"
}
}
},
"lines": [
{
"cost": {
"finalPrice": {
"amount": "89.00",
"currencyCode": "USD"
},
"price": {
"amount": "99.00",
"currencyCode": "USD"
},
"subtotalAmount": {
"amount": "198.00",
"currencyCode": "USD"
},
"totalAmount": {
"amount": "178.00",
"currencyCode": "USD"
}
},
"id": "123",
"merchandise": {
"compareAtPrice": {
"amount": "129.00",
"currencyCode": "USD"
},
"id": "123",
"isCustom": false,
"metafields": [
{
"key": "variant_badge",
"namespace": "cart_flex",
"type": "single_line_text_field",
"value": "limited"
}
],
"price": {
"amount": "99.00",
"currencyCode": "USD"
},
"product": {
"category": 101,
"customCategory": "bundle",
"handle": "travel-bundle",
"id": "123",
"inSalesPlanGroup": false,
"isGiftCard": false,
"metafields": [
{
"key": "bundle_config",
"namespace": "cart_flex",
"type": "json",
"value": "{\"children\":[{\"variantId\":\"123\",\"quantity\":1},{\"variantId\":\"123\",\"quantity\":2}]}"
}
],
"title": "Travel Bundle"
},
"requiresShipping": true,
"sku": "TRAVEL-BUNDLE",
"title": "Default Title",
"weight": 1.2,
"weightUnit": "kg"
},
"properties": [
{
"additional": "{\"fontSize\":\"14px\",\"font\":\"Inter\",\"color\":\"#333333\"}",
"extInfo": "{\"source\":\"theme\"}",
"name": "gift_message",
"roleVisibility": "customer",
"show": true,
"type": "text",
"value": "Happy trip"
},
{
"name": "gift_preview",
"roleVisibility": "all",
"show": true,
"type": "picture",
"urls": [
"https://cdn.shoplineapp.com/files/custom-preview.png"
],
"value": "Gift preview"
}
],
"quantity": 2
}
],
"metafields": [
{
"key": "cart_rule",
"namespace": "cart_flex",
"type": "json",
"value": "{\"enabled\":true}"
}
]
},
"cartFlex": {
"metafield": {
"key": "cart_flex_config",
"namespace": "cart_flex",
"type": "json",
"value": "{\"bundleRules\":[{\"handle\":\"travel-bundle\"}]}"
}
},
"localization": {
"country": "US",
"language": "en"
},
"presentmentCurrencyRate": {
"currencyCode": "USD",
"rate": "1.0000"
},
"shop": {
"localTime": "2026-05-15T10:30:00+08:00"
}
}
返回示例
{
"operations": [
{
"lineUpdate": {
"lineItemId": "123",
"image": {
"url": "https://img.myshopline.com/files/travel-bundle-cover.png"
},
"price": {
"percentageAdjustment": {
"value": "-10"
}
},
"title": "Gold Member Travel Bundle"
}
},
{
"lineExpand": {
"lineItemId": "123",
"expandedLineItems": [
{
"properties": [
{
"name": "bundle_component",
"roleVisibility": "all",
"show": true,
"type": "text",
"value": "Travel Pouch"
}
],
"variantId": "123",
"price": {
"fixedPricePerUnit": {
"amount": "19.90"
}
},
"quantity": 1
},
{
"variantId": "123",
"quantity": 2
}
],
"image": {
"url": "https://cdn.shopline.com/files/travel-bundle-cover.png"
},
"price": {
"percentageAdjustment": {
"value": "-10"
}
},
"title": "Travel Bundle"
}
},
{
"linesMerge": {
"lineItems": [
{
"lineItemId": "123",
"quantity": 1
},
{
"lineItemId": "123",
"quantity": 1
}
],
"properties": [
{
"name": "bundle_name",
"roleVisibility": "all",
"show": true,
"type": "text",
"value": "City Commute Set"
}
],
"parentVariantId": "123",
"image": {
"url": "https://cdn.shopline.com/files/city-commute-set.png"
},
"price": {
"fixedPricePerUnit": {
"amount": "149.00"
}
},
"title": "City Commute Set"
}
}
]
}
返回示例同时展示三类操作,实际业务按命中的规则返回需要的 operation 即可。不要在同一个 LineOperation 中同时设置 lineUpdate、lineExpand、linesMerge,也不要在同一个 price 中同时设置 percentageAdjustment 和 fixedPricePerUnit。
业务示例
仅更新商品行展示
适用于按商品、SKU、自定义属性或元数据命中规则后,只改标题、图片或价格。
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
if req == nil || req.Cart == nil {
return module.CartFlexFunctionResponse{}
}
title := "会员专享套装价"
discount := "-10"
operations := make([]*module.LineOperation, 0)
for _, line := range req.Cart.Lines {
if line == nil || line.Merchandise.Sku == nil || *line.Merchandise.Sku != "VIP-BUNDLE" {
continue
}
operations = append(operations, &module.LineOperation{
LineUpdate: &module.LineUpdateOperation{
LineItemId: line.Id,
Title: &title,
Price: &module.PriceAdjustment{
PercentageAdjustment: &module.PercentageAdjustment{Value: &discount},
},
},
})
}
return module.CartFlexFunctionResponse{Operations: operations}
}
展开套装商品
适用于一个父商品行代表一个套装,需要在购物车/结账中展示套装包含的子商品。
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
if req == nil || req.Cart == nil {
return module.CartFlexFunctionResponse{}
}
title := "旅行套装"
fixedPrice := "19.90"
for _, line := range req.Cart.Lines {
if line == nil || line.Merchandise.Product.Handle != "travel-bundle" {
continue
}
return module.CartFlexFunctionResponse{Operations: []*module.LineOperation{
{
LineExpand: &module.LineExpandOperation{
LineItemId: line.Id,
Title: &title,
ExpandedLineItems: []*module.ExpandedLineItem{
{
VariantId: "gid://shopline/ProductVariant/child-variant-1",
Quantity: 1,
Price: &module.PriceAdjustment{
FixedPricePerUnit: &module.FixedPricePerUnit{Amount: &fixedPrice},
},
},
{
VariantId: "gid://shopline/ProductVariant/child-variant-2",
Quantity: 2,
},
},
},
},
}}
}
return module.CartFlexFunctionResponse{}
}
合并多个商品行
适用于购物车里同时存在多件指定商品时,将它们展示为一个组合商品。
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
if req == nil || req.Cart == nil {
return module.CartFlexFunctionResponse{}
}
var topLine, bottomLine *module.LineItem
for _, line := range req.Cart.Lines {
if line == nil || line.Merchandise.Sku == nil {
continue
}
switch *line.Merchandise.Sku {
case "SET-TOP":
topLine = line
case "SET-BOTTOM":
bottomLine = line
}
}
if topLine == nil || bottomLine == nil {
return module.CartFlexFunctionResponse{}
}
title := "上下装组合"
return module.CartFlexFunctionResponse{Operations: []*module.LineOperation{
{
LinesMerge: &module.LinesMergeOperation{
ParentVariantId: "gid://shopline/ProductVariant/parent-set-variant",
Title: &title,
LineItems: []*module.MergeLineItem{
{LineItemId: topLine.Id, Quantity: 1},
{LineItemId: bottomLine.Id, Quantity: 1},
},
},
},
}}
}
注意事项
- lineItemId 必须使用输入中的购物车商品行 ID,不是商品 ID,也不是商品变体 ID。
- variantId 和 parentVariantId 使用商品变体 ID。
- 如果某个字段没有在 input.graphql 中声明,业务逻辑中不要依赖它有值。
- properties 是商品行维度的自定义属性。返回 properties 时建议同时提供 name 和 type;type 使用 text、picture、link,可见性优先使用 roleVisibility,仅在缺省或无效时由 show 兜底。
Cart Flex 规则管理
Cart Flex Function代码发布后,还需要创建 Cart Flex 规则,结算流程才会执行对应的 function。
Cart Flex 实例管理支持开发者为商家提供可视化界面,由商家自主配置结算环节的商品组合规则与页面展示样式,助力商家灵活制定各类商品营销玩法。
典型场景:商家可以在应用程序内创建一个Cart Flex 实例,当客户将相关的产品(如手机、蓝牙耳机和充电器)添加到购物车时,自动将他们组合成一个捆绑包行项目。
核心规则:
- 只有某个 function_id 存在对应 CartFlex 规则时,该 function_id 在购物车/结算流程中才会被执行。
- CartFlex 规则中的 metafield 会在函数入参 CartFlex.metafield 中传入。
- metafield 用于保存开发者/商家在具体店铺下的配置。SHOPLINE 不关心其中的业务内容和格式,只负责保存并在执行 Function 时透传给函数。
接口公共说明
以下接口用于管理 CartFlex 规则。调用时需要使用店铺访问令牌,并在请求头中传入:
| 请求头 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Content-Type | String | 是 | 固定为 application/json; charset=utf-8。 |
| Authorization | String | 是 | 店铺访问令牌,格式为 Bearer {access_token}。 |
接口路径中的 {handle} 是店铺域名前缀,例如店铺域名为 open001.myshopline.com,则 {handle} 为 open001。{version} 使用当前开放接口版本。
CartFlexCreate
创建接口用于为指定 function_id 创建 CartFlex 规则,并写入该规则的元数据配置。创建成功后,只要该规则存在,对应 function_id 才会进入购物车/结算执行链路。
POST https://{handle}.myshopline.com/admin/openapi/{version}/cartflex.json
权限点:write_cart_flex
请求体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| function_id | String | 是 | Function ID。通常是推送 Function 扩展后由 SHOPLINE 分配的函数标识。 |
| metafield | MetafieldInput | 是 | 要保存到该规则上的元数据配置。 |
MetafieldInput:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| key | String | 是 | 元数据 key,用于标识配置项。 |
| namespace | String | 否 | 元数据命名空间,用于区分不同业务域。 |
| type | String | 是 | 元数据类型。若配置内容是 JSON,建议使用 JSON 类型。 |
| value | String | 是 | 元数据值。SHOPLINE 不解析业务含义,只在 Function 执行时透传。 |
请求示例:
{
"function_id": "{function_id}",
"metafield": {
"key": "cart_flex_config",
"namespace": "cart_flex",
"type": "json",
"value": "{\"rules\":[] }"
}
}
响应体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| cart_flex_info | CartFlexInfo | 否 | 创建成功后的 CartFlex 规则信息。 |
CartFlexQuery
查询接口用于确认某个 function_id 或规则 ID 是否已经存在对应 CartFlex 规则,也可用于读取该规则当前保存的元数据。
GET https://{handle}.myshopline.com/admin/openapi/{version}/cartflex.json
权限点:read_cart_flex
请求体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| function_id | String | 否 | Function ID。用于查询某个函数在当前店铺下是否已有规则。 |
| id | String | 否 | CartFlex 规则 ID。用于查询指定规则。 |
请求示例:
{
"function_id": "{function_id}",
"id": "{cart_flex_customizition_id}"
}
响应体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| cart_flex_info | CartFlexInfo | 否 | 查询到的 CartFlex 规则信息。未查询到时表示该函数当前不会因为该规则被执行。 |
CartFlexDelete
删除接口用于删除指定 CartFlex 规则。删除后,该规则不再为对应 function_id 提供启用关系和元数据配置;如果该 function_id 不再存在对应规则,则购物车/结算流程不会执行该 function。
DELETE https://{handle}.myshopline.com/admin/openapi/{version}/cartflex/delete.json
权限点:write_cart_flex
请求体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | String | 是 | 要删除的 CartFlex 规则 ID。 |
请求示例:
{
"id": "{cart_flex_customizition_id}"
}
响应体:
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | String | 否 | 已删除的 CartFlex 规则 ID。 |
响应对象
CartFlexInfo
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| function_id | String | 否 | Function ID。 |
| id | String | 否 | CartFlex 规则 ID。后续查询或删除规则时使用。 |
| metafield | Metafield | 否 | 该规则当前保存的元数据。Function 执行时会作为 CartFlex.metafield 传入。 |
Metafield
| 字段 | 类型 | 必填 | 业务含义 |
|---|---|---|---|
| id | String | 否 | 元数据 ID。 |
| key | String | 否 | 元数据 key。 |
| namespace | String | 否 | 元数据命名空间。 |
| type | String | 否 | 元数据类型。 |
| value | String | 否 | 元数据值。SHOPLINE 不解析业务含义,只透传给 Function。 |
配置传递示例
创建规则时保存的元数据:
{
"key": "cart_flex_config",
"namespace": "cart_flex",
"type": "json",
"value": "{\"bundleRules\":[{\"handle\":\"travel-bundle\"}]}"
}
Function 执行时,会在 req.CartFlex.Metafield 中读取到同一份配置。开发者可以自行解析 value 中的 JSON,并按规则返回 operations。
func CartFlexFunction(req *module.CartFlexFunctionRequest) (result module.CartFlexFunctionResponse) {
if req == nil || req.CartFlex == nil || req.CartFlex.Metafield == nil {
return module.CartFlexFunctionResponse{}
}
config := req.CartFlex.Metafield.Value
_ = config
return module.CartFlexFunctionResponse{}
}
使用建议
- 先推送并发布 Cart Flex Function,拿到 function_id 后,再创建 CartFlex 规则。
- 应用后台可为商家提供配置页面,将商家配置序列化后写入规则的元数据 value。
- 不要把密钥、访问令牌、支付凭证等敏感信息写入元数据。
- 删除规则会影响对应 Function 是否进入购物车/结算执行链路,建议在应用后台提供明确的停用确认。
