carrier-contract.php

20260916-201504 轮 · 看它的历史
A. 注册表 fail-closed
  ✅ A1 easyparcel 已注册
  ✅ A2 未知 code 不认
  ✅ A3 未知 code 抛 ERR_UNKNOWN_CARRIER
  ✅ A4 SELF_PRINT 不是 adapter
  ✅ A5 labels() 不含 SELF_PRINT

B. 能力声明(Open API 六个能力齐全)
  ✅ B1 支持取号
  ✅ B2 支持取面单
  ✅ B3 支持余额
  ✅ B4 🔴 支持取消(换代后)
  ✅ B5 未知承运商不支持任何能力
  ✅ B6 服务档位是动态的(返回 null)
  ✅ B7 支持询价
  ✅ B8 支持轨迹
  ✅ B9 🔴 声明的每个能力都有对应实现
  ✅ B10 configFields 由 adapter 声明

C. 州名 → 州代码(Appendix III)
  ✅ C-Johor
  ✅ C-Kedah
  ✅ C-Kelantan
  ✅ C-Melaka
  ✅ C-Negeri Sembilan
  ✅ C-Pahang
  ✅ C-Pulau Pinang
  ✅ C-Perak
  ✅ C-Perlis
  ✅ C-Selangor
  ✅ C-Terengganu
  ✅ C-Sabah
  ✅ C-Sarawak
  ✅ C-Kuala Lumpur
  ✅ C-Labuan
  ✅ C-Putra Jaya
  ✅ C-别名 Penang
  ✅ C-别名 Malacca
  ✅ C-别名 Putrajaya
  ✅ C-联邦直辖区全称
  ✅ C-大小写与空格归一
  ✅ C-🔴 十六个州码互不相同
  ✅ C-🔴 库里真实存在的 Choose State 被拒
  ✅ C-🔴 Others 被拒
  ✅ C-🔴 Singapore 被拒
  ✅ C-空州名被拒

D. 长度与必填(超长抛错而不是截断)
  ✅ D1 正常值原样返回
  ✅ D2 首尾空白被 trim
  ✅ D3 恰好等于上限放行
  ✅ D4 超一个字节即拒
  ✅ D5 必填为空即拒
  ✅ D6 非必填为空返回空串
  ✅ D7 🔴 中文按字节算(12 字 = 36 字节 → 拒)
  ✅ D8 中文 11 字 = 33 字节 → 放行
  ✅ D9 重量为 0 被拒
  ✅ D10 重量缺失被拒
  ✅ D11 重量负数被拒
  ✅ D12 重量格式化两位小数

E. 两层错误判定
  ✅ E1 L1 都是 200 不抛
  ✅ E2 L1 401 → AUTH
  ✅ E3 L1 403 → AUTH
  ✅ E4 L1 400 按 data.errors 细分
  ✅ E5 🔴 L1 429 → RATE_LIMITED(不是余额不足)
  ✅ E6 🔴 body 说 200 但 HTTP 500 → 仍然抛
  ✅ E7 🔴 HTTP 200 但 body 说 401 → 仍然抛
  ✅ E8 L1 缺 status_code 时按 HTTP 判
  ✅ E9 L2 余额不足 → INSUFFICIENT
  ✅ E10 L2 地址不可达 → ADDRESS
  ✅ E11 L2 邮编问题 → ADDRESS
  ✅ E12 L2 缺必填 → BAD_PARAM
  ✅ E13 L2 已取消 → NOT_CANCELLABLE
  ✅ E14 L2 过了取消期限 → NOT_CANCELLABLE
  ✅ E15 L2 认不出的归 UPSTREAM,不猜
  ✅ E16 L2 空错误也不当成功
  ✅ E17a 🔴 返回值是已登记的错误码
  ✅ E17b 🔴 返回值不含收件人姓名
  ✅ E17c 🔴 返回值不含电话
  ✅ E17d 🔴 返回值不含地址

P. 电话规范化(只做最确定的两件事)
  ✅ P1 剥 +60 国际前缀
  ✅ P2 剥前导 0
  ✅ P3 去掉分隔符
  ✅ P4 已经是裸号的原样
  ✅ P5 🔴 未知国家不猜拨号码
  ✅ P6 新加坡拨号码
  ✅ P7 国家码统一大写
  ✅ P8 国家为空回落 MY

L. 面单地址挑选(A6 优先)
  ✅ L1 🔴 有 A6 优先 A6
  ✅ L2 没有 A6 退 A4
  ✅ L3 都没有则用 awb_url
  ✅ L4 🔴 全空返回空串(面单还没生成)

F. 出参形状(error_hint 不含上游文本)
  ✅ F1 fail.ok=false
  ✅ F2 fail.data=null
  ✅ F3 fail.error_code 原样
  ✅ F4 fail.error_hint 非空
  ✅ F5 🔴 error_hint 不含上游原文
  ✅ F6 ok.ok=true
  ✅ F7 ok.error_code 为空
  ✅ F8 shipment 运单号
  ✅ F9 shipment 真实承运商
  ✅ F10 shipment 未给的键有默认值
  ✅ F11 label 归一成字节

G. 配置 name 生成
  ✅ G1 nameFor
  ✅ G2 senderNameFor
  ✅ G3-client_id 🔴 归密文桶
  ✅ G4-client_id 不在明文桶
  ✅ G3-client_secret 🔴 归密文桶
  ✅ G4-client_secret 不在明文桶
  ✅ G3-access_token 🔴 归密文桶
  ✅ G4-access_token 不在明文桶
  ✅ G3-refresh_token 🔴 归密文桶
  ✅ G4-refresh_token 不在明文桶
  ✅ G5 🔴 token_expire 归明文桶
  ✅ G6 寄件人字段含必填的邮编
  ✅ G7-carrier_easyparcel_token_expire 🔴 在 common.php 里也被当成明文
  ✅ G7-carrier_easyparcel_account_currency 🔴 在 common.php 里也被当成明文
  ✅ G7-carrier_easyparcel_env 🔴 在 common.php 里也被当成明文
  ✅ G7-carrier_easyparcel_account_id 🔴 在 common.php 里也被当成明文
  ✅ G8 managed 字段清单
  ✅ G9 🔴 put 拒绝未声明的字段

I. 技术描述与用户提示分开
  ✅ I1 州名错有用户提示
  ✅ I2 用户提示里带上是哪个州
  ✅ I3 🔴 技术错误不产生用户提示
  ✅ I4 超长提示用中文字段名
  ✅ I5 🔴 超长提示不含内部字段名
  ✅ I6 fieldLabel 映射
  ✅ I6b 🔴 寄件人侧提示的是寄件人
  ✅ I6c 🔴 两侧的提示不相同
  ✅ I7 fieldLabel 认不出时原样返回
  ✅ I8 技术描述仍在 getMessage 供排障

H. 守卫:正常场景没被误伤
  ✅ H1 合法州名照常通过
  ✅ H2 合法长度照常通过
  ✅ H3 合法重量照常通过
  ✅ H4 成功响应不抛
  ✅ H7 合法电话照常通过
  ✅ H8 正常错误消息不会被误判成"不能取消"
  ✅ H5 已注册承运商照常解析得到
  ✅ H6 能力表非空

S. 🔴 审计打点的源码形态
  ✅ S1 🔴 没有 $this->_logAdminOp(它是全局函数)
  ✅ S2 🔴 没有 \app\AdminOpCode(类在全局命名空间)
  ✅ S3 🔴 _logAdminOp 第 2 个参数不是数组字面量
  ✅ S4 守卫:确实扫到了 _logAdminOp 调用
  ✅ S5 守卫:本次新增的那个 op_code 真的在用

T. 🔴 面单 202 的处置
  ✅ T1-202 🔴 HTTP 202 判成 LABEL_PENDING(不是 upstream)
  ✅ T1-400 🔴 HTTP 400 判成 LABEL_PENDING(不是 upstream)
  ✅ T1-404 🔴 HTTP 404 判成 LABEL_PENDING(不是 upstream)
  ✅ T1b-401 守卫:401 没被塞进 pending 那一档
  ✅ T1b-403 守卫:403 没被塞进 pending 那一档
  ✅ T1b-500 守卫:500 没被塞进 pending 那一档
  ✅ T1b-502 守卫:502 没被塞进 pending 那一档
  ✅ T2 🔴 而且排在"非 200 即错误"那一条之前(排后面等于没写)
  ✅ T3 守卫:确实扫到了 fetchLabel 的正文
  ✅ T3b 🔴 非 200 的 detail 带上了上游原话
  ✅ T3c upstreamNote 抠不出结构化字段时原样截,不返回空
  ✅ T3d upstreamNote 认得 message
  ✅ T3e 守卫:空体还是空(别拼一个只有分隔符的后缀)
  ✅ T4 嵌套 {message,code} 能拍平
  ✅ T5 扁平字符串数组照旧
  ✅ T6 守卫:空的还是空的(别拍出一个空串条目)
  ✅ T7 🔴 不再出现字面量 Array

U. 占位面单
  ✅ U1 姓名打码
  ✅ U2 电话留前 3 后 2
  ✅ U3 🔴 详细地址整段打码(门牌号本身就能定位到人)
  ✅ U4 城市/州保留(否则这张纸看不出发往哪,失去"像面单"的意义)
  ✅ U5 邮编保留
  ✅ U6 守卫:打码后仍有内容
  ✅ U7 非 ASCII 变星号(镜像里没有中文字体,见 DebugLabel 文件头)
  ✅ U8 PDF 里有特殊含义的括号被转义(否则内容流会断)
  ✅ U9 是合法 PDF 开头
  ✅ U10 有 EOF 尾(阅读器认它)
  ✅ U11 🔴 纸面写着 TEST LABEL
  ✅ U12 🔴 纸面写着不会被揽收/派送
  ✅ U13 🔴 纸面写着别贴到真包裹上
  ✅ U14 带上了对方的真实状态(说明这张纸为什么会存在)
  ✅ U15 🔴 明文电话没出现在 PDF 里
  ✅ U16 🔴 明文门牌地址没出现在 PDF 里
  ✅ U17 🔴 没有条码(扫得动的假面单最危险)
  ✅ U18 🔴 正式账号下 isEnabled 恒 false
  ✅ U19 🔴 isEnabled 同时要求 FF_DEBUG_MODE 与沙箱(两者是与,不是或)
  ✅ U20 守卫:确实扫到了 DebugLabel 的正文

PASS=174 FAIL=0