跳转到正文
帮助与文档 联系支持

商城开放 API 接入文档

按步骤对接分类、图片、商品、会员价格、库存状态、余额采购和订单交付。

更新于
下载 Node.js 接入示例

这篇说明适合谁

开放平台与接口示例提供分类、图片、商品、价格与订单对接概览。本文是实际请求参数与签名规则的依据。

能力类似供货商城对接,但协议不是卡速售协议。 第三方可按本文开发导入、采购与状态同步;不能直接在第三方系统选择“卡速售”后只替换域名。图片、分类和商品可以读取,是否一键导入取决于对方是否实现卡速腾适配器。

适合希望用自己的程序查询商城商品、读取会员价、使用余额采购并查询交付结果的客户。API 是程序之间的接口;普通用户直接在商城下单,不需要配置这些内容。

接入的是您实际采购的商城。官网文档中心只提供说明,不接收采购请求,也不保存您的商城余额。

商城作为货源时,采购付款扣的是店主绑定的商城会员账户余额。 外部店铺已经收款,不等于商城采购已付款;不会直接扣外部平台钱包,也不会自动从外部销售收入划款。

开始前准备什么

准备项怎么取得或检查
实际商城域名使用采购所属主站或分站的 HTTPS 域名
正常的商城会员账户能登录该商城,账户未停用
客户 API 权限请商城管理员在「对接管理 → 客户 API 授权」检查当前权限
站点密钥会员进入「接口管理」生成 key.idsecret
商品与余额商品已发布、在您的可售范围内;余额足够支付测试订单
服务端程序签名和密钥保存在自己的服务器;下文提供 Node.js 18+ 示例

API 权限与会员等级、渠道供货权限分别管理。购买或续费会员不会自动开通 API;管理员可设置「默认开放客户 API 权限」,也可将某位客户设为「跟随默认」「手动允许」或「手动禁止」,客户单独设置优先。权限允许后仍需生成密钥。

在哪里配置

  1. 管理员:进入商城后台「对接管理 → 客户 API 授权」,确认该会员显示允许访问。需要验证码时,打开「密钥验证设置」,配置手机或邮箱验证。
  2. 会员:登录采购所属商城,进入用户中心「接口管理」或「开放 API」。直接入口为 https://商城域名/developer/#api
  3. 在页面生成密钥,保存返回的 key.idsecretsecret 只在生成时显示一次;重置会让旧密钥立即失效。
  4. 将密钥放入服务端环境变量,将接口域名设为实际商城域名。

后台未启用手机、邮箱验证时,已授权客户可直接获取、重置和撤销密钥。启用后需先绑定并验证对应联系方式;同时启用两种方式时任选一种。验证码有效期 5 分钟,仅用于当前操作,不能重复使用。验证渠道不可用时不会自动跳过验证。

密钥按会员和主站/分站绑定,服务端按访问域名确定站点。主站、分站应分别生成和使用密钥,不能跨站读取订单。请求中不需要也不能靠自行填写会员 ID、分站 ID 来切换扣款账户。

第一次怎样确认接入成功

  1. 先运行本文示例查询 /balance。返回的 memberId 应是您的商城会员,balanceCents 应与会员中心余额一致。例如 10000 是 100.00 元。
  2. 查询 /products 和商品详情,核对商品、SKU、会员价和所需下单资料。商品列表不是库存预留承诺。
  3. 用稳定且唯一的 reference 创建一笔您确认要采购的订单。此时应为 pending_payment,仅预占库存,尚未扣余额。
  4. 核对 totalAmountCents 后调用该订单的 /pay,这一步会真实扣商城余额。
  5. 查询原订单和 /fulfillmentpaid 只表示付款成功;继续确认履约任务完成、交付内容正确,并在「我的订单」「资金中心」核对一笔订单和一笔扣款。

创建或付款超时,不要直接换业务单号重新采购。先查原订单,再按下文重试规则处理。本文默认运行的 Node.js 示例只查余额。

接口约定与完整路径

接口根地址:https://商城域名/openapi/v1
传输:HTTPS
请求与响应:JSON,UTF-8
金额:整数分,CNY;100 分 = 1.00 元
方法完整路径用途
GET/openapi/v1/categories查询当前站点分类
GET/openapi/v1/products查询商品列表与当前账户价格
GET/openapi/v1/products/{id}查询商品详情、SKU 和下单模板
GET/openapi/v1/product-statuses?ids=ID1,ID2批量检查商品可见性及库存状态,需商城 0.1.248+
GET/openapi/v1/balance查询当前会员商城余额
POST/openapi/v1/orders创建待付款订单
POST/openapi/v1/orders/{id}/pay使用当前会员商城余额付款
GET/openapi/v1/orders/{id}按商城订单 ID 查询
GET/openapi/v1/order-references/{reference}按客户业务单号查询
GET/openapi/v1/orders/{id}/fulfillment查询履约进度和交付内容

{id} 用真实商品 ID 或订单 ID 替换,不是商品名称或订单展示编号。路径参数须 URL 编码。成功响应外层为 data,失败一般为 error;不同接口的 data 结构见下文。

请求签名

每个请求带以下四个请求头:

请求头内容
X-API-Key生成时返回的 key.id,当前为 32 位字符串
X-API-TimestampUnix 秒级时间戳,与服务端相差不超过 300 秒
X-API-Nonce每次请求新生成的随机字符串,16–128 位,仅英文字母、数字、_-
X-API-SignatureHMAC-SHA256 十六进制签名,示例输出小写

签名原文为以下六行,以 LF(\n)连接,最后一行后不加换行:

HTTP_METHOD
ESCAPED_PATH
CANONICAL_QUERY
TIMESTAMP
NONCE
HEX_SHA256_BODY
  • HTTP_METHOD:实际大写方法,如 GETPOST
  • ESCAPED_PATH:实际 URL 的转义路径,包含 /openapi/v1,不含域名或查询串。不要把 %2F 先解码成 /
  • CANONICAL_QUERY:参数名按 Go url.Values.Encode() 的排序和编码规则处理;按键升序,重复键的值保留原顺序。表单编码中空格为 +,特殊字符为百分号编码;没有查询参数时保留空行。
  • HEX_SHA256_BODY:对实际发出的原始请求体字节求 SHA256。GET 不发送请求体时,对空字节串求哈希。
  • HMAC 密钥为 secret 字符串本身的 UTF-8 字节。虽然当前 secret 看起来是 64 位十六进制字符串,不能先进行十六进制解码

JSON 只序列化一次,签名和发送使用同一份字节。请求体上限为 1 MiB(1,048,576 字节)。每个密钥每个服务端 UTC 分钟最多接受 120 次认证请求,同一 nonce 在 10 分钟内不能重复。业务失败后的重试也使用新 nonce,但原采购业务号保持不变。429 时按 Retry-After 等待。

分类、商品和余额

查询分类

GET /openapi/v1/categories

data.items 为当前站点可见分类。分类和商品仍受当前站点经营范围、会员规则限制。

分类返回 idparentIdcodenameimageUrlsortOrderrecommendedlabelagentPriceEnabled。用 idparentId 重建分类树,根分类可能不返回 parentId。保留外部 ID 映射,分类同名时不能靠名称覆盖。

查询商品列表

GET /openapi/v1/products?q=月卡&categoryId=CATEGORY_ID&limit=20&offset=0
参数规则
q可选,商品关键词;服务端按最多 64 字符清理
categoryId可选,分类 ID,不超过 64 字节
limit每页 1–100 条,默认 20
offset从 0 开始,最大 100000

响应 data 包含 itemstotallimitoffset。需要全部商品时持续分页,不要把第一页当作全部。列表商品的标识是 productId;价格字段包括 minPriceCentsmaxPriceCents,下单 SKU 及具体单价从详情读取。

要同步的内容读取字段与规则
商品与目录productIdproductCodenamesummarycategoryIdcategoryName
商品图片coverUrl 是封面;为空时使用自己的默认图片,不代表接口故障
价格minPriceCentsmaxPriceCents 为当前会员报价,金额单位为分
库存状态商品和 SKU 的 stockStatusavailable 为有货,out_of_stock 为缺货;不是精确库存数量,也不预留库存
规格与下单资料详情 skus 和解析后的 contentJson.checkoutFields

imageUrlcoverUrl 可能是完整的 HTTPS 地址,也可能是 /media/... 相对地址。相对地址应基于采购商城域名解析,例如 new URL(imagePath, KST_API_BASE).href,不要拼接官网 kasuteng.cn。详情中的其他图片以商家实际发布的 contentJson 为准,并非所有商品都有统一图库数组。若自行下载图片,应限定 HTTP/HTTPS、文件类型、大小和超时,不向图片服务器发送 API 密钥。

批量检查商品状态

商城 0.1.248+ 提供:

GET /openapi/v1/product-statuses?ids=product-001,product-002

ids 为 1–20 个不同商品 ID,用英文逗号分隔;只允许传一个 ids 参数。每个 ID 最长 64 字节,不含空白、控制字符和斜杠。使用与其他接口相同的签名、权限及限流。

{
  "data": {
    "items": [
      {"productId": "product-001", "status": "published", "stockStatus": "available"},
      {"productId": "product-002", "status": "unavailable", "stockStatus": "unknown"}
    ],
    "checkedAt": "2026-09-22T08:00:00Z"
  }
}
  • published:对当前站点和会员可见的已发布商品,库存单独判断。仍需遵守营业时间、限购、实名及其他下单规则,不等于保证可购买。
  • unavailable:商品不存在、已下架、已删除、分类不可见、账户无权访问或暂无有效报价。为保护经营范围,接口不区分这些原因;下游应暂停该商品采购,不要删除已有订单与映射。
  • stockStatusavailable / out_of_stock;不可见商品返回 unknowncheckedAt 是本次检查时间,不是商品更新时间。
  • 接口整体失败返回非 2xx,例如依赖服务异常返回 503。失败时保留上次状态并重试,不能把所有商品当作下架。

建议首次全量分页导入分类、图片、商品和 SKU;随后定时拉取价格与已映射商品状态。仅当完整分页成功后再比较商品集合,不能因为某页超时、权限错误或只拿到第一页就批量下架。批量状态接口一次响应不是跨多个商品的数据库快照,付款前仍以服务端订单校验为准。目前本流程使用定时查询,不承诺商品变化实时推送。

查询商品详情

GET /openapi/v1/products/PRODUCT_ID

data.product 为商品,data.skus 为 SKU 数组。SKU 读取 skuIdpriceCentsstockStatus。商品必须已审核发布,且在当前站点和账户可售范围内。

下单模板位于 data.contentJson,它是 JSON 字符串,与 productskus 同级。 解析后读取 checkoutFields,不要从 data.product.contentJson 取值。

const detail = await callAPI('/openapi/v1/products/' + encodeURIComponent(productId));
const content = detail.contentJson ? JSON.parse(detail.contentJson) : {};
const fields = content.checkoutFields || [];

按模板的字段名、类型、必填项、长度和选项收集资料。account 只是充值商品的常见字段,不代表所有商品都要求该字段。返回价格按当前有效会员等级和站点规则计算,不返回供货成本或其他客户的密钥。

查询余额

GET /openapi/v1/balance
{"data":{"memberId":"MEMBER_ID","balanceCents":10000,"currency":"CNY"}}

这是当前会员的商城账户余额。示例数字只说明结构,不代表实际账户金额。

创建订单

POST /openapi/v1/orders
Content-Type: application/json

{
  "reference": "my-order-20260921-001",
  "maxAmountCents": 5000,
  "items": [{"skuId": "SKU_ID", "quantity": 1}],
  "checkoutData": {"SKU_ID": {"account": "TARGET_ACCOUNT"}}
}
字段必填规则
reference客户业务单号,使用 8–80 位不含空格的可打印 ASCII;建议仅字母、数字、_-,并保持原值
maxAmountCents整单最高可接受金额,整数分,1–10,000,000,000;不是指定成交价
items1–100 个明细,不可重复 SKU
items[].skuId实际 SKU ID,1–64 字节,无首尾空白
items[].quantity1–999 的整数;还需满足实际商品起购、限购、购买倍数等规则
checkoutData按商品要求对象,以 SKU ID 为键;内容按该商品 checkoutFields 填写,无资料要求可省略

服务端按实际账户、站点、商品、库存与购买规则重新报价,整单商品数量还受 9999 上限约束。超过安全金额会拒绝,不接受客户端自定商品单价。

首次成功 HTTP 201,相同请求重放 HTTP 200:

{
  "data": {
    "order": {
      "id": "ORDER_ID",
      "orderNo": "API_DISPLAY_NUMBER",
      "status": "pending_payment",
      "currency": "CNY",
      "totalAmountCents": 2000,
      "items": [{"id":"ITEM_ID","productId":"PRODUCT_ID","skuId":"SKU_ID","name":"账户充值","skuName":"标准规格","quantity":1,"unitPriceCents":2000,"subtotalCents":2000}],
      "createdAt": "2026-09-21T00:00:00Z",
      "expiresAt": "2026-09-21T00:30:00Z"
    },
    "replayed": false
  }
}

时间仅为结构示例,付款以实际 expiresAt 为准。新订单 pending_payment 只预占库存,尚未扣余额。

同一会员、同一销售站点的 reference 是幂等业务号:相同请求返回原订单;更改商品、数量、资料或最高金额后沿用旧号,返回 idempotency_conflict。请求超时可能已创建成功,先按原 reference 查单。

付款、查单和交付

使用商城余额付款

POST /openapi/v1/orders/ORDER_ID/pay
Content-Type: application/json

{}

响应为 data.orderdata.replayed。服务端从该订单所属会员的商城余额扣款,重复成功付款请求不会重复扣款。余额不足时,可充值后在原订单有效期内重试该订单付款;如已有待完成在线支付单,应继续原支付流程。

查询原订单

GET /openapi/v1/orders/ORDER_ID
GET /openapi/v1/order-references/my-order-20260921-001

这两个接口的 data 直接是订单对象,不是 data.order。只允许本会员、当前站点的订单。按业务号查询时,使用 encodeURIComponent(reference) 编码路径段。

订单状态含义
pending_payment待付款
paid已付款,仍需检查交付
cancelled / closed已取消或关闭,不能按正常待付款单继续支付
refunding退款处理中,不等于已退款
refunded已退款

查询履约与交付内容

GET /openapi/v1/orders/ORDER_ID/fulfillment

data 包含 orderIdtasksdeliveries。任务 status 可能为 pendingprocessingdeliveredfailedmanualcancelled;交付记录包含 taskIddeliveryTypecontentcontentSha256createdAt 等字段。按任务和交付记录核对结果,空数组不代表已交付。

卡密等内容仅保存在自己的服务端,避免写入公开日志。适当间隔轮询;上游状态未知、人工处理或履约失败时先查单或提交工单,不要重新采购。该查询接口读取已有交付记录,不承诺像渠道供货接口一样在退款状态下隐藏历史内容;接入方应先检查订单状态,退款处理中停止向外分发。

售后与退款

当前 /openapi/v1 没有取消订单、申请退款或退款通知接口。售后在商城「工单售后」处理,提交 reference、商城订单 ID/编号、问题和错误 requestId,不要提交 secret 或完整卡密。

可用入口:

/pc/#orders   我的订单
/pc/#funds    资金中心
/support/    工单售后

退款申请不等于资金已退。确认退款以商城订单、支付、退款记录和钱包流水为准;余额订单确认退款后退回原商城会员余额。已实际交付的卡密、直充不能通过当前退款确认流程直接退款。授权类商品另有授权撤销校验,不能概括为所有已交付商品一律同规则。

错误处理

{"error":{"code":"api_disabled","message":"API 授权未开通、已停用或密钥已撤销","requestId":"REQUEST_ID"}}

下表为主要错误,不是所有公共中间件和业务模块错误的穷举。保留 HTTP 状态、error.codemessagerequestId,不要只按提示文字判断。

HTTPcode处理方法
400api_invalid_input / invalid_request检查参数、JSON 字段和类型;不要多传未定义字段
401api_signature_invalid检查时钟、key/secret、站点、路径、查询编码、原始正文;撤销的密钥也可能返回此码
403api_disabled检查会员状态及当前有效 API 权限
403identity_required按商品要求完成实名认证
404api_not_found / order_not_found / catalog_not_found / payment_not_found核对对象 ID、站点、账户和商品是否可见
409api_nonce_replayed用新 nonce 重试,业务号不变
409api_conflict授权或密钥已变更,刷新密钥管理状态
409idempotency_conflict原业务号参数已固定,先核对原订单
409price_limit_exceeded / price_unavailable重新确认报价,核对原单后再决定是否发起新采购
409insufficient_stock / product_unavailable检查库存、商品与销售条件
409wallet_insufficient充值后在有效期内重试原订单付款
409online_payment_pending继续原在线支付流程,不重复付款
409invalid_payment_state / invalid_order_state查询原订单和支付状态,停止盲目重试
422invalid_quantity / invalid_idempotency_key / invalid_checkout_data修正数量、业务号或下单资料;已存在订单的业务号不能改参数重用
413api_body_too_large请求体超过 1 MiB
429api_rate_limitedRetry-After 退避,当前为 60 秒
503open_api_unavailable / catalog_unavailable / trade_unavailable / payment_unavailable / fulfillment_unavailable保留原业务号与 requestId,稍后查原单或联系管理员

完整 Node.js 签名示例

下列 mall-api-client.mjs 可在 Node.js 18 或更高版本运行,无需第三方依赖。默认只读取余额,不会创建订单或付款。它与仓库 docs/examples/mall-api-client.mjs 使用相同签名协议,此处直接提供完整代码,避免依赖页面附件。

import { createHash, createHmac, randomBytes } from 'node:crypto';
import { pathToFileURL } from 'node:url';

function formEscape(value) {
  return encodeURIComponent(value)
    .replace(/[!'()*]/g, char => '%' + char.charCodeAt(0).toString(16).toUpperCase())
    .replace(/%20/g, '+');
}

export async function callAPI(path, { method = 'GET', body } = {}) {
  const { KST_API_BASE, KST_API_KEY, KST_API_SECRET } = process.env;
  if (!KST_API_BASE || !KST_API_KEY || !KST_API_SECRET) {
    throw new Error('缺少 KST_API_BASE / KST_API_KEY / KST_API_SECRET');
  }
  const base = new URL(KST_API_BASE);
  if (base.protocol !== 'https:' || base.username || base.password) {
    throw new Error('KST_API_BASE 必须为实际商城的 HTTPS 地址');
  }
  const target = new URL(path, base);
  if (target.origin !== base.origin || target.username || target.password ||
      target.hash || !target.pathname.startsWith('/openapi/v1/')) {
    throw new Error('API 路径无效');
  }
  method = method.toUpperCase();
  if (!['GET', 'POST'].includes(method) || (method === 'GET' && body !== undefined)) {
    throw new Error('方法或请求体无效');
  }

  // 按 Go 的 UTF-8 键排序,重复键维持原顺序。
  const pairs = [...target.searchParams].sort(([a], [b]) =>
    Buffer.compare(Buffer.from(a), Buffer.from(b)));
  const query = pairs.map(([key, value]) =>
    `${formEscape(key)}=${formEscape(value)}`).join('&');
  target.search = query;
  const raw = body === undefined ? '' : JSON.stringify(body);
  if (typeof raw !== 'string' || Buffer.byteLength(raw) > 1048576) {
    throw new Error('请求体无效或超过 1 MiB');
  }
  const timestamp = String(Math.floor(Date.now() / 1000));
  const nonce = randomBytes(16).toString('hex');
  const canonical = [method, target.pathname, query, timestamp, nonce,
    createHash('sha256').update(raw, 'utf8').digest('hex')].join('\n');
  const signature = createHmac('sha256', KST_API_SECRET)
    .update(canonical, 'utf8').digest('hex');

  // 超时可能发生在订单提交后;这里不自动重复采购。
  const response = await fetch(target, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': KST_API_KEY,
      'X-API-Timestamp': timestamp,
      'X-API-Nonce': nonce,
      'X-API-Signature': signature
    },
    body: body === undefined ? undefined : raw,
    signal: AbortSignal.timeout(20000),
    redirect: 'error'
  });
  let result;
  try { result = await response.json(); }
  catch { throw new Error(`HTTP ${response.status}:响应不是合法 JSON,请查询原订单`); }
  if (!response.ok || result?.error) {
    const error = new Error(`HTTP ${response.status} ${result?.error?.code || ''}: ${result?.error?.message || '请求失败'}`);
    error.requestId = result?.error?.requestId;
    error.retryAfter = response.headers.get('Retry-After');
    throw error;
  }
  return result.data;
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  callAPI('/openapi/v1/balance')
    .then(data => console.log(JSON.stringify(data, null, 2)))
    .catch(error => {
      console.error(error.message, error.requestId || '');
      process.exitCode = 1;
    });
}

Linux / macOS:

export KST_API_BASE='https://您的商城域名'
export KST_API_KEY='生成时返回的key.id'
export KST_API_SECRET='生成时显示的secret'
node mall-api-client.mjs

Windows PowerShell:

$env:KST_API_BASE = 'https://您的商城域名'
$env:KST_API_KEY = '生成时返回的key.id'
$env:KST_API_SECRET = '生成时显示的secret'
node mall-api-client.mjs

在自己的 Node.js 服务端脚本中查询商品:

import { callAPI } from './mall-api-client.mjs';

try {
  const products = await callAPI('/openapi/v1/products?limit=20&offset=0');
  console.log(products);
} catch (error) {
  console.error(error.message, error.requestId || '');
  process.exitCode = 1;
}

不得将密钥写入公开网页、APP 客户端、代码仓库或日志。阿奇索、闲管家供货协议和平台授权部署接口使用各自的路径、权限和签名,不能套用此处 HMAC 签名。本文说明当前实现,不代表您的生产域名、账户或真实交付渠道已经验收。