12 KiB
agent-open-api Specification
Purpose
定义代理开放接口的签名认证、数据权限、卡查询、套餐列表、预充值钱包和钱包购买能力,确保第三方代理系统可以在受控权限内对接业务接口。
Requirements
Requirement: 代理开放接口签名认证
系统 SHALL 为 /api/open/v1 下所有代理开放接口提供签名认证。调用方 MUST 在请求 Header 中传入 X-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。系统 MUST 校验后台账号存在、账号类型为代理账号、账号状态启用、已绑定启用状态的代理店铺、密码正确、时间戳在允许时间窗内、nonce 未被使用、签名正确。系统 MUST NOT 依赖独立开放接口账号授权表;所有启用状态的代理账号默认允许调用开放接口。
签名原文 SHALL 使用以下格式:
METHOD + "\n" +
PATH + "\n" +
canonical_query_without_sign + "\n" +
SHA256(raw_body) + "\n" +
timestamp + "\n" +
nonce + "\n" +
account
签名算法 SHALL 使用 hex(HMAC-SHA256(password, sign_payload))。canonical_query_without_sign MUST 按参数名升序排列并排除 sign 字段。空 body 的 SHA256 MUST 按空字符串计算。
响应 MUST 使用统一格式 {code, msg, data, timestamp}。认证失败错误码 MUST 在 pkg/errors/ 定义,不得把 bcrypt、HMAC、数据库底层错误暴露给调用方。
Scenario: 签名认证通过
- WHEN 启用状态的代理账号携带正确密码、时间戳、nonce、请求参数和签名调用
/api/open/v1/cards/status - THEN 系统通过认证并将代理账号信息写入请求上下文
Scenario: 参数被篡改
- WHEN 请求中的 query 或 body 在签名后被修改
- THEN 系统验签失败并返回签名无效错误
Scenario: 密码错误
- WHEN 调用方传入的
X-Agent-Password与后台代理账号密码不匹配 - THEN 系统返回认证失败错误,不继续执行业务逻辑
Scenario: nonce 重放
- WHEN 同一账号在有效时间窗内重复使用相同
X-Agent-Nonce - THEN 系统返回重复请求错误,并拒绝执行业务逻辑
Scenario: 代理账号未启用
- WHEN 后台代理账号存在但账号状态为禁用
- THEN 系统返回认证失败错误,不继续执行业务逻辑
Requirement: 开放接口代理上下文与数据权限
系统 SHALL 将通过签名认证的代理账号映射为内部代理上下文。上下文 MUST 包含 ContextKeyUserID、ContextKeyUserType=UserTypeAgent、ContextKeyShopID、ContextKeySubordinateShopIDs。开放接口业务查询 MUST 只允许访问该代理店铺及其下级店铺范围内的卡、套餐、订单和钱包数据。
Scenario: 访问自己店铺的卡
- WHEN 代理开放接口账号查询自己店铺名下卡的流量、状态或实名状态
- THEN 系统返回该卡数据
Scenario: 访问下级店铺的卡
- WHEN 代理开放接口账号查询下级店铺名下卡的流量、状态或实名状态
- THEN 系统返回该卡数据
Scenario: 访问无权限卡
- WHEN 代理开放接口账号查询非自己及非下级店铺名下的卡
- THEN 系统返回
CodeForbidden,消息为“无权限操作该资源或资源不存在”
Scenario: 企业账号调用开放接口
- WHEN 企业账号使用正确后台密码调用代理开放接口
- THEN 系统拒绝调用,因为开放接口仅允许代理账号
Requirement: 卡流量查询开放接口
系统 SHALL 提供 GET /api/open/v1/cards/traffic 接口,按 card_no 查询单卡流量和套餐信息。card_no MUST 支持 ICCID、虚拟号、MSISDN 中任一种可解析为 IoT 卡的标识。接口 MUST 只返回单卡套餐,不返回设备级套餐信息。
响应 data MUST 至少包含:
card_noactive_remaining_flow_mb:当前生效套餐剩余流量active_total_flow_mb:当前生效套餐总流量active_used_flow_mb:当前生效套餐已使用流量active_packages:当前生效套餐列表pending_packages:待生效套餐列表active_expires_at:当前生效套餐过期时间
active_packages 每项 MUST 包含:package_code、total_flow_mb、expires_at、start_at、package_name、series_name、package_type、package_type_name。pending_packages 每项 MUST 包含:package_code、total_flow_mb、package_name、series_name、package_type、package_type_name、valid_days、priority。
流量口径 MUST 满足:
- 总流量使用套餐真总量快照
data_limit_mb - 已用流量使用换算后的展示已用量
min(data_usage_mb * display_gain_ratio_snapshot, data_limit_mb) - 剩余流量使用
max(data_limit_mb - used_flow_mb, 0) - 响应 MUST NOT 包含
virtual_*、ratio、threshold、reduction、虚流量、停机阈值等内部字段或文案
Scenario: 查询有生效套餐的卡流量
- WHEN 代理查询自己名下单卡,且该卡存在生效中的正式包和加油包
- THEN 系统返回当前生效套餐列表、总流量、换算后已用流量、剩余流量和当前过期时间
Scenario: 查询待生效套餐
- WHEN 代理查询单卡流量,且该卡存在待生效套餐
- THEN 系统在
pending_packages中返回套餐编码、真总流量、套餐名称、系列名称、套餐类型、有效天数和生效优先级
Scenario: 不暴露虚流量
- WHEN 卡的套餐启用了虚流量
- THEN 响应只返回真总量、换算后已用量和剩余量,不返回任何虚流量字段或内部倍率字段
Scenario: 查询无套餐的卡
- WHEN 代理查询有权限但当前无生效或待生效套餐的卡
- THEN 系统返回空套餐列表,流量数值为 0
Requirement: 卡状态查询开放接口
系统 SHALL 提供 GET /api/open/v1/cards/status 接口,按 card_no 查询单卡状态。响应 data MUST 包含 card_no、card_status、card_status_name、stop_reason、stop_reason_name。卡状态 MUST 按现有网络状态映射为停机/正常:network_status=0 返回停机,network_status=1 返回正常。
Scenario: 查询正常卡状态
- WHEN 代理查询有权限且
network_status=1的卡 - THEN 系统返回
card_status=normal,card_status_name=正常
Scenario: 查询停机卡状态
- WHEN 代理查询有权限且
network_status=0的卡 - THEN 系统返回
card_status=stopped、card_status_name=停机以及停机原因
Requirement: 卡实名状态查询开放接口
系统 SHALL 提供 GET /api/open/v1/cards/realname-status 接口,按 card_no 查询单卡实名状态。响应 data MUST 包含 card_no 和 is_realnamed。当 real_name_status 为已实名常量时,is_realnamed MUST 为 true,否则为 false。
Scenario: 查询已实名卡
- WHEN 代理查询有权限且
real_name_status=1的卡 - THEN 系统返回
is_realnamed=true
Scenario: 查询未实名卡
- WHEN 代理查询有权限且
real_name_status=0的卡 - THEN 系统返回
is_realnamed=false
Requirement: 代理套餐列表开放接口
系统 SHALL 提供 GET /api/open/v1/packages 接口,分页返回代理可购买套餐列表。接口 MUST 复用后台代理套餐列表的权限过滤:只返回当前代理已分配且启用的非赠送套餐,并按代理自己的上下架状态过滤。接口 MUST 支持 page、page_size、package_type、series_id、package_name 查询参数,page_size 最大 100。
响应每项 MUST 包含:package_id、package_code、package_name、series_name、package_type、package_type_name、cost_price、retail_price、total_flow_mb、valid_days。retail_price MUST 返回代理配置的生效零售价;当代理零售价未配置时,按现有策略回退为成本价。
Scenario: 代理查询套餐列表
- WHEN 代理调用
GET /api/open/v1/packages?page=1&page_size=20 - THEN 系统分页返回该代理可购买套餐,并包含成本价和生效零售价
Scenario: 过滤未分配套餐
- WHEN 套餐未分配给当前代理
- THEN 该套餐不会出现在开放套餐列表中
Scenario: 生效零售价回退
- WHEN 代理套餐分配记录未配置零售价
- THEN 响应
retail_price返回成本价
Requirement: 代理钱包套餐购买开放接口
系统 SHALL 提供 POST /api/open/v1/wallet/package-orders 接口,允许代理使用预充值主钱包为指定卡购买套餐。请求 body MUST 包含 card_nos 和 package_code。package_code MUST 只允许一个,card_nos MUST 至少 1 个,最多 100 个。
系统 MUST 按卡独立创建后台钱包订单,复用后台代理钱包支付购买逻辑。每张卡购买成功后 MUST 返回订单号;购买失败时 MUST 返回该卡失败原因。任一卡失败 MUST NOT 回滚其他已成功卡的订单。
响应 data MUST 包含:batch_no、package_code、success_count、failed_count、orders、failed_cards。orders 每项 MUST 包含 card_no、order_no。failed_cards 每项 MUST 包含 card_no、code、message。
当任一卡购买失败时,外层 code/msg MUST 返回首条失败卡的错误码和原因,data MUST 保留批次明细;调用方 MUST 以 success_count、failed_count、orders、failed_cards 判断每张卡的处理结果,避免整批重试导致重复下单。
Scenario: 多卡部分成功
- WHEN 代理提交 3 张卡购买同一个套餐编码,其中 2 张成功、1 张失败
- THEN 系统外层
code和msg返回首条失败卡的错误码和原因,data返回success_count=2、failed_count=1、2 条订单号和 1 条失败卡原因
Scenario: 全部购买失败
- WHEN 代理提交的卡全部购买失败
- THEN 系统外层
code和msg返回首条失败卡的错误码和原因,data.failed_cards返回全部失败明细
Scenario: 单套餐编码限制
- WHEN 请求包含多个套餐编码或套餐编码为空
- THEN 系统返回参数错误,不创建订单
Scenario: 钱包余额不足
- WHEN 某张卡购买套餐时代理主钱包余额不足
- THEN 该卡返回失败原因“余额不足”,其他卡已成功订单不受影响
Scenario: 复用后台钱包支付
- WHEN 某张卡购买套餐成功
- THEN 系统创建已支付订单、扣减代理主钱包余额、创建主钱包扣款流水并激活套餐
Requirement: 代理预充值钱包余额开放接口
系统 SHALL 提供 GET /api/open/v1/wallet/balance 接口,查询当前代理预充值主钱包余额。响应 data MUST 包含 balance、frozen_balance、available_balance、currency。当主钱包不存在时,系统 MUST 返回 0 余额,不报错。
Scenario: 查询主钱包余额
- WHEN 代理主钱包余额为 10000 分、冻结余额为 2000 分
- THEN 系统返回
balance=10000、frozen_balance=2000、available_balance=8000
Scenario: 主钱包不存在
- WHEN 代理从未充值且主钱包不存在
- THEN 系统返回 0 余额和币种 CNY
Requirement: 代理预充值钱包流水开放接口
系统 SHALL 提供 GET /api/open/v1/wallet/transactions 接口,分页返回当前代理预充值主钱包流水。接口 MUST 与后台 GET /api/admin/shops/:shop_id/main-wallet/transactions 的返回字段保持一致,支持 page、page_size、transaction_type、start_date、end_date 查询参数,结果按 created_at DESC 排序。
响应每项 MUST 包含:id、transaction_type、transaction_subtype、amount、balance_before、balance_after、remark、created_at。
Scenario: 查询钱包流水
- WHEN 代理调用
GET /api/open/v1/wallet/transactions?page=1&page_size=20 - THEN 系统返回当前代理主钱包流水,字段与后台主钱包流水接口一致
Scenario: 按日期过滤流水
- WHEN 代理传入
start_date和end_date - THEN 系统只返回该日期范围内的主钱包流水
Requirement: 开放接口文档和路由注册
系统 SHALL 将代理开放接口注册到路由总入口和 OpenAPI 文档生成器。新增 Handler 后 MUST 同步更新 cmd/api/docs.go 和 cmd/gendocs/main.go 的 bootstrap.Handlers 初始化,确保接口文档中出现 /api/open/v1 下所有开放接口。
Scenario: 文档生成器包含开放接口
- WHEN 系统生成 OpenAPI 文档
- THEN 文档中包含
/api/open/v1下全部代理开放接口