For AI agents: the complete documentation index is available at https://docs.halo.run/llms.txt, the full documentation bundle is available at https://docs.halo.run/llms-full.txt, and this page is available as Markdown at https://docs.halo.run/developer-guide/shop/theme/storefront-api.md.

前台接口与交易流程

本页以 Halo 商城版 2.27 为基线,面向需要自行实现交互的主题。仅调整页面外观时,优先使用内置组件。2.27 新增的游客购物车、结算与订单访问能力不能直接用于旧版后端。

商品、订阅和结算等 RESTful API 的请求字段与响应模型见 Halo 商城版 2.27 RESTful API,可在 User-center API V1alpha1 中查找 EcommerceProductUc、EcommerceSubscriptionUc、EcommerceCheckoutUc 等标签。该站点的 2.27 定义不包含本页列出的 /shop/** 页面与交易路由,这部分应结合匹配版本的 shop-templates 使用。

选择接口与身份

接口范围典型用途
/shop/**浏览器前台页面、会话购物车、结算表单、订单与支付操作
/apis/uc.api.ecommerce.halo.run/v1alpha1/**商品查询、订阅计划、登录用户地址簿和优惠券等
Console 商城 API管理端操作,不应作为顾客前台的调用入口

不能仅凭 uc.api 前缀推断接口必须登录或允许匿名,具体由接口决定。商品详情和订阅商品公开计划可供匿名访问;用户地址簿和客户优惠券依赖登录身份。

普通前台购物车和交易使用当前会话。店铺启用游客结算后,游客可购买支持游客流程的商品;未开启时,需要先登录。订阅商品不支持游客购买,应引导登录后继续。

浏览器请求应与 Halo 保持同源并携带会话 Cookie。不要把管理端 Personal Access Token 写入主题脚本。

普通 HTML 表单带上隐藏字段:

<input type="hidden" name="_csrf" th:value="${_csrf.token}" />

JavaScript 写请求可以使用内置组件采用的 X-XSRF-TOKEN 请求头。下面的函数可放在商品详情模板中,调用时传入用户选中的 SKU ID 与数量:

<script th:inline="javascript">
  const csrfToken = /*[[${_csrf.token}]]*/ "";

  async function addToCart(productVariantId, quantity) {
    const response = await fetch("/shop/cart/items", {
      method: "POST",
      credentials: "same-origin",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        "X-XSRF-TOKEN": csrfToken,
      },
      body: JSON.stringify({ productVariantId, quantity }),
    });

    if (!response.ok) {
      throw new Error("加入购物车失败,请刷新页面后重试");
    }
    return response.json();
  }
</script>

调用方需要显示成功或失败结果。CSRF 失效时应让用户刷新当前页面,不要复用其他会话的令牌。

查询商品与订阅计划

常用只读接口:

方法与路径用途
GET /apis/uc.api.ecommerce.halo.run/v1alpha1/products/{id}?includeVariants=true获取商品详情与 SKU
GET /apis/uc.api.ecommerce.halo.run/v1alpha1/subscription-products/{id}获取公开订阅计划及当前登录客户的订阅信息
GET /apis/uc.api.ecommerce.halo.run/v1alpha1/ecommerceconfigs/shop获取店铺询价地址和联系信息

订阅商品响应包含 plans、currentSubscription、subscriptions 和 singleSubscription。游客获得公开计划,客户订阅为空。新购订阅使用所选计划的 variantId;切换已有订阅是另一条业务流程,不能简单当作再买一次。可以复用内置购买组件,或引导到 /uc/shop/subscriptions/{subscriptionId}/change?planId={planId} 处理变更。

购物车

2.27 的前台购物车接口同时支持登录用户和开启游客结算后的游客会话:

方法与路径数据
GET /shop/cart/items返回当前购物车商品列表
POST /shop/cart/itemsJSON:productVariantId、quantity
PUT /shop/cart/items/{id}JSON:更新该购物车项的数量
DELETE /shop/cart/items/{id}删除购物车项,成功返回 204
POST /shop/cart表单:productVariantId、quantity、_csrf;成功跳转到购物车

登录用户的购物车项 id 是购物车记录的 ID;游客会话使用 SKU ID 作为购物车项 id。两种身份都应使用 GET /shop/cart/items 返回的 id 更新或删除购物车项,不要自行推导标识。

准备结算

POST /shop/checkout/prepare 接受 application/x-www-form-urlencoded 表单。成功后由服务端建立会话结算上下文,并重定向到 /shop/checkout/{contextId}。

立即购买时,source=BUY_NOW,每一项传 SKU ID 和数量:

<form action="/shop/checkout/prepare" method="post">
  <input type="hidden" name="_csrf" th:value="${_csrf.token}" />
  <input type="hidden" name="source" value="BUY_NOW" />
  <label
    >SKU ID
    <input type="number" name="items[0].productVariantId" min="1" required
  /></label>
  <label
    >数量
    <input type="number" name="items[0].quantity" min="1" value="1" required
  /></label>
  <button type="submit">立即购买</button>
</form>

上例便于在开发环境验证表单字段。正式购买界面应把 SKU ID 输入替换为规格选择产生的隐藏字段,并显示价格、库存与数量限制。

购物车结算使用 source=CART,对应字段为 items[0].cartItemId 和 items[0].quantity;多项依次使用 [1]、[2] 等索引。cartItemId 使用当前购物车接口返回的 id,登录用户和游客都遵循这一规则。两个来源不要混用字段。

如果在动作地址上附加 redirect_uri 以便登录后返回当前页面,使用 encodeURIComponent 编码完整 URL,保留原页面的查询参数。

读取和提交结算

方法与路径用途
GET /shop/checkout/{contextId}/context读取当前会话结算上下文和计算结果
POST /shop/checkout/{contextId}/discountJSON { "code": "优惠码" },应用折扣
DELETE /shop/checkout/{contextId}/discount移除折扣
PUT /apis/uc.api.ecommerce.halo.run/v1alpha1/checkout/{contextId}/coupons登录客户应用优惠券,JSON 字段为 customerCouponIds
POST /shop/checkout/{contextId}/submit提交结算表单并创建订单

结算提交同样使用表单编码。主要字段包括:

  • items[i].productVariantId、items[i].quantity。
  • selectedAddressId,或 userAddress.* 中的新地址字段。
  • customerNotes、saveAsNewAddress。
  • 游客的 email,以及 _csrf。

模型中还包含 items[i].selectedShippingRateId,但 Halo 商城版 2.27 尚未实现配送方式选择与运费计算,不应依赖该字段完成配送计费。

地址字段和必填约束应按 API 模型 CheckoutSubmitRequest、UserAddressUpsertRequest 处理。游客不能借用用户地址簿,提交时使用自己的联系信息;订阅商品要求登录。

展示金额时读取结算响应中的 calculateResult,优惠券可能部分成功,应同时处理 appliedCoupons 与 rejectedCoupons。重新计算失败时,保留用户已填写的表单并展示错误,不要把已有内容替换成空白页。

提交成功返回重定向:待支付订单进入支付页,已经支付的订单进入对应订单页。游客跳转还可能携带访问令牌,应遵循服务端返回地址。使用原生表单可以直接完成页面跳转;若改用 fetch,需要自行处理浏览器导航,不能把最终 HTML 当作 JSON 解析。

支付

订单创建后按以下顺序处理:

  1. GET /shop/order/{orderCode},携带 Accept: application/json 获取订单。
  2. GET /shop/order/{orderCode}/payment-methods,获取该订单可用支付方式。
  3. POST /shop/order/{orderCode}/initiate-payment,JSON 包含 paymentMethodId、returnUrl,按需提供 preferredResponseType。
  4. 按响应中的支付渠道、响应类型和 payload 展示二维码、表单、跳转或人工付款说明。
  5. 使用返回的 sessionCode 请求 GET /shop/order/{orderCode}/payment-sessions/{sessionCode}/status,在终态停止轮询并更新订单显示。

不要把支付链接返回、打开支付 App 或回到 returnUrl 当作已付款。支付结果以服务端状态为准,人工转账类方式还需等待确认。切换支付方式产生新会话后,轮询应跟随新的 sessionCode,重新计算超时。

微信 H5、微信内置浏览器、桌面二维码和支付宝表单跳转的处理不同。没有自行实现这些分支的需求时,保留内置支付组件。

游客订单与履约

2.27 中,游客订单依赖下单后或邮件中的访问凭据。有效的 access 查询参数由服务端处理并写入订单访问 Cookie,再跳转到不含该参数的地址。主题应保留这一导航过程,不要把订单号本身视为授权凭据。

无权限或凭据失效时,订单 JSON 请求可能返回 404;HTML 订单页则可能提供 locked=true 的找回界面。自定义支付页应提供找回入口,而非只针对 401、403 处理。

方法与路径用途
POST /shop/order/{orderCode}/access-link提交下单邮箱重新获取访问邮件
POST /shop/order/{orderCode}/cancel取消允许取消的订单
GET /shop/order/{orderCode}/fulfillments获取履约信息
GET /shop/order/{orderCode}/digital-resources/{publicId}/download通过订单授权下载数字资源
GET /shop/order/{orderCode}/items/{orderItemId}/cdks获取订单项对应的卡密

找回接口支持表单和 JSON。JSON 请求使用 Accept: application/json,成功返回 204;提示应为“如果邮箱与订单一致,访问链接已发送”,不要宣称已验证订单归属或一定发送成功。

订单金额、支付状态、可取消状态和履约结果都应使用服务端数据。主题只负责呈现与发起操作,不在浏览器中推导最终授权或支付成功状态。