开放接口接入文档
连接您的业务系统,完成商品同步、卡密采购与账号直充。
https://www.k8s2024.com/open/v1系统接入域名:www.k8s2024.com。请使用上述网关地址调用接口。
完整请求示例:GET https://www.k8s2024.com/open/v1/account
下文接口路径已包含 /open/v1;按完整路径拼接域名时,不要重复添加这个前缀。
文档概述
本文用于合作方系统连接优卡中控,完成账户余额查询、商品同步、卡密采购、账号直充、订单查询和结果通知。调用使用经销商 API 身份,订单按该经销商的授权范围及结算价格从预存余额扣款。
接口一览
/open/v1/account查询账户 →GET/open/v1/products查询商品 →POST/open/v1/orders提交订单 →GET/open/v1/orders/by-request/{request_id}按请求号查单 →GET/open/v1/orders/{id}按订单 ID 查单 →POST/open/v1/orders/{id}/cards提取卡密 →| 项目 | 说明 |
|---|---|
| 文档版本 | V1.0 · 2026年10月22日 |
| 适用系统 | V2026.10.22.3 |
| 协议版本 | system-supply-v1 |
| 接口网关地址 | https://www.k8s2024.com/open/v1 |
| 接入资料 | 平台提供正式或联调域名、经销商 API Key、签名密钥及商品授权 |
| 技术约定 | HTTPS · UTF-8 · JSON · HMAC-SHA256 |
先完成身份开通和签名验证,再读取账户及商品,最后联调下单、查单和回调。本文示例均使用演示商品、订单及密钥;接入时替换为平台发放的数据。域名占位符不是可调用地址。
本协议用于合作方调用本站。京东专用接单协议、供货商采购协议及管理后台接口各自独立,不能混用地址、签名或回执格式。
接入准备与请求签名
经销商须已审核通过、账号启用并开通 API,且取得可卖卡种授权。平台暂停经销商访问时,API 同时停止接入。登录密码不用于接口签名。
| 请求头 | 填写规则 |
|---|---|
| X-Api-Key | 32 位接口账号,由平台发放 |
| X-Timestamp | 10 位 Unix 秒时间戳,与平台时间差不超过 300 秒 |
| X-Signature | HMAC-SHA256 结果,64 位小写十六进制 |
| Accept | application/json,所有请求均建议携带 |
| Content-Type | POST 请求使用 application/json |
签名原文
canonical = timestamp + "\n" + METHOD + "\n" + path + "\n" + rawBody
signature = HMAC_SHA256(secret, canonical) // 小写十六进制METHOD 使用大写 GET 或 POST。path 包含 /open/v1 前缀,不含域名和查询字符串。换行是单个 LF 字节,不是字面量反斜杠加 n。GET 不发送正文,rawBody 为空;POST 按实际发送的 UTF-8 原始字节签名,签名后不得再次序列化或更改空格。
GET 请求 URL:/open/v1/products?page=2
签名使用 path:/open/v1/products
POST 请求 path:/open/v1/orders密钥轮换与网络限制
A、B 两组签名密钥均可验证入站请求。轮换时先切换到另一组可用密钥,再重置旧密钥;API Key 不变。密钥只保存在服务器端,不放入网页、客户端安装包或日志。
IP 白名单为空时允许通过验签的任意来源;配置后按明确的 IPv4 或 IPv6 地址匹配,不支持 CIDR。请提供实际调用服务器的公网出口 IP。
防重复规则
签名在时间窗口内可以重复验证,不属于一次性凭据。交易防重依靠 request_id:同一经销商、同一 UUID、相同业务参数返回原订单;修改参数后复用 UUID 会拒绝。每次重试可更新签名时间,但必须保留原 request_id 和原下单参数。
查询账户与商品目录
2.1 查询账户
GET /open/v1/account无业务请求参数。返回当前接口经销商的账户信息,不返回其他经销商账户。
| 字段 | 类型 | 说明 |
|---|---|---|
| protocol_version | string | 固定 system-supply-v1 |
| balance_cents | integer | 可用余额,单位分 |
| frozen_cents | integer | 冻结金额,单位分 |
{"protocol_version":"system-supply-v1",
"balance_cents":100000,"frozen_cents":0}2.2 查询商品目录
GET /open/v1/products?page=1| 查询参数 | 类型 | 说明 |
|---|---|---|
| page | integer | 可选,默认第 1 页,每页固定 20 条 |
| q | string | 可选,最多 150 字符,搜索名称、编码或 ID |
| search_by | string | 可选,all / title / sku / id,默认综合搜索 |
| product_id | integer | 可选,精确查询平台商品 ID,最小为 1 |
| category | string | 可选,最多 80 字符,按分类名称筛选;一级包含其二级 |
仅返回当前经销商获准、平台已上架且经销商分销启用的商品。商品下架、分类隐藏或授权撤销后可能从目录消失;同步时必须处理已移除商品。
分页约定
响应直接是分页对象,无统一 code/data 成功包装。data 是本页商品数组;current_page、last_page、per_page、total 表示当前页、总页数、每页数量和总条数。读取至 last_page 为止,不要只同步第一页。查询结果为空时 data=[]。
商品字段与价格库存
| 商品字段 | 类型 | 含义 |
|---|---|---|
| id | integer | 本站商品 ID,下单 product_id 使用此值 |
| title / sku | string | 商品名称 / 商品编码;不是上游商品 ID |
| category | string | 所属分类名称 |
| kind | string | card 为卡密;phone 为直充,包含非话费券包直充 |
| business_type | string | 业务类型 card 或 recharge |
| price_cents | integer | 当前经销商每份结算单价,整数分 |
| stock | integer | 当前销售库存额度;0 表示不能下单 |
| min_quantity / max_quantity | integer | 每单购买份数范围,还受供货及实发数量限制 |
| delivery_multiple | integer | 每购买 1 份的实发数量,直充必须为 1 |
| delivery_form | string | number_password / password / link / upstream |
| recharge_fields | object / array | 附加直充字段,键为提交字段名,值为中文名称;无字段为 [] |
| cover_url / description | string / null | 商品封面 / 商品说明 |
| aftersales_days | integer | 售后展示周期,具体售后由平台处理 |
响应可能包含额外展示字段,接入方应忽略未知字段。商品目录不返回采购成本或供货账号。下单不要使用展示面值作为结算价格。
价格和数量
price_cents 已按卡种成本、发货倍数和该经销商比例计算,并向上取整到分。接入方直接使用该值,不必自行重算成本比例。订单总额为当前每份单价乘购买份数;实际扣款以订单 total_cents 为准。
quantity 是购买份数,delivery_quantity 是实发数量。例如倍数为 2、购买 3 份,实发 6 张;价格和库存按购买份数计算。实发总数不能超过 9999,还可能受该货源单次数量限制。
stock 是销售额度,不等于物理仓库卡密数,也不是上游库存保证。下单时再次校验额度和履约条件。当前版本返回非负整数;不能将缺失或异常库存解释为无限库存。
提交订单
POST /open/v1/orders
Content-Type: application/json一次提交购买一种商品。接口按当前经销商价格校验并扣减余额,安排履约后返回订单对象。HTTP 成功仅表示接口已返回,是否交付成功仍须检查 status。
| 字段 | 类型及必填 | 规则 |
|---|---|---|
| product_id | integer 必填 | 目录中的本站商品 ID |
| quantity | integer 必填 | 1—9999,符合限购;直充仅为 1 |
| request_id | string 必填 | 合法 UUID,先持久保存再提交;一笔业务一个 UUID |
| max_total_cents | integer 可选 | 建议必传,总额上限,单位分;范围 0—10000000000 |
| recharge_number | string 条件必填 | 直充主账号,最多 500 字符;话费为合法手机号。卡密订单省略 |
| recharge_params | object 可选 | 按商品 recharge_fields 填写;最多 30 项,每值为非空字符串且最多 200 字符 |
| pickup | object 可选 | 交付方式;普通系统对接省略,使用默认自动交付 |
| supply_chain | array 可选 | 同系统转接专用;普通合作方省略,不自行生成链路标识 |
直充字段
不要把所有直充都当成手机号充值。根据商品说明填写主账号;附加字段必须使用 recharge_fields 返回的原始键名。列出的字段须填写完整,不得提交未配置的键;无附加字段时省略 recharge_params。数店等商品还会按模板检查账号类型。
金额上限与重试
例如目录单价为 10200 分,购买 2 份,可设置 max_total_cents=20400。若提交时价格上涨至超过上限,接口拒绝该笔请求。不能在超时后修改金额上限继续复用同一 UUID;先查清原单,再决定后续业务。
平台可能存在余额门槛、风控或供货数量限制,满足通用参数范围并不保证一定受理。错误信息以实际响应 message / errors 为准。
下单与商品响应示例
5.1 卡密下单请求
{
"product_id": 123,
"quantity": 1,
"request_id": "c2fbd360-750b-4e77-9041-9b3bc5344683",
"max_total_cents": 10200
}5.2 直充下单请求
以下演示商品要求 region 字段;实际字段名必须来自该商品目录。主账号和附加字段均以字符串发送。
{
"product_id": 124,
"quantity": 1,
"request_id": "788be8a2-d884-4d1f-8f01-2b27c143b30b",
"max_total_cents": 20,
"recharge_number": "13800000000",
"recharge_params": {"region": "上海"}
}5.3 商品分页响应节选
{
"current_page": 1,
"data": [{
"id": 123, "title": "演示卡券100元", "sku": "DEMO100",
"category": "商超卡", "kind": "card", "business_type": "card",
"price_cents": 10200, "stock": 9999,
"min_quantity": 1, "max_quantity": 10,
"delivery_multiple": 1, "delivery_form": "number_password",
"recharge_fields": []
}],
"last_page": 1, "per_page": 20, "total": 1
}示例为主要字段节选。金额 10200 表示人民币 102.00 元;20 表示人民币 0.20 元。所有订单号、卡号和密码均按字符串保存,避免丢失前导零或整数精度。
查询订单与状态处理
GET /open/v1/orders/by-request/{request_id}
GET /open/v1/orders/{id}推荐使用原下单 UUID 查单。id 是接口返回的内部订单编号;order_no 是展示订单号,接入系统应分别保存并优先使用内部 id 或 request_id。只能查询当前经销商本人的订单。
| 字段 | 说明 |
|---|---|
| id / order_no | 内部订单 ID / 展示订单号,均按字符串保存 |
| request_id | 原下单 UUID;部分受控交付响应可能不提供,应在本地保留 |
| status / reason | 履约状态 / 状态或失败说明 |
| product_id / quantity | 商品 ID / 购买份数 |
| delivery_quantity | 实发数量,可能与购买份数不同 |
| kind / total_cents | card 或 phone / 订单总额,整数分 |
| created_at / completed_at | 创建 / 完成时间,未完成可为 null |
| cards / cards_masked | 卡密数组 / 是否掩码;不得交付掩码内容 |
| aftersales_status / aftersales | 视响应分支提供售后状态或售后对象,须一并检查 |
| status | 含义及处理 |
|---|---|
| pending | 已创建待处理;继续查原单 |
| processing | 处理中;继续查原单,不重复采购 |
| unknown | 结果未知;保留原单,等待核实,不按失败重下 |
| success | 履约成功;直充无需卡密,卡密按提取规则交付 |
| failed | 履约失败;核对 reason 和资金流水 |
GET 查单读取当前状态,不等同于立即向上游发起新采购。售后状态与履约状态分别存在,不能仅凭历史 success 再次发出卡密。
网络超时、连接中断、5xx 或查单暂时 404 时,先继续查询原 request_id;404 不代表正在执行的提交必然失败。如需补交,必须复用原 UUID 和原参数。建议查单采用渐进间隔并控制总频率。
卡密结果与显式提取
7.1 普通成功响应节选
{
"id": "b2a73d8d-72b6-4bcf-b4e3-321539ad0205",
"order_no": "20261022100000123456",
"request_id": "c2fbd360-750b-4e77-9041-9b3bc5344683",
"product_id": 123, "quantity": 1, "delivery_quantity": 1,
"kind": "card", "status": "success", "total_cents": 10200,
"cards": [{"cardNumber":"DEMO0001", "password":"DEMO-PASS",
"expiryDate":"2027-12-31"}]
}默认交付且实发不超过 30 张的成功卡密订单通常直接返回明文。直充不返回卡密。卡号、密码和有效期对应 cardNumber、password、expiryDate;无值可能为 "-"。密码或链接型商品需按商品形式和实际字段交付,不得擅自截断内容。
7.2 大批量卡密
实发超过 30 张时,普通查单不直接提供整批明文,返回 cards_masked=true、pickup_required=true,并在可提取时提供 pickup_url 和 machine_pickup_path。提卡链接有效期为 3 天,后续查单可能更新链接。链接属于交付凭据。
POST /open/v1/orders/{id}/cards
{"confirmed":true}按实际 POST 正文签名。仅限本人通过开放 API 创建、未被设为受控交付且当前允许提取的成功卡密订单。返回 id、request_id、order_no、quantity、delivery_quantity、cards、cards_masked=false 和 usage_instructions。此操作不会再次扣款或采购,会记录提取审计。
7.3 交付限制
pending、refunding、全额 refunded 或 refund_failed 等售后限制会阻止提取。部分卡券已退回时,应以提取接口实际返回的剩余可交付卡券为准,不得从旧缓存补回已退款内容。
普通对接建议省略 pickup。主动选择 link、sms、email、secure_archive 等受控交付后,必须走对应交付流程,不能用机器提卡接口绕过。短信和邮箱需平台配置可用发送服务;直充只允许默认自动方式。
订单结果通知
接入方提供独立的公网 HTTPS 接收地址,在经销商 API 权限中的“订单结果通知”保存并启用。地址不得含账号、密码、查询参数或片段,不接受内网及保留地址,不跟随重定向。
平台向该地址 POST JSON,仅通知启用期间完成的开放 API 订单。成功事件为 order.succeeded,失败事件为 order.failed。处理中和未知状态不产生终态通知;未启用期间的历史终态不补发。
| 通知头 | 说明 |
|---|---|
| X-Api-Key / X-Timestamp / X-Signature | 使用与请求相同的 HMAC-SHA256 签名规则 |
| X-Event-Id | 事件 UUID,与正文 event_id 一致 |
| X-Signature-Key-Slot | A 或 B;优先用当前 A,仅有 B 时用 B |
{
"event_id": "c04a263f-128a-42b1-bdea-3595fc2a0a31",
"event": "order.succeeded",
"user_id": 123,
"occurred_at": "2026-10-22 10:00:00",
"order": {
"id": "b2a73d8d-72b6-4bcf-b4e3-321539ad0205",
"order_no": "20261022100000123456",
"request_id": "c2fbd360-750b-4e77-9041-9b3bc5344683",
"status": "success", "product_id": 123, "quantity": 1,
"kind": "card", "total_cents": 10200,
"completed_at": "2026-10-22 10:00:00"
}
}失败事件的 event 为 order.failed、order.status 为 failed,其余结构相同。通知不含卡密、提卡链接或采购成本;失败原因通过查单获取。日期时间使用平台配置时区,默认 Asia/Shanghai;签名时间使用 Unix 秒。
通知是订单完成时的历史快照。收到成功事件后,应签名查单并获取当前可交付结果,后续售后仍可能限制提取。
通知验签与确认
9.1 验签步骤
读取原始请求体,核对 X-Api-Key、时间差、签名密钥槽位及事件编号。签名 path 使用接入方所配置的回调路径,而不是 /open/v1/orders。不要先解析再重新编码 JSON 进行验签。
例如接收地址:https://partner.example.com/callback/order
canonical = timestamp + "\nPOST\n/callback/order\n" + rawBody计算 HMAC-SHA256 并使用恒定时间比较。校验正文 event_id 与 X-Event-Id 一致;用 event_id 建立唯一约束,在自己的事务中保存业务处理结果后,再向平台确认。
9.2 成功回执
返回 HTTP 2xx,同时 JSON 正文包含匹配的事件编号和 status=success。只返回 HTTP 200、空正文或纯文本 success / ok 均不算订单通知确认。
{
"event_id": "c04a263f-128a-42b1-bdea-3595fc2a0a31",
"status": "success"
}9.3 重试和幂等
平台至少投递一次,同一事件可能重复到达。重复已成功处理的 event_id,直接返回同一成功回执,不得重复发货、充值或重新向本站下单。
每轮最多 8 次投递,失败后依次约 1、2、5、10、30、60、180 分钟重试;实际时间受调度影响。每次重试使用当前密钥和新的签名时间,事件 ID 和正文不变。单次连接等待约 20 秒。
达到上限后,可在经销商通知记录中核对并手动重试原事件。已确认事件不能手动重发。密钥轮换后应同步更新接收端。
9.4 配置变更
关闭通知或停用账号/API 会暂停已有待发事件,恢复后继续;关闭期间完成的订单不新建通知。变更接收地址会影响尚未确认事件的后续投递,已发出的请求无法追回。
平台需保持队列与计划任务正常运行。接入方仍应保留查单和对账任务,不能把没有收到通知直接视为下单失败。
商品变更通知
商品通知与订单通知分别配置。平台通常每 5 分钟检查当前经销商可见目录,发生变化后向“商品变更通知地址”发送 POST JSON。通知只提示重新同步,不附带完整商品列表。
| 字段 | 类型 | 说明 |
|---|---|---|
| event | string | 固定 catalog.changed |
| user_id | integer | 当前经销商用户 ID |
| revision | string | 64 位 SHA-256 目录版本 |
| timestamp | integer | 通知生成时间,Unix 秒 |
{
"event": "catalog.changed",
"user_id": 123,
"revision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"timestamp": 1792627200
}签名及回执
请求头为 X-Api-Key、X-Timestamp、X-Signature,原文使用接收地址的路径和原始正文。商品通知当前只使用密钥 A,只有 B 密钥的账号不会发送商品通知。
接收方验签后,应按 user_id + revision 去重,可靠保存同步任务再确认;重新分页读取 /products 并处理新增、修改和移除。成功返回纯文本 success 或 ok,去除首尾空白后必须完全一致。未确认通常约 5 分钟后重试。
HTTP 200
Content-Type: text/plain
success两类回执不能混用
| 项目 | 订单结果通知 | 商品变更通知 |
|---|---|---|
| 去重键 | event_id | user_id + revision |
| 签名密钥 | 优先 A,仅有 B 时使用 B | 仅 A |
| 成功回执 | JSON:event_id + status=success | 纯文本 success 或 ok |
| 接收后的动作 | 查原单并取得当前可交付结果 | 重新分页同步目录 |
接收地址建议分开,例如 /callback/order 与 /callback/catalog。不要只凭 URL 或 JSON 中的 event 值信任通知,必须完成签名校验。
异常处理与联调验收
| HTTP 状态 | 含义 | 接入方处理 |
|---|---|---|
| 401 | 账号、时间或签名失败 | 核对 API 状态、密钥和签名原文 |
| 403 | IP 或访问权限不允许 | 核对出口 IP、白名单及访问开关 |
| 404 | 记录不可见或原单暂未查到 | 核对本人订单;超时场景继续查原 UUID |
| 422 | 参数或业务条件不满足 | 读取 message / errors,不盲目重试 |
| 429 | 触发限流 | 降速、退避,读取 Retry-After 如有返回 |
| 5xx / 超时 | 服务或网络结果不明确 | 保留原交易,查单后再决策 |
网关配置每分钟 120 次请求限制,共享给该鉴权账户的相关调用。请为查单、目录和提卡统筹频率,避免并发轮询挤占下单额度。
{"message":"接口签名不正确"}
{"message":"请求数据校验失败",
"errors":{"quantity":["下单数量不符合商品限购范围"]}}以上为错误结构示意,具体文案可能变化,不应把中文 message 当固定错误码。协议未定义统一数值业务错误码,也没有公开取消或退款接口;售后联系平台处理。
接入验收清单
| 检查项 | 通过标准 |
|---|---|
| 身份与签名 | 账户查询成功;错误密钥、过期时间及非白名单请求被拒绝 |
| 目录与价格 | 完整读取分页;正确处理授权、上下架、售价和库存变化 |
| 卡密与直充 | 各用平台指定测试商品验证;直充字段完整且数量为 1 |
| 重复提交 | 同 UUID 同参数只产生一笔订单和一次扣款 |
| 超时恢复 | 模拟丢失响应后按原 UUID 找回订单,不生成新采购 |
| 交付与通知 | 能处理批量提卡、重复通知及两类不同回执 |
| 金额与对账 | 订单总额、余额变化和最终失败退款结果一致 |
联调下单会使用该经销商余额,应由平台提供测试账号、测试商品和限额。上线前确认实际域名、账号、出口 IP、回调地址及密钥轮换联系人。
签名校验向量
以下固定数据仅用于离线核对不同语言的签名实现,不能直接用于在线请求,固定时间戳会过期。密钥为演示值,不对应任何账户。
| 参数 | 值 |
|---|---|
| secret | demo-secret-not-for-production |
| X-Timestamp | 1792627200 |
| METHOD | POST |
| path | /open/v1/orders |
原始 JSON 正文
下方为参与签名的单行原文,可直接复制。不要在正文末尾添加换行。
{"product_id":123,"quantity":1,"request_id":"c2fbd360-750b-4e77-9041-9b3bc5344683","max_total_cents":10200}预期签名
91b9876e54560e80c397325b8011f8e2f9f7ea85eb4464bf82a5f51f4c505064PHP 离线校验
<?php
$secret = 'demo-secret-not-for-production';
$body = '{"product_id":123,"quantity":1,'
. '"request_id":"c2fbd360-750b-4e77-9041-9b3bc5344683",'
. '"max_total_cents":10200}';
$canonical = "1792627200\nPOST\n/open/v1/orders\n".$body;
$actual = hash_hmac('sha256', $canonical, $secret);
$expected = '91b9876e54560e80c397325b8011f8e2f9f7ea85eb4464bf82a5f51f4c505064';
if (!hash_equals($expected, $actual)) {
throw new RuntimeException('签名校验失败');
}
echo "signature OK\n";常见差异
时间戳使用毫秒、path 带域名或查询串、换行变成 CRLF、正文末尾多出换行、签名后重新格式化 JSON、把 HMAC 当普通 SHA256,都会导致验签失败。查询参数本身不进入当前签名原文。
PHP 服务器调用示例
示例使用 PHP 8 和 cURL。API_BASE 填 https://www.k8s2024.com,不带 /open/v1;API_KEY 与 API_SECRET 从安全配置读取。以下只调用账户查询,未自动采购商品。
<?php
function callApi(string $method, string $path, string $body = '',
string $query = ''): array {
$base = rtrim((string)getenv('API_BASE'), '/');
$key = (string)getenv('API_KEY');
$secret = (string)getenv('API_SECRET');
if (!str_starts_with($base, 'https://') || !$key || !$secret) {
throw new RuntimeException('请配置 HTTPS 域名和接口凭据');
}
$ts = (string)time();
$raw = $ts."\n".$method."\n".$path."\n".$body;
$sign = hash_hmac('sha256', $raw, $secret);
$ch = curl_init($base.$path.($query === '' ? '' : '?'.$query));
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/json',
'X-Api-Key: '.$key, 'X-Timestamp: '.$ts,
'X-Signature: '.$sign,
],
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$failed = $response === false;
curl_close($ch);
if ($failed) {
throw new RuntimeException('连接未确认,交易须查原单');
}
return ['http_status' => $status,
'body' => json_decode($response, true, 512,
JSON_THROW_ON_ERROR)];
}
$result = callApi('GET', '/open/v1/account');
// 判断 http_status 后处理 body;不要输出密钥或卡密到日志。POST 时先生成并保存 request_id,将参数编码一次得到 $body,再用同一字符串签名和发送。自行实现查单及重试策略,不要在网络异常时自动生成新 UUID。