# 老群老频道模块商城 BOT 对接文档 适用公共接口契约 `1.2.0` 和商城框架 V5。模块管理 Telegram 账号、群频道库存、报价、预占、转让和退款判定;商城管理用户账户、余额、交易快照、支付授权、佣金和 Bot 消息。生产入口为 `https://qun.96aipro.cc`,服务 API 前缀为 `/module/v1`。 商城 BOT 当前尚未完成,本模块先按契约提供独立 API;真实平台联调安排在商城完成后。平台资金服务地址及凭据尚需由商城方提供;未接通资金服务时不能开始付费履约,也不能展示已扣款或退款到账。本地测试和测试替身不代表生产资金联调通过。部署版对接文档入口为 `/integration`,机器可读接口说明为 `/openapi.json`。 ## 资金和计时口径 现有契约只有整单 `charge` 和逐项 `refund`,没有冻结、解冻或支付担保接口。因此商城确认页应写“确认后从平台余额扣款;未完成项核实后退款至原平台余额”。不要把扣款显示为冻结,把退款显示为解冻。如产品必须采用真正冻结资金,需要平台另行实现资金协议并共同更新契约。 接收窗口为平台 `Funds.paid_at + 1 小时`。以付款时刻开始,发送邀请、消息补发、进程重启和继续支付均不延长窗口。发送较晚时,Bot 应显示真实截止时间和剩余分钟。到期只停止新转让;已经发出但结果不明的转让先核实,不能同时退款和继续转让。 商城充值仍由商城原充值流程负责。余额不足时保留原订单,在支付有效期内充值后用原 `continue-payment` 请求号重试;已过支付截止则关闭原交易、重新报价并让用户确认。模块不创建充值地址、不收取链上资金、不直接修改平台余额。 ## 鉴权和数据约定 所有服务接口使用 HTTPS 和 `Authorization: Bearer <模块服务凭据>`。管理员后台使用单独的登录会话;不能把后台 Cookie 当作平台服务凭据。模块调用平台资金服务使用另外一枚服务凭据。密钥只在服务端保存,不能放入 Telegram 按钮参数、前端 JavaScript 或日志。 金额固定为 `USDT`,JSON 使用整数,单位为 `0.01 USDT`。例如 `50` 表示 `0.50 USDT`,不能提交 `0.5`、`"50"`、`50.0` 或布尔值。金额由定点或整数运算产生,不能经二进制浮点舍入。本项成交金额等于本项基础金额加锁定佣金;主 Bot 佣金为零。所有金额均是交付项合计。 所有 ID 均为字符串,Telegram 用户或聊天 ID 用十进制字符串。本模块输入 ID 长度为 1–180 字符,接受英文字母、数字及 `_ . : @ -`,但拒绝单独的 `.` 或 `..`;金额最大为 `9000000000000` 个最小单位。时间用 UTC RFC 3339,例如 `2026-10-22T05:00:00Z`。用户界面可转成北京时间,API 不改变时区口径。 模块修复版为 `1.0.1`,公共协议仍为 `1.2.0`。交付包只返回仍对应当前交付项的有效邀请;换货、阻断、撤销和到期后,旧链接不会继续作为当前邀请返回。平台发送通知时应重新读取订单及 `delivery_package.ready`,不能从已过期事件缓存重新拼出旧链接。资金快照矛盾会暂停新转让;单项退款或结佣故障不会阻塞同单其他项。 一个群或频道对应一个最小交付项,`quantity=1`;购买三个频道返回三个稳定的 `item_id`。该 ID 必须贯穿报价、平台快照、建单、转让、退款和结佣,不能用 SKU 或库存 ID 代替。 ## 公共接口 | 方法及路径 | 作用 | 成功结果 | | --- | --- | --- | | `GET /module/v1/products` | 获取 SKU 及输入说明;可带 `cursor` | `Catalog` | | `POST /module/v1/quotes` | 校验规格、数量、目标并报价;不预占、不扣款 | `Quote` | | `POST /module/v1/orders` | 复制报价快照、建单并预占 | `Operation`,含 `order_id` | | `GET /module/v1/orders?request_id=...` | 用原建单号查回订单 | `Order` | | `GET /module/v1/orders/{order_id}` | 查询原快照和逐项履约结果 | `Order` | | `POST /module/v1/orders/{order_id}/continue-payment` | 首次启动或恢复原支付流程 | `Operation` | | `POST /module/v1/orders/{order_id}/cancel` | 关闭未支付交易,确认后释放预占 | `Operation` | | `POST /module/v1/orders/{order_id}/actions` | 逐项人工交付、退款或换货 | `Operation` | | `GET /module/v1/operations/{request_id}` | 查询写动作 | `Operation` | | `GET /module/v1/orders/{order_id}/items/{item_id}/asset` | 公共契约文件领取接口;本模块无账号文件交付 | 无可领取文件 | 本模块 `module_id` 为 `qun`,SKU 格式为 `group-2025` 或 `channel-2025`,后缀是有效销售年份。实际 SKU 和可售量始终从服务读取;不要硬编码库存数量或从 Telegram 自行推算库存。公共契约的 `products` 基础字段是 SKU、名称、单位和输入说明,本模块的扩展字段以部署的 `/openapi.json` 和实际响应为准。 目录单页返回,当前调用应省略 `cursor`,`next_cursor=null`。本模块的 Product 扩展包括 `type`(`group/channel`)、`year`、`unit_price`(整数分)、`stock`(当前可售数量)、`stock_cap`(人工上限,可为 `null`)、`enabled`、`currency` 和 `sales_enabled`。平台根据 `unit_price` 计算用户成交价;目录不是价格承诺,仍须调用报价。模块未开售时 `sales_enabled=false`,平台应显示维护或即将开售。 报价请求示例: ```json {"sku_id":"channel-2025","quantity":2,"target":{},"parameters":{}} ``` 当前版本要求请求的 `target` 和 `parameters` 均为空对象;报价响应中会明确返回 `target.recipient_policy=first_invite_joiner` 及 `parameters.type/year`,供平台锁定快照。 ### 订单邀请包和通知扩展 `GET /module/v1/orders/{order_id}` 在公共 `Order` 外扩展 `delivery_package`: ```json { "ready": true, "individual_links": [{"item_id":"item-1","url":"https://t.me/+example"}], "folder_links": [{"url":"https://t.me/addlist/example","account_group":"group-1","item_ids":["item-1","item-2"]}], "warnings": [], "join_deadline": "2026-10-22T06:00:00Z", "notice": "请仅转发给指定接收人" } ``` 这是字段格式示例,链接不可使用。平台应等待 `ready=true` 再一次性发送完整包;实际数组取决于购买数量和 Telegram 文件夹权限。`warnings` 用于说明分组或无法创建文件夹等情况。`ready=false` 时应继续查询,不能把空包显示为发送完成。 订单另扩展 `version` 和 `notification_events`。每条通知事件含 `id,type,text,created_at`。平台定时查询订单,并按“模块订单 ID + 事件 ID + 目标 Bot/接收人”持久化发送与重试记录;消息发送失败只重试该事件。`version` 帮助发现变化,不能代替通知事件防重。该方案无需另设未经约定的回调地址或通知凭据。 邀请消息发送前重新读取当前订单包,不使用通知事件之外的旧链接缓存。退款、交付阻断或撤销后,查询结果会隐藏相关链接。换货保持原 `item_id` 并生成新邀请通知事件,平台应提示旧链接已停用。文件领取接口要求查询参数 `platform_user_id`,先核验付款用户和交付项归属,然后返回 `404 NOT_FOUND`,因为本模块交付的是所有权而不是账号文件。 ## 平台接入顺序 1. 读取目录,按群、频道分两个入口,再按年份、价格和库存展示。售罄可以显示,但不能跳过报价检查。 2. 用户选择商品并输入 `1–100` 的购买数量。调用 `POST /quotes`,请求必须包含 `sku_id, quantity, target, parameters`,无目标或额外参数也传 `{}`。 3. 平台计算各项成交价及佣金,展示确认订单。按钮只携带服务端快照引用,服务端复核操作者,不能信任按钮中的金额。 4. 用户确认后,平台先持久化 `transaction_id`、固定建单 `request_id`、付款用户、来源 Bot、代理、报价及逐项金额,再调用模块建单。首次建单必须在报价有效期内。 5. 获得 `order_id` 后查询 `Order`,逐项核验交易、用户、报价、金额、ID、支付及预占截止。平台内部绑定模块订单并登记支付授权;该授权写入接口不开放给模块。 6. 调用 `continue-payment`。模块先查询付款事实,仅在未付款且授权有效时调用原扣款号。成功意味着已确认平台 `paid` 并已持久化履约待办,不能视为已转让。 7. 平台轮询订单及资金结果,取完整邀请包后一次发送。通知失败只重发通知,不能重建订单、重扣款或重转让。 8. 交付以接收人所有权核实为准;平台据逐项状态汇总成功、退款到账、退款处理中和人工处理数量,分别告知买家。 ### 建单示例 以下 ID 来自示意报价,真实调用应使用该次报价原值。每个频道基础价为 `0.50 USDT`,代理加价为 `0.10 USDT`。 ```json { "request_id": "mall-create-tx-1341", "transaction_id": "tx-1341", "platform_user_id": "user-88", "quote_id": "quote-from-module", "quote_version": "1", "currency": "USDT", "items": [ {"item_id": "item-from-quote-1", "sale_amount": 60, "commission_amount": 10}, {"item_id": "item-from-quote-2", "sale_amount": 60, "commission_amount": 10} ] } ``` 建单成功返回: ```json {"request_id":"mall-create-tx-1341","status":"succeeded","retryable":false,"order_id":"order-from-module"} ``` 继续支付提交: ```json {"request_id":"mall-pay-tx-1341"} ``` ## 邀请和所有权规则 各项单独邀请链接始终是交付邀请包的基础。批量购买尽力生成 Telegram 文件夹邀请链接。同一导出账号必须能访问各聊天,且聊天公开或该账号拥有管理邀请链接权限。因此多个账号名下的私有库存通常需要多条分组一键加入链接,无法把若干普通邀请链接拼成一条有效 `addlist`。Bot 必须展示返回的全部分组链接和全部单独链接,并说明各组包含的项目。Telegram 还会限制文件夹与邀请数量。[Telegram 文件夹权限及限制](https://core.telegram.org/api/folders) 链接出现只表示接收人可以加入,不表示所有权交付。接收规则为第一个符合条件的新加入用户;应使用稳定 Telegram 用户 ID,保存加入证据。出现身份冲突、无法确定最早加入人、外部请求结果不明时应转人工。所有权转让使用用户端 MTProto 的 `messages.editChatCreator`,Bot API 本身不能完成;需满足 Telegram 的两步验证、会话及密码等待期等条件。以实际 RPC 返回为准,不承诺所有账号可立即转让。[所有权转让接口](https://core.telegram.org/method/messages.editChatCreator) Bot 邀请通知建议使用: ```text 📦 频道/群组订单邀请链接 请只转发给指定接收人,不要让陌生人加入。 默认转让给第一个符合条件的加入用户。 【一键加入】 按模块返回分组列出,无法创建时说明并保留单独链接。 【单独链接】 逐项列出全部链接。 请在 YYYY-MM-DD HH:mm(北京时间)前加入。 转让成功后,请勿点击 Telegram 的“拒绝接受频道转让”按钮。 ``` 通知中的成功数量必须来自核实所有权的 `delivered` 项。退款到账数量必须来自平台 `refund_state=succeeded`。不能用 `order_state=closed`、提交退款请求成功或发送消息成功替代这些事实。 ## 资金服务对接 模块请求的平台地址由运营配置,前缀为 `/funds/v1`。平台必须实现以下接口,金额、退款账户及代理都取自平台已锁定快照,模块不会传任意退款金额。 | 方法及路径 | 请求体 | | --- | --- | | `GET /transactions/{transaction_id}` | 无,返回 `Funds` | | `POST /transactions/{transaction_id}/charge` | `request_id` | | `POST /transactions/{transaction_id}/close` | `request_id, reason` | | `POST /transactions/{transaction_id}/items/{item_id}/refund` | `request_id, reason, evidence` | | `POST /transactions/{transaction_id}/items/{item_id}/settle` | `request_id, evidence` | | `GET /operations/{request_id}` | 无,返回 `Operation` | 已知交易尚未登记授权时返回 `200`,其中 `authorization=null`,不能返回“不存在”。`paid_at` 在 `paid` 时有值,其余支付状态为 `null`。退款不抹去 `paid` 历史。退款按项全额返还原付款平台账户余额,佣金同时由平台冲减;不能在模块后台修改金额补偿。 平台暂不支持退款、资金接口超时或退款结果不明时,订单保留阻断和待处理记录,Bot 应写“未完成项正在核实/退款待处理,请联系客服”,不能宣称退款成功。恢复连接后先查询原退款请求及当前资金事实,再决定原号续试;人工售后也必须通过平台统一账本和业务防重流程。 ## 人工售后接口 调用 `POST /module/v1/orders/{order_id}/actions` 的身份必须是平台服务,`operator_id` 是平台已认证的操作员。请求必须包含全部公共字段: ```json { "request_id":"manual-refund-1341-item1", "action":"refund", "item_id":"item-from-order", "operator_id":"operator-7", "reason":"已核实无法交付", "evidence":{"case_id":"support-1341"}, "parameters":{} } ``` `refund` 逐项申请全额退款;若此前存在未知转让,操作员必须先核实没有在途效果,再在证据中提交 `no_inflight_confirmed:true`,模块还会查询确认原库存账号仍拥有所有权。仅填写该字段不能绕过所有权检查。 `deliver` 是核实人工已完成的所有权交付,不是强制改为成功。优先核实本项已锁定接收人;尚未锁定时,`parameters.recipient` 提供 `{"id":"稳定Telegram用户ID"}`,需要时提供该用户的 `access_hash`。模块查询所有权成功后才记录交付及结佣。退款阻断项拒绝再次交付。 `replace` 仅允许没有在途转让、没有交付和退款阻断,且仍处于原一小时时间窗的项目。替换同规格真实库存,旧库存隔离,原 `item_id`、成交金额、退款金额和截止保持不变;平台收到新邀请事件后发送新链接。所有人工操作均保存操作者、原因和证据。 ## 幂等和错误处理 除报价外,每个 POST 都需要固定业务 `request_id`。同一动作重试沿用原号;建单、继续支付、扣款、关闭、每项退款和每项结佣分别有自己的号。幂等键由服务身份及请求号组成,并校验动作、资源与业务参数;同号异参返回 `IDEMPOTENCY_CONFLICT`。换号也不能绕过同交易单次建单、同项单次退款等业务防重。 `200` 的查询结果仍需读取 `Operation.status`;`202` 表示处理中。`succeeded` 是该动作的历史成功,不保证当前资金或订单仍维持原状态。当前事实另查 `Order/Funds`。`rejected` 且 `retryable=true` 才能同号同参再试。 HTTP 超时、断线或 `5xx` 后先查原动作;查不到可原号重送。`404` 不代表可以改号重建或释放库存。未支付取消或支付超时必须取得平台 `close` 成功的事实,才能释放预占。已付订单即使支付截止已过仍可恢复履约、查询、退款和结佣。 | HTTP | 常见 code | 平台处理 | | --- | --- | --- | | 400 | `INVALID_REQUEST` | 修正字段,不能盲重试 | | 401 / 403 | `UNAUTHORIZED` / `FORBIDDEN` | 检查凭据、归属和权限 | | 404 | `NOT_FOUND` | 查原资源和请求,不自动重建 | | 409 | `IDEMPOTENCY_CONFLICT` | 查原请求,不能覆盖 | | 409 | `QUOTE_EXPIRED` / `QUOTE_CHANGED` | 重新报价并重新确认 | | 409 | `AUTHORIZATION_PENDING` / `INSUFFICIENT_BALANCE` | 条件满足且原授权有效时原号续试 | | 409 | `PAYMENT_CLOSED` / `PAYMENT_EXPIRED` / `ALREADY_PAID` / `STATE_CONFLICT` | 按原订单和资金事实处理 | 错误 JSON 必须含 `code,message,retryable`;客户端用 `code/status` 分支,不解析中文说明。 ## JSON Schema 必填字段 以下摘自随项目提供的 `common-contract.v1.2.openapi.json`。扩展字段不能替代必填字段;`{}` 和 `null` 仅在相应类型允许时使用。 | 结构 | 必填字段 | | --- | --- | | `Error` | `code, message, retryable` | | `Operation` | `request_id, status, retryable` | | `Command` | `request_id` | | `ReasonCommand` | `request_id, reason` | | `EvidenceCommand` | `request_id, evidence` | | `RefundCommand` | `request_id, reason, evidence` | | `Product` | `sku_id, name, sale_unit` | | `Catalog` | `items` | | `QuoteRequest` | `sku_id, quantity, target, parameters` | | `QuoteItem` | `item_id, sku_id, quantity, base_amount, target, parameters` | | `Quote` | `module_id, quote_id, quote_version, expires_at, currency, items` | | `PricedItem` | `item_id, sale_amount, commission_amount` | | `CreateOrder` | `request_id, transaction_id, platform_user_id, quote_id, quote_version, currency, items` | | `OrderItem` | `item_id, sku_id, quantity, base_amount, target, parameters, sale_amount, commission_amount, fulfillment_state` | | `Order` | `module_id, order_id, transaction_id, request_id, platform_user_id, quote_id, quote_version, currency, order_state, payment_expires_at, reserved_until, items` | | `AdminAction` | `request_id, action, item_id, operator_id, reason, evidence, parameters` | | `Authorization` | `order_id, expires_at` | | `FundsItem` | `item_id, sale_amount, commission_amount, refund_state, refunded_amount, net_commission_amount` | | `Funds` | `transaction_id, currency, payment_state, paid_at, authorization, items` | 条件必填:拒绝的 `Operation` 另需 `code,message`;非拒绝动作 `retryable=false`。建单成功另带 `order_id`。已交付的 `OrderItem` 另需 `delivery_result`,渠道有任务号时带 `channel_task_id`。`Funds.payment_state=paid` 要求非空 `paid_at`,其他状态要求 `null`。`reserved_until` 必须存在,可为 `null`;有库存预占时支付截止不能晚于预占截止。 | 字段 | 允许值 | | --- | --- | | `order_state` | `open, closed` | | `payment_state` | `unpaid, paid, closed` | | `fulfillment_state` | `pending, running, unknown, delivered, stopped` | | `refund_state` | `none, processing, succeeded` | | `Operation.status` | `processing, succeeded, rejected` | | `AdminAction.action` | `deliver, refund, replace` | 公共 Schema 不足以表达所有业务约束。实现还须检查 `1–100` 数量、整数范围、非负金额、成交金额关系、逐项集合完全相同、订单归属及并发互斥。