精确单价与数量限制
新版在旧整分字段旁返回十进制字符串。商城 0.1.377 起最多支持八位有效小数,客户端不得先转为两位浮点金额,也不能改换商品单位来缩小数量。
| 场景 | 精确字段 | 兼容字段 |
|---|---|---|
| 商品/采购规格报价 | price | priceCents |
| 商品最低/最高报价 | minPrice、maxPrice | minPriceCents、maxPriceCents |
| 购物车与开放采购订单行 | unitPrice | unitPriceCents |
| 顾客订单冻结快照 | snapshot.unitPrice | unitPriceCents |
例如 price: "0.000201" 对应兼容 priceCents: 1。新客户端必须使用精确字符串,不能按 1 × 数量 计算应付款。缺少精确字符串的历史整分商品继续读取旧字段。顾客响应不返回精确采购成本。
精确单价乘数量后整单合计一次向上取分:0.000300 × 101 收 4 分,两行相同金额合计收 7 分。行 subtotalCents 是服务端分配结果,总计等于实收;付款以服务端 totalAmountCents 为准,退款与重放使用原冻结快照。
商品 contentJson.purchaseRules 以及新版目录规格/购物车的 purchaseRules 包含 minQuantity、maxQuantity、quantityStep,具体封装以对应接口为准。客户端保存规则并在购买页显示和校验,服务端下单再次验证。每行及整单硬上限 1000000000,实际可用数量仍由商品规则、货源协议及库存限制;没有明确规则的旧商品保留默认 1000000。本地卡密最多 1023,规格表单仍需完整填写。不得把供货平台的原始 -1 直接作为商城下单数量,应使用商城接口归一化后的购买规则。
同系统双方均需升级至支持精确价格的版本才能完整使用不足一分报价;七、八位单价和超过旧上限的商品要求双方使用 0.1.377 或更新版本。旧客户端只读整数分字段会丢失语义,其自行计算的金额不能作为服务端承诺价格;旧已生成订单仍按原快照结算。
这篇说明适合谁
开放平台与接口示例提供分类、图片、商品、价格与订单对接概览。本文是实际请求参数与签名规则的依据。
能力类似供货商城对接,但协议不是卡速售协议。 第三方可按本文开发导入、采购与状态同步;不能直接在第三方系统选择“卡速售”后只替换域名。图片、分类和商品可以读取,是否一键导入取决于对方是否实现卡速腾适配器。
卡速腾商城之间可直接选择“卡速腾”渠道对接,无需另写程序。 双方升级至商城 0.1.260 或更新版本后,在采购方后台「对接管理 → 货源渠道」新建渠道,选择列表第一项「卡速腾(同系统)」。填写供货商城的 HTTPS 域名、会员 API Key 和 API Secret,保存并测试连接,然后进入「对接商品」同步目录、选择商品并导入。
支持分类层级与顺序、分类图片、商品封面与相册、详情、规格属性、会员价格和下单表单。导入时可沿用上游目录,也可指定商城分类;图片可使用上游链接或现有图片转存方式。同一父商品可以保留多规格,来源按稳定规格代码绑定;未知或不同模板不会靠丢字段强行合组。新版对接任务可选择自动上架新商品,已经编辑的商户商品及旧任务保持原有发布设置。可售状态和价格会进入货源同步记录,实际采购由上游再次核验;接口不提供精确库存数量。系统版本、会员开通、分站开通等站内专用商品不作为普通货源商品导入。
双方升级至0.1.353+后,同系统优先按100商品一页读取完整目录和所有规格,只有上游明确没有新路由时才兼容旧列表加详情。错误、权限失败和未闭合分页不会当作旧版或空目录。当前同系统全量对接有界容量为10000商品、20000规格,同时受单响应4MiB和持久同步快照8MiB影响;图文较大的目录可能先触及字节上限,超限保留旧目录并明确失败,不截断后冒称完整。
采购使用您在供货商城的会员余额,客户在您自己商城付款不会自动给上游充值。同步任务在后台分批执行,关闭页面后可回来查看进度;同步不完整时保留原目录。订单超时先查原单,未付款原单确认后可继续余额支付,不会另建一笔采购。
适合希望用自己的程序查询商城商品、读取会员价、使用余额采购并查询交付结果的客户。API 是程序之间的接口;普通用户直接在商城下单,不需要配置这些内容。
接入的是您实际采购的商城。官网文档中心只提供说明,不接收采购请求,也不保存您的商城余额。
商城作为货源时,采购付款扣的是店主绑定的商城会员账户余额。 外部店铺已经收款,不等于商城采购已付款;不会直接扣外部平台钱包,也不会自动从外部销售收入划款。
开始前准备什么
| 准备项 | 怎么取得或检查 |
|---|---|
| 实际商城域名 | 使用采购所属主站或分站的 HTTPS 域名 |
| 正常的商城会员账户 | 能登录该商城,账户未停用 |
| 客户 API 权限 | 请商城管理员在「对接管理 → 客户 API 授权」检查当前权限 |
| 站点密钥 | 会员进入「接口管理」生成 key.id 和 secret |
| 商品与余额 | 商品已发布、在您的可售范围内;余额足够支付测试订单 |
| 服务端程序 | 签名和密钥保存在自己的服务器;下文提供 Node.js 18+ 示例 |
API 权限与会员等级、渠道供货权限分别管理。购买或续费会员不会自动开通 API;管理员可设置「默认开放客户 API 权限」,也可将某位客户设为「跟随默认」「手动允许」或「手动禁止」,客户单独设置优先。权限允许后仍需生成密钥。
在哪里配置
- 管理员:进入商城后台「对接管理 → 客户 API 授权」,确认该会员显示允许访问。需要验证码时,打开「密钥验证设置」,配置手机或邮箱验证。
- 会员:登录采购所属商城,进入用户中心「接口管理」或「开放 API」。直接入口为
https://商城域名/developer/#api。 - 在页面生成密钥,保存返回的
key.id与secret。secret只在生成时显示一次;重置会让旧密钥立即失效。 - 将密钥放入服务端环境变量,将接口域名设为实际商城域名。
后台未启用手机、邮箱验证时,已授权客户可直接获取、重置和撤销密钥。启用后需先绑定并验证对应联系方式;同时启用两种方式时任选一种。验证码有效期 5 分钟,仅用于当前操作,不能重复使用。验证渠道不可用时不会自动跳过验证。
密钥按会员和主站/分站绑定,服务端按访问域名确定站点。主站、分站应分别生成和使用密钥,不能跨站读取订单。请求中不需要也不能靠自行填写会员 ID、分站 ID 来切换扣款账户。
第一次怎样确认接入成功
- 先运行本文示例查询
/balance。返回的memberId应是您的商城会员,balanceCents应与会员中心余额一致。例如10000是 100.00 元。 - 查询
/products和商品详情,核对商品、SKU、会员价和所需下单资料。商品列表不是库存预留承诺。 - 用稳定且唯一的
reference创建一笔您确认要采购的订单。此时应为pending_payment,仅预占库存,尚未扣余额。 - 核对
totalAmountCents后调用该订单的/pay,这一步会真实扣商城余额。 - 查询原订单和
/fulfillment。paid只表示付款成功;继续确认履约任务完成、交付内容正确,并在「我的订单」「资金中心」核对一笔订单和一笔扣款。
创建或付款超时,不要直接换业务单号重新采购。先查原订单,再按下文重试规则处理。本文默认运行的 Node.js 示例只查余额。
接口约定与完整路径
接口根地址:https://商城域名/openapi/v1
传输:HTTPS
请求与响应:JSON,UTF-8
金额:整数分,CNY;100 分 = 1.00 元| 方法 | 完整路径 | 用途 |
|---|---|---|
| GET | /openapi/v1/categories | 查询当前站点分类 |
| GET | /openapi/v1/products | 查询商品列表与当前账户价格 |
| GET | /openapi/v1/catalog/products | 批量读取完整商品、所有可报价规格、表单及数量规则;需商城0.1.353+,仅明确路由缺失使用原列表+详情 |
| GET | /openapi/v1/products/{id} | 查询商品详情、SKU 和下单模板 |
| GET | /openapi/v1/products/by-number/{productNumber} | 按本商城短商品编号查询详情;需商城 0.1.325 及更新版本 |
| 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 | 查询履约进度和交付内容 |
| GET | /openapi/v1/orders/{id}/items/{itemId}/supplier-actions | 查询原订单明细顾客操作,0.1.353+ |
| POST | /openapi/v1/orders/{id}/items/{itemId}/verification-code | 向原采购单提交验证码,0.1.353+ |
| POST | /openapi/v1/orders/{id}/cancel | 取消未付款订单,需商城 0.1.260+ |
| POST | /openapi/v1/orders/{id}/refunds | 提交退款审核申请,需商城 0.1.260+ |
| GET | /openapi/v1/orders/{id}/refunds | 查询订单退款记录,需商城 0.1.260+ |
{id} 用真实商品 ID 或订单 ID 替换,不是商品名称或订单展示编号。路径参数须 URL 编码。成功响应外层为 data,失败一般为 error;不同接口的 data 结构见下文。
请求签名
每个请求带以下四个请求头:
| 请求头 | 内容 |
|---|---|
X-API-Key | 生成时返回的 key.id,当前为 32 位字符串 |
X-API-Timestamp | Unix 秒级时间戳,与服务端相差不超过 300 秒 |
X-API-Nonce | 每次请求新生成的随机字符串,16–128 位,仅英文字母、数字、_、- |
X-API-Signature | HMAC-SHA256 十六进制签名,示例输出小写 |
签名原文为以下六行,以 LF(\n)连接,最后一行后不加换行:
HTTP_METHOD
ESCAPED_PATH
CANONICAL_QUERY
TIMESTAMP
NONCE
HEX_SHA256_BODYHTTP_METHOD:实际大写方法,如GET、POST。ESCAPED_PATH:实际 URL 的转义路径,包含/openapi/v1,不含域名或查询串。不要把%2F先解码成/。CANONICAL_QUERY:参数名按 Gourl.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/categoriesdata.items 为当前站点可见分类。分类和商品仍受当前站点经营范围、会员规则限制。
分类返回 id、parentId、code、name、imageUrl、sortOrder、recommended、label、agentPriceEnabled。用 id 与 parentId 重建分类树,根分类可能不返回 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 包含 items、total、limit、offset。需要全部商品时持续分页,不要把第一页当作全部。商品返回稳定内部标识 productId 和本商城短商品编号 productNumber(字符串,如 "1")。价格字段包括 minPriceCents、maxPriceCents,下单 SKU 及具体单价从详情读取。
一次读取完整商品与规格
商城 0.1.353+ 提供 GET /openapi/v1/catalog/products?limit=100&offset=0&categoryId=CATEGORY_ID,减少“读列表后每个商品再发一次详情请求”。沿用相同签名、会员/站点权限、分类层级、实时会员价格及限流;接口与我们导入第三方的 catalog.v2 不是同一协议。
data.items 每项保留 product、完整可报价 skus、公开 contentJson 和 purchaseNotice,并直接给出 checkoutFields 与 purchaseRules;data.categories 为当前授权目录,total/limit/offset/hasMore/nextOffset/checkedAt 用于分页。skuId 是当前真实规格ID,code 是稳定规格代码,规格换修订后ID可变,不按名称合并。
categoryId 查询该目录与全部后代商品;商品返回的 categoryId 仍是实际归属,不能把父目录的聚合结果重新归到父级。目录可能本身没有商品但含有子目录,这不是读取失败。
所有金额为当前调用会员可购买的整数分;只给可售库存状态,不提供精确库存数,不提供供货成本、其他会员价格、来源密钥或后台采购元数据。公开内容包括展示说明、图片、下单字段和数量规则,未知内部商品JSON不会整份转发。
服务端先读取轻量已发布元数据,权限筛选后只加载当前页详情和报价,避免每页先读几千个商品完整资料。total 为当前授权、匹配且有已发布有效规格的元数据数量;页内无有效报价时整页409,不悄悄漏掉商品。响应按4MiB限定;过大时明确报错并建议降低limit,不返回假“完整”的截断清单。各页读取实时数据,不宣称跨页冻结数据库快照;中途下架/权限变动或依赖服务失败导致本页失败时,保留旧目录并重试,不能删掉没读完的商品。读取到 hasMore=false 才代表本轮分页结束,checkedAt 只是本页检查时间。
Node.js 完整只读示例见 [商城 API 客户端](../examples/mall-api-client.mjs) 的 readCatalog()。它只在全部分页成功后返回,检测重复商品/缺SKU/分页不前进/总数变化;默认直接执行示例仍只查询余额,不建单、不付款。
仅新路径返回HTTP404且JSON错误码为 route_not_found,或正文精确为Go默认 404 page not found 时,可沿用 /products 分页及 /products/{id} 详情;业务404、其他404及503、401、403、429不能当作“旧版没有接口”回退,分别处理服务暂不可用、签名、授权及限流。本轮候选未正式发布前,生产旧版按实际能力使用;完整 JSON、表单编码与签名向量见仓库 mall/docs/openapi-supplier-contract.md。
商品编号与旧接口兼容
商城 0.1.325 及更新版本中,productNumber 按商城租户从 1 递增,创建草稿即分配,导入商品也占用一个编号;删除不回收,编辑、发布和改价不换号。已有商品升级时会稳定补号。编号不是全平台唯一,保存映射时还应保存供货商城或渠道身份。
productNumber:页面显示和新对接使用的短商品编号,JSON 中必须使用字符串。最大值为18446744073709551615;不要在 JavaScript 中转换成 Number,以免精度丢失。productId:既有内部 ID,继续用于旧详情路径和既有映射,不改写成短号。productCode:商户商品代码,和短编号是不同字段。外部货源的goodsSN、externalProductId、externalVariantId仍属于上游协议,不用本商城短号覆盖。skuId:具体规格 ID。一个商品可有多个规格,下单仍必须从详情选择真实skuId;商品短号不能代替规格。
短编号只接受不带前导零的正整数十进制字符串;0、01、+1、空白、浮点数、科学计数法及超出上述上限均返回 400。格式正确但在当前租户、站点或账户范围内不可见时,详情返回 404,不回退匹配内部 ID 或商户代码。旧版本不支持新增路径时,继续使用列表返回的 productId 和 skuId。
| 要同步的内容 | 读取字段与规则 |
|---|---|
| 商品与目录 | productNumber、productId、productCode、name、summary、categoryId、categoryName |
| 商品图片 | coverUrl 是封面;为空时使用自己的默认图片,不代表接口故障 |
| 价格 | minPriceCents、maxPriceCents 为当前会员报价,金额单位为分 |
| 库存状态 | 商品和 SKU 的 stockStatus:available 为有货,out_of_stock 为缺货;不是精确库存数量,也不预留库存 |
| 规格与下单资料 | 详情 skus 和解析后的 contentJson.checkoutFields |
imageUrl、coverUrl 可能是完整的 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-002ids 为 1–20 个不同商品 ID,用英文逗号分隔;只允许传一个 ids 参数。每个 ID 最长 64 字节,不含空白、控制字符和斜杠。使用与其他接口相同的签名、权限及限流。
商城 0.1.325 及更新版本也支持 GET /openapi/v1/product-statuses?productNumbers=1,2。productNumbers 同样为 1–20 个不同值,只能传一次,且不能与 ids 混用。可见商品同时返回 productId 和 productNumber。使用短号查询不可见商品时,仅回显请求的 productNumber,productId 返回空字符串,status 为 unavailable,不会暴露内部身份;使用旧 ids 查询仍回显原 ID。
{
"data": {
"items": [
{"productId": "product-001", "status": "published", "stockStatus": "available"},
{"productId": "product-002", "status": "unavailable", "stockStatus": "unknown"}
],
"checkedAt": "2026-09-22T08:00:00Z"
}
}published:对当前站点和会员可见的已发布商品,库存单独判断。仍需遵守营业时间、限购、实名及其他下单规则,不等于保证可购买。unavailable:商品不存在、已下架、已删除、分类不可见、账户无权访问或暂无有效报价。为保护经营范围,接口不区分这些原因;下游应暂停该商品采购,不要删除已有订单与映射。stockStatus:available/out_of_stock;不可见商品返回unknown。checkedAt是本次检查时间,不是商品更新时间。- 接口整体失败返回非 2xx,例如依赖服务异常返回 503。失败时保留上次状态并重试,不能把所有商品当作下架。
建议首次全量分页导入分类、图片、商品和 SKU;随后定时拉取价格与已映射商品状态。仅当完整分页成功后再比较商品集合,不能因为某页超时、权限错误或只拿到第一页就批量下架。批量状态接口一次响应不是跨多个商品的数据库快照,付款前仍以服务端订单校验为准。目前本流程使用定时查询,不承诺商品变化实时推送。
查询商品详情
GET /openapi/v1/products/PRODUCT_ID
GET /openapi/v1/products/by-number/1两种路径返回相同详情结构。旧 /products/{id} 始终按内部 ID 查询,不把纯数字参数自动解释为商品编号;按短号查询必须使用 /products/by-number/{productNumber}。
data.product 为商品,data.skus 为 SKU 数组。SKU 读取 skuId、priceCents、stockStatus。商品必须已审核发布,且在当前站点和账户可售范围内。
下单模板位于 data.contentJson,它是 JSON 字符串,与 product、skus 同级。 解析后读取 checkoutFields,不要从 data.product.contentJson 取值。
const productNumber = '1'; // 从商品列表或商家提供的本商城短编号取得
const detail = await callAPI('/openapi/v1/products/by-number/' + encodeURIComponent(productNumber));
const content = detail.contentJson ? JSON.parse(detail.contentJson) : {};
const fields = content.checkoutFields || [];按模板的字段名、类型、必填项、长度和选项收集资料。password 以密码控件显示,仍按原字符串提交;multiselect 的值是在字符串中JSON编码的选项字符串数组,例如 "[\"月卡\",\"年卡\"]",不是逗号拼接;textarea 允许换行,其他单行类型不允许。purchaseRules 的起购/最大/步长一起生效,最大值0代表沿用本商城9999总数量上限。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": [{"productNumber": "1", "skuId": "SKU_ID", "quantity": 1}],
"checkoutData": {"SKU_ID": {"account": "TARGET_ACCOUNT"}}
}| 字段 | 必填 | 规则 |
|---|---|---|
reference | 是 | 客户业务单号,使用 8–80 位不含空格的可打印 ASCII;建议仅字母、数字、_、-,并保持原值 |
maxAmountCents | 是 | 整单最高可接受金额,整数分,1–10,000,000,000;不是指定成交价 |
items | 是 | 1–100 个明细,不可重复 SKU |
items[].skuId | 是 | 实际 SKU ID,1–64 字节,无首尾空白 |
items[].productNumber | 否 | 本商城短商品编号字符串;提供时必须与 skuId 所属商品一致 |
items[].productId | 否 | 兼容的内部商品 ID;提供时必须与 skuId 所属商品一致 |
items[].quantity | 是 | 1–9999 的整数;整单总数量仍不超过9999,还需满足实际商品起购、限购、购买倍数等规则 |
checkoutData | 按商品要求 | 对象,以 SKU ID 为键;内容按该商品 checkoutFields 填写,无资料要求可省略 |
服务端按实际账户、站点、商品、库存与购买规则重新报价,整单商品数量还受 9999 上限约束。超过安全金额会拒绝,不接受客户端自定商品单价。
本地卡池商品的单份交付限1MiB、单张卡密限1024字节,因此单规格最多1023张;商品原有更低上限仍然生效。目录接口返回有效购买规则,网页、小程序及开放API在付款前走同一公共数量校验。上游采购商品按其真实交付类型和上游规则校验,不误用本地卡池上限。
推荐传入 productNumber 与明确选择的 skuId。同时传入 productNumber、productId 时,两者必须指向同一商品,且与该 SKU 一致,否则返回 400;未知或跨租户 SKU/编号不会用于其他商品。既有只传 skuId 的客户端保持兼容。新订单使用已删除或下架商品会失败;已成功提交的订单按完全相同的 reference 和请求重试仍返回原订单,即使商品之后下架或删除。重试不要增加、删去或改写商品标识字段,否则属于请求内容变更,返回 409。
订单明细中的 productNumber 来自下单时保存的商品编号快照;商品后续变化不会重写历史。升级前未保存该快照的历史订单返回空字符串,不拿当前编号冒充历史快照。
首次成功 HTTP 201,相同请求重放 HTTP 200:
{
"data": {
"order": {
"id": "ORDER_ID",
"orderNo": "API_DISPLAY_NUMBER",
"status": "pending_payment",
"currency": "CNY",
"totalAmountCents": 2000,
"items": [{"id":"ITEM_ID","productNumber":"1","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.order 与 data.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/fulfillmentdata 包含 orderId、tasks、deliveries。任务 status 可能为 pending、processing、delivered、failed、manual、cancelled;交付记录包含 taskId、deliveryType、content、contentSha256、createdAt 等字段。按任务和交付记录核对结果,空数组不代表已交付。
卡密等内容仅保存在自己的服务端,避免写入公开日志。适当间隔轮询;上游状态未知、人工处理或履约失败时先查单或提交工单,不要重新采购。该查询接口读取已有交付记录,不承诺像渠道供货接口一样在退款状态下隐藏历史内容;接入方应先检查订单状态,退款处理中停止向外分发。
原订单需要顾客验证码
商城0.1.353+ 对支持顾客操作的货源原单提供以下签名接口,目前实现卡易速 provideOtp。使用订单返回的真实 items[].id,不使用SKU ID代替订单明细ID;只允许本会员、当前销售站点已付款且仍处理中的订单。
GET /openapi/v1/orders/ORDER_ID/items/ITEM_ID/supplier-actions{"data":{"orderId":"ORDER_ID","itemId":"ITEM_ID","actions":[{"type":"provideOtp","label":"提交验证码","minLength":1,"maxLength":32}],"available":true,"retryAt":null}}actions 为当前上游原单实际允许的操作;为空时不显示验证码入口。available=false 且仍有 provideOtp 时保留入口,按 retryAt 显示倒计时后重读状态。不要从商品名称、供应商ID或静态 needsOtp 配置猜操作能力。
POST /openapi/v1/orders/ORDER_ID/items/ITEM_ID/verification-code
Content-Type: application/json
{"otp":"001234"}验证码必须为1至32个字符的字符串,不能为空白、含NUL或换行;保留原值和前导零。成功响应 data.orderId/itemId/submitted/retryAt,submitted=true 只表示已接受提交,随后查询原订单和履约确认是否完成。每次提交后至少30秒的持久冷却,同采购账户限流也会延后 retryAt;429按 Retry-After 等待,不重买商品。
接口只查询已存在的采购单并向其提交验证码,已退款、取消、终态、身份不符或没有原采购记录时拒绝;不会创建新采购或切换货源。验证码不写入订单快照、履约、审计或响应。网络超时不能证明提交失败,先查原单;不能自动循环重发相同验证码。接入方也须隐藏验证码并避免记录完整请求正文。
售后与退款
商城 0.1.260 起提供取消与退款申请接口,均须使用同一会员、同一站点的 API 密钥签名。旧版本需要先升级。未付款订单可调用 POST /orders/{id}/cancel,请求体为 {};成功返回 data 订单信息。已付款订单不能直接撤单,需提交退款审核。
POST /openapi/v1/orders/ORDER_ID/refunds
Content-Type: application/json
{"reference":"refund-order-001","reason":"申请取消未完成的充值"}reference 为本次退款申请的唯一业务号,沿用创建订单的业务号字符与长度规则;reason 为退款原因,最多 500 字。成功返回 data.refund 和 data.replayed,重复提交相同业务号和内容不会创建第二笔申请。该接口申请整单剩余可退金额,不会自动绕过审核。请求体参与签名,不需要额外的 Idempotency-Key 请求头。
GET /openapi/v1/orders/{id}/refunds 返回 data.items,最多最近 50 条,字段包含 id、orderId、status、amountCents、currency、reason、requestedAt、completedAt。requested 为待审核,manual 为人工处理中,succeeded 为该笔退款成功,rejected 为申请被拒绝。单笔退款成功可能只是部分金额,整单以订单 refunded 状态为准。
目前仍使用查询接口确认退款,不提供退款推送通知。需要人工协助时,在商城「工单售后」提交 reference、商城订单 ID/编号、问题和错误 requestId,不要提交 secret 或完整卡密。
可用入口:
/pc/#orders 我的订单
/pc/#funds 资金中心
/support/ 工单售后退款申请不等于资金已退。确认退款以商城订单、支付、退款记录和钱包流水为准;余额订单确认退款后退回原商城会员余额。已实际交付的卡密、直充不能通过当前退款确认流程直接退款。授权类商品另有授权撤销校验,不能概括为所有已交付商品一律同规则。
错误处理
{"error":{"code":"api_disabled","message":"API 授权未开通、已停用或密钥已撤销","requestId":"REQUEST_ID"}}下表为主要错误,不是所有公共中间件和业务模块错误的穷举。保留 HTTP 状态、error.code、message、requestId,不要只按提示文字判断。
| HTTP | code | 处理方法 |
|---|---|---|
| 400 | api_invalid_input / invalid_request | 检查参数、JSON 字段和类型;不要多传未定义字段 |
| 401 | api_signature_invalid | 检查时钟、key/secret、站点、路径、查询编码、原始正文;撤销的密钥也可能返回此码 |
| 403 | api_disabled | 检查会员状态及当前有效 API 权限 |
| 403 | identity_required | 按商品要求完成实名认证 |
| 404 | api_not_found / order_not_found / catalog_not_found / payment_not_found | 核对对象 ID、站点、账户和商品是否可见 |
| 409 | api_nonce_replayed | 用新 nonce 重试,业务号不变 |
| 409 | api_conflict | 授权或密钥已变更,刷新密钥管理状态 |
| 409 | idempotency_conflict | 原业务号参数已固定,先核对原订单 |
| 409 | price_limit_exceeded / price_unavailable | 重新确认报价,核对原单后再决定是否发起新采购 |
| 409 | insufficient_stock / product_unavailable | 检查库存、商品与销售条件 |
| 409 | wallet_insufficient | 充值后在有效期内重试原订单付款 |
| 409 | online_payment_pending | 继续原在线支付流程,不重复付款 |
| 409 | invalid_payment_state / invalid_order_state | 查询原订单和支付状态,停止盲目重试 |
| 422 | invalid_quantity / invalid_idempotency_key / invalid_checkout_data | 修正数量、业务号或下单资料;已存在订单的业务号不能改参数重用 |
| 413 | api_body_too_large | 请求体超过 1 MiB |
| 429 | api_rate_limited | 按 Retry-After 退避,当前为 60 秒 |
| 503 | open_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.mjsWindows 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 签名。本文说明当前实现,不代表您的生产域名、账户或真实交付渠道已经验收。
货源自动运营状态接口
管理员货源页面可读取持久化自动运营概览:
GET /api/mall/v1/admin/supplier-channels/{channelId}/autopilot/overview该接口只返回当前管理员有权查看的白名单状态,不返回上游 URL、ID/Key、密码、采购资料或卡密;渠道状态中也不回传原始同步错误。响应包含 state/message/channel/importJob/syncJobs/monitoring/lastCheckedAt。importJob.productProgress 明确区分 directoryComplete 与 productsComplete,并给出 expected/items/imported/skipped/queued/pending/failed/deferred;monitoring.sync 展示 products/stock/balance 最近任务状态,monitoring.orders 展示已持久化的 supplier-orders 总数和 pending/succeeded/failed/unknown/cancelled 计数。
该接口用于关闭窗口后恢复进度和后台监控,不代表真实上游已全部成功。商品/规格失败必须先处理详情或重试;订单 unknown 必须查询原单,不能重新生成采购单。客户开放 API 仍按本文既有 /openapi/v1 查询与签名契约执行。