OPEN API / 店铺共享协议
让你的平台,
连接本站商品。
从同步目录到完成交付,一份文档说明整个流程。支持 PHP、Python、Node.js,以及能发送 HTTP 请求的其他服务端。
站点根地址 / BASE URLhttps://your-site.example
推荐 v2 HMAC 签名表单 POST;兼容旧共享 MD5 协议。
01 / 认证商户 ID + 密钥会员中心获取对接凭据
02 / 同步目录 · 库存 · 价格以当前商户权限和价格为准
03 / 交付下单 → 查询 → 发货异步订单完成后再交付内容
AUTHENTICATION
认证与签名
登录会员中心的控制台,查看“商户 ID”和“商户密钥”。商户 ID 对应 app_id;密钥对应签名函数的 app_key。密钥只保存在你自己的服务器配置中,不放进网页、浏览器请求或公开日志。
| 公共参数 | 类型 | 说明 |
|---|
app_id | string | 商户 ID,所有请求必填 |
sign | string | v2 为 64 位 HMAC;v1 为 32 位 MD5,均为小写十六进制 |
app_key | 仅本地 | 参与签名计算,不提交给接口 |
推荐:v2 HMAC-SHA256
新客户端默认使用 v2。除 app_id、sign 外,每次提交 signature_version=2、timestamp(Unix 秒)、nonce(16 字节随机数的小写十六进制,共 32 位)。时间偏差不超过 120 秒,同一商户的 nonce 不可重复。
- 全部表单字段转字符串,只移除 sign,保留空字符串和 0。
- 参数名按 ASCII 升序排序,键和值按 RFC3986 编码(空格编码为 %20),用 & 连接;不能再 urldecode。
- 拼接 POST、接口路径、timestamp、nonce、规范表单,使用换行分隔。
- 使用商户密钥计算 HMAC-SHA256 小写十六进制签名;通过表单提交,密钥不随请求发送。
$params['signature_version'] = '2';
$params['timestamp'] = (string)time();
$params['nonce'] = bin2hex(random_bytes(16));
unset($params['sign']);
ksort($params, SORT_STRING);
$canonical = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$path = '/shared/authentication/connect';
$message = "POST\n" . $path . "\n" . $params['timestamp']
. "\n" . $params['nonce'] . "\n" . $canonical;
$params['sign'] = hash_hmac('sha256', $message, $appKey);
服务端保存商户 nonce 并加锁防重放。已验签的请求即使后续业务失败,也不能复用 nonce。查询原订单时生成新 nonce;付款结果未知时仍不能重新采购。系统时钟需保持同步。v2 使用标准 /shared/… 路径,不使用 index.php?s= 路由别名。
旧客户端兼容:v1 MD5 签名
- 组装全部业务字段和
app_id,移除 sign。 - 按参数名升序排序;只移除值为空字符串的项,保留
0 和 "0"。 - 用 PHP
http_build_query(RFC1738)拼接,再追加 &key=你的密钥。 - 对整个拼接结果进行一次
urldecode,计算小写 md5。中文统一 UTF-8。 - 把签名放入
sign,以 application/x-www-form-urlencoded 发送原始表单参数。
function sign(array $params, string $appKey): string {
unset($params['sign']);
ksort($params);
foreach ($params as $name => $value) {
if ($value === '') unset($params[$name]);
}
return md5(urldecode(http_build_query(
$params, '', '&', PHP_QUERY_RFC1738
) . '&key=' . $appKey));
}
这是现有共享协议的兼容算法。请使用 HTTPS;旧 v1 没有时间戳或 nonce,也不提供严格防重放保证。不要自行改成 HMAC、二次编码或大写 MD5,否则现有接口无法验签。
旧 ACG 客户端可能仍提交 app_key,兼容入口允许其参与原签名;新 SDK 不提交它。旧签名不提供 v2 的防重放和无歧义编码。所有下游迁移后,可在 config/shared-api-v213.php 将 allow_legacy 改为 false;不可在旧客户端迁移前直接停用。
QUICK START
先连接,再读取商品
下载下方客户端,填写服务端环境变量 SHOP_BASE_URL、SHOP_APP_ID、SHOP_APP_KEY。以下示例只读取余额和目录,不创建订单。
客户端使用 30 秒总截止时间、8 MiB 响应上限(可配置);PHP/Python 的连接超时为 10 秒,不跟随重定向,不自动重试请求;订单号使用字符串,金额请使用十进制定点数。下载客户端的采购示例默认处于注释状态。
PRODUCT MODEL
商品、价格与规格
| 字段 / 概念 | 正确处理 |
code | 本站对接商品 CODE;下单使用 shared_code,详情/库存使用 sharedCode。不要用数据库 ID 或上游 shared_code 替代。 |
factory_price | 当前商户、会员等级对应的拿货单价,使用最多 5 位小数字符串返回。price / user_price 是其他展示价格,不应直接用作你的采购成本。 |
config | INI 格式文本,不是 JSON。出现 category 时,以种类键作为 race;category_factory 为对应种类拿货价。不要仅依赖普通 factory_price。 |
minimum / maximum | 数量下限 / 上限;实际以服务端购买规则为准。num 为份数,不要统一按“每千份”换算,供应商计价规则不等于本站对外商品计价。 |
widget | 商品动态表单定义,可能为 JSON 文本。按定义收集字段,再把字段名和值平铺提交到下单表单并参与签名;不要把整个 widget 当成一个 JSON 参数。 |
delivery_way | 0 为自动发货、1 为手动发货;自动商品也可能异步完成,不能据此假设下单立即返回卡密。 |
draft_status | 1 表示支持预选;先读 draftCard 选择 card_id。预选通常按单卡处理,勿用同一个 card_id 下多份。 |
商品单价可包含最多 5 位小数,例如 0.03500。不要先把单价保留 2 位再乘数量;指定数量请使用 quote 试算,账单金额以本站返回的 amount 为准,不能承诺 amount 也有 5 位小数。建议服务端用 Decimal / 定点计算,并把大订单号保留为字符串。
列表为空时,检查商品“上架”“开放 API”、所属分类是否启用、会员等级可见性及专属商品限制。部分网络、时长或动态表单商品还需要额外业务参数,不能只用通用三字段下单;以商品字段定义和本站配置为准。
ORDER LIFECYCLE
下单与异步交付
保存接入方业务单号→创建并支付→查询原订单→确认完成后交付
- 创建 1–64 位 ASCII 字母数字、下划线或连字符组成的
request_no(不能为 0),例如 商户ID-UUID,先写入你的订单数据库,并对同一业务单加锁。现有服务端单号重复检查是全局的,请加入商户前缀。 - 调用 trade 会使用余额付款。成功后保存
tradeNo、amount,不要再次创建付款单。 - 用
/shared/integration212/orderStatus 查询。同一商户一次只填 trade_no 或 request_no。 state=pending 时按 next_poll_seconds(默认 5 秒)轮询;持续等待可退避至 10–30 秒。到达你的等待期限后转订单页继续查询,不自动退款或重下。- 只有
state=delivered 且 result_available=true 才交付 secret;pending 的 secret 固定为 null。交付内容原样保存,保留换行,不把它当作 HTML 执行。
付款请求超时、断网、返回 HTML 或“request ID already exists”,都不代表购买失败。请按原 request_no 查询,勿换新单号重发。现有 request_no 检查不构成严格并发幂等;短时间查无订单也不能证明扣款不会稍后完成。
旧 query / query2 的 status=1 仅表示已付款,secret 可能是处理中提示。新接口不触发上游查询和采购,只读取本站订单。服务类商品完成时可能返回服务说明。标记已发货却没有交付内容时返回 attention,停止自动交付并联系本站。退款和部分完成没有单独映射,请联系本站处理,勿根据等待时间自行推断。
11 个接口
POST
连接与余额
/shared/authentication/connect
验证商户 ID、签名及当前余额。只读,不扣款。
| 参数 | 类型 | 要求 | 说明 |
|---|
| 仅需公共参数 app_id、sign。 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/authentication/connect' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN"
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"shopName": "示例商店",
"balance": "100.00"
}
}
POST
分类与商品列表
/shared/commodity/items
返回分类数组,每个分类的 children 是商品。无分页参数;数组可能为空。仅包含分类启用、商品上架且开放 API,并通过会员可见性检查的商品。
| 参数 | 类型 | 要求 | 说明 |
|---|
| 仅需公共参数 app_id、sign。 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/items' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN"
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"name": "邮箱账号",
"children": [
{
"id": 101,
"code": "MAIL-DEMO",
"name": "示例邮箱",
"factory_price": 0.035,
"api_status": 1,
"status": 1,
"minimum": 1,
"maximum": 100,
"delivery_way": 0,
"draft_status": 0,
"config": "",
"widget": null
}
]
}
]
}
POST
单个商品详情
/shared/commodity/item
仍返回“分类 → children”结构,不是单个平铺对象。使用商品 code,不使用本地商品 ID 或上游 shared_code。
| 参数 | 类型 | 要求 | 说明 |
|---|
sharedCode | string | 必填 | 商品 code;注意是驼峰名称 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/item' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'sharedCode=MAIL-DEMO'
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"name": "邮箱账号",
"children": [
{
"code": "MAIL-DEMO",
"name": "示例邮箱",
"factory_price": 0.035,
"config": "",
"widget": null
}
]
}
]
}
POST
库存与价格
/shared/commodity/inventory
下单前刷新库存。count 为库存提示,不是锁库承诺;手动发货商品可能返回 99999,不代表真实卡密数。多种类价格见 config.category_factory,勿将 factory_price=0 误作免费。
| 参数 | 类型 | 要求 | 说明 |
|---|
sharedCode | string | 必填 | 商品 code |
race | string | 可选 | 种类键,无种类传空字符串 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/inventory' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'sharedCode=MAIL-DEMO' \
--data-urlencode 'race='
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"count": 100,
"delivery_way": 0,
"draft_status": 0,
"price": 0.3,
"user_price": 0.25,
"factory_price": 0.035,
"is_category": false,
"config": ""
}
}
POST
购买数量预检
/shared/commodity/inventoryState
校验当前库存及可选卡密。成功仅代表本次预检通过,实际下单仍可能因库存变化失败。
| 参数 | 类型 | 要求 | 说明 |
|---|
shared_code | string | 必填 | 商品 code;此接口使用下划线 |
num | integer | 必填 | 购买数量,正整数 |
card_id | integer | 可选 | 无预选传 0 |
race | string | 可选 | 无种类传空字符串 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/inventoryState' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'shared_code=MAIL-DEMO' \
--data-urlencode 'num=1' \
--data-urlencode 'card_id=0' \
--data-urlencode 'race='
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": []
}
POST
预选卡密列表
/shared/commodity/draftCard
仅用于 draft_status=1 的商品。返回预选说明和卡 ID,不返回未购买的卡密。返回分页字段以服务端为准。
| 参数 | 类型 | 要求 | 说明 |
|---|
sharedCode | string | 必填 | 商品 code |
page | integer | 必填 | 页码,建议从 1 开始 |
limit | integer | 必填 | 每页数量,建议 20 |
race | string | 可选 | 无种类传空字符串 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/draftCard' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'sharedCode=MAIL-DEMO' \
--data-urlencode 'page=1' \
--data-urlencode 'limit=20' \
--data-urlencode 'race='
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"current_page": 1,
"data": [
{
"id": 42,
"draft": "示例预选说明"
}
],
"total": 1
}
}
POST
创建并付款
/shared/commodity/trade
使用商户余额支付,成功即可能扣款。商品 widget 额外字段按名称平铺提交并一起签名。request_no 是接入建议的必填项;旧服务端并未实现严格的并发幂等承诺。超时必须先查原单,不能自动重下。
| 参数 | 类型 | 要求 | 说明 |
|---|
shared_code | string | 必填 | 商品 code |
num | integer | 必填 | 购买数量,正整数 |
request_no | string | 必填 | 业务单号:1–64 位字母数字、下划线或连字符,不能为 0;建议 商户ID-UUID |
contact | string | 必填 | 联系方式;根据商品要求填写 |
card_id | integer | 可选 | 无预选传 0 |
device | integer | 可选 | 设备标识,默认 0 |
password | string | 可选 | 查单密码,默认空 |
race | string | 可选 | 商品种类键,默认空 |
from | integer | 可选 | 默认 0 |
coupon | string | 可选 | 必须为空,优惠券已停用 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/trade' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'shared_code=MAIL-DEMO' \
--data-urlencode 'num=1' \
--data-urlencode 'request_no=YOUR_APP_ID-UUID' \
--data-urlencode '[email protected]' \
--data-urlencode 'card_id=0' \
--data-urlencode 'device=0' \
--data-urlencode 'password=' \
--data-urlencode 'race=' \
--data-urlencode 'from=0' \
--data-urlencode 'coupon='
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"url": null,
"amount": 0.04,
"tradeNo": "905488631289636187",
"secret": "示例交付内容"
}
}
POST
指定数量试算
v213 新增/shared/integration212/quote
只计算本站当前价格,不扣款、不锁库存、不请求供应商。普通商品和静态价格可试算;需要专用参数的 IP 网络及 t57h 服务商品明确拒绝通用试算,避免返回错误金额。最终付款仍以实际订单 amount 为准。
| 参数 | 类型 | 要求 | 说明 |
|---|
| shared_code | string | 必填 | 本站商品 CODE |
| num | integer | 必填 | 1–999999;同时检查商品最少/最多购买限制 |
| race | string | 可选 | 多种类商品应明确传入种类键 |
{"code":200,"msg":"success","data":{"code":"MAIL-DEMO","quantity":100,"race":"","unit_price":"0.03500","amount":"3.50","currency":"CNY","binding":false}}
POST
准确查询发货状态
v213 新增
/shared/integration212/orderStatus
v213 新增只读接口,只返回当前商户自己的订单;不触发采购、补单、退款或同步。state=delivered 才交付 secret。pending 时 secret=null;服务类商品的 secret 可能是完成说明,不一定为账号密码。
| 参数 | 类型 | 要求 | 说明 |
|---|
trade_no | string | 可选 | 本站订单号;与 request_no 二选一 |
request_no | string | 可选 | 接入方业务单号;与 trade_no 二选一 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/integration212/orderStatus' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'trade_no=905488631289636187'
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"trade_no": "905488631289636187",
"request_no": "1001-demo-001",
"payment_status": 1,
"delivery_status": 1,
"state": "delivered",
"result_available": true,
"amount": "0.04",
"currency": "CNY",
"quantity": 1,
"secret": "[email protected]_ONLY",
"next_poll_seconds": null
}
}
POST
旧版按本站单号查询
/shared/commodity/query
status 是付款状态,不能当作发货完成。旧接口可能返回处理中提示;新接入使用 orderStatus;空交付异常返回 attention。widget 结构与商品有关,不应对外直接展示内部字段。
| 参数 | 类型 | 要求 | 说明 |
|---|
tradeNo | string | 必填 | 本站订单号,驼峰 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/query' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'tradeNo=905488631289636187'
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"secret": "示例内容或处理中提示",
"widget": null,
"status": 1
}
}
POST
旧版按接入方单号查询
/shared/commodity/query2
查询范围限定当前商户。请求名称是 requestNo,而创建订单时是 request_no;新接入推荐 orderStatus。
| 参数 | 类型 | 要求 | 说明 |
|---|
requestNo | string | 必填 | 接入方单号,驼峰 |
cURL 请求示意 先在服务端生成 TIMESTAMP、NONCE 和 SIGN;同一次请求须一致
curl --request POST '{{BASE}}/shared/commodity/query2' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "app_id=$SHOP_APP_ID" \
--data-urlencode "signature_version=2" \
--data-urlencode "timestamp=$TIMESTAMP" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "sign=$SIGN" \
--data-urlencode 'requestNo=YOUR_APP_ID-UUID'
成功响应示例 示意数据,非真实商品或卡密
{
"code": 200,
"msg": "success",
"data": {
"secret": "示例内容或处理中提示",
"widget": null,
"status": 1
}
}
没有匹配的接口,请换一个关键词。
DEVELOPER RESOURCES
直接带走接入资源
示例客户端使用标准 HTTP 库;PHP 需要 cURL 扩展。为了防止服务器把示例当作程序执行,源文件以 .txt 提供,下载后按说明改名。
OpenAPI 中的站点地址为 /,导入外部工具后替换为本站 HTTPS 根地址。签名需由服务端或工具的请求前置脚本计算;导入文件不会自动完成签名。
TROUBLESHOOTING
错误与排障
接口默认每商户 240 次/分钟、每来源 IP 300 次/分钟,超限返回频率错误并附 Retry-After。服务器仅信任 REMOTE_ADDR;经共享代理接入时应按实际部署评估限额。所有表单字段名使用 ASCII 字母开头的字母、数字或下划线。
| 现象 | 处理方式 |
| HTTP 200,但 code 不为 200 | 业务失败,读取 msg;不要仅依赖 HTTP 码,也不要硬编码未经承诺的错误编号。 |
| 密钥错误 / 签名不能为空 | 检查是否签了所有字段,空字符串被移除而 0 保留;UTF-8、参数排序、签名版本与编码是否一致、时钟偏差、nonce 是否复用、密钥是否已经重置。 |
| 商品列表为空 | 检查上架、开放 API、分类启用和会员可见性;确认请求的是 shared/commodity/items。 |
| 库存不足 / 最少购买 / 最多购买 | 刷新商品库存和规格;修改数量前先确认前一笔付款单没有成立。 |
| 订单不存在 | 检查商户是否一致、单号是否字符串,以及 trade_no / request_no 是否用错;接口只允许查当前商户订单。 |
| 返回 404、登录页面或 HTML | 检查站点根地址、URL 重写、WAF 与 PHP 日志。不要把 HTML 当作 JSON,不要附加后台 /mzt 前缀。 |
| 超时 / 5xx / 网络中断 | 付款结果可能未知,先查询原单;只读查询可以退避重试,付款请求禁止盲目重试。 |
COMPATIBILITY
不同平台如何接入
能编写 HTTP 接口适配器的平台,可按本文的共享协议接入:验证余额 → 展开分类 children → 保存 code → 同步库存与拿货价 → 余额下单 → 查询交付结果。只支持固定供应商协议的平台,需要编写对应适配器。
已有“ACG 店铺共享”驱动的平台,可先用商户 ID / 密钥配置本站根地址,检查该驱动是否正确处理异步发货。新适配器建议使用本页的 orderStatus,而非把旧 status=1 直接判成已发货。
本站原有独角 Next 上游协议使用独立的 /api/v1/upstream/… 路径及 HMAC 请求头,属于另一套协议,部署时还依赖对应的 URL 重写。本页共享协议的签名和参数不能与其混用。
API 面向服务端,不依赖网页登录 Cookie。本页不提供会扣款的在线调试按钮,也不收集或保存真实密钥。目录缓存可按业务需求设置,付款前重新读取库存与价格。