电商3.0-运营平台开放接口
    • 概览
    • 快速开始
    • 请求与加密
    • 响应处理
    • 示例与自测
    • 数据约定
    • 返回码与排查
    • 更新日志
    • 订单
      • 查询订单列表
        POST
      • 查询订单详情
        POST
      • 取消预约
        POST
    • 售后
      • 申请退单
        POST
      • 查询退单
        POST
    • 景区
      • 查询景区列表
        POST
      • 查询景区详情
        POST

    请求与加密

    本页说明请求的组成、加密和签名规则。所有接口都遵循同一套规则,各接口页只描述自己的业务参数。
    本页用「必须」「不得」「可以」表述要求。除密钥和 IV 的取法是本协议自定的规则外,每一步编码都对应一项公开的行业标准(索引见本页「编码规范与标准对照」),与开发语言无关:按本页的规范形式实现,任何语言算出的字节都与示例与自测逐字节一致。
    本文档中「位」只表示比特(bit),字符串长度一律写作「个字符」。

    协议一览#

    项规则
    传输HTTPS
    方法POST
    请求头Content-Type: application/json
    字符编码UTF-8,不带 BOM
    请求体JSON 对象,固定 4 个成员 key、timestamp、body、sign,不超过 256 KiB
    业务参数JSON 对象,AES-256-CBC 加密(PKCS#7 填充),标准 Base64 编码后放进 body
    签名sign = MD5(body + timestamp + secret),32 个十六进制字符,大写
    时间戳Unix 毫秒数,JSON 整数,只参与签名
    响应Base64 密文,或以 { 开头的明文 JSON,见响应处理

    接入凭证#

    平台管理员在开放平台后台为调用方创建「应用实例」并授权接口后,提供三项凭证:
    凭证长度用途
    key32 个字符应用实例标识,随每次请求明文传递
    secret64 个字符签名密钥,只参与签名计算,不随请求传递
    aesKey32 个 ASCII 字符(UTF-8 编码后 32 字节,即 256 bit)业务参数加密密钥,同样不随请求传递
    各环境的网关地址见概览。

    请求结构#

    所有接口均为 POST,请求头 Content-Type: application/json,字符编码为 UTF-8,请求体固定为 4 个字段:
    字段类型必填说明
    keystring是应用实例的 key
    timestampnumber是发起请求时的毫秒时间戳(JSON 整数),如 1794370000000,格式见「timestamp 格式」
    bodystring是业务参数 JSON 字符串加密后的 Base64 文本,见「加密规则」
    signstring是签名,32 个十六进制字符(大写),见「签名规则」
    {
      "key": "调用方的key",
      "timestamp": 1794370000000,
      "body": "43EYRVvX8lxqL6rR/XZ+iBSNgsos0HHX8JUVQJsDDhk=",
      "sign": "6D1F10D8A10FB9580628DA5C4C00E1CD"
    }
    各接口页的「请求参数」和请求示例「业务参数(加密前)」就是加密前的业务参数,不能直接发送。没有业务参数时,对空对象 {} 加密。

    HTTP 与 JSON 报文#

    方法为 POST(RFC 9110 §9.3.3)。上面的 JSON 文本就是整个 HTTP 请求体:不得放进查询串或表单,不得压缩(不带 Content-Encoding,RFC 9110 §8.4)。请求体不得超过 256 KiB(262144 字节),超出时返回 code 为 500(HTTP 状态码 200)。
    Content-Type(RFC 9110 §8.3)必须为 application/json,可以带 ; charset=utf-8,不得声明其他字符集(application/json 本身不定义 charset 参数,RFC 8259 §11)。缺少该请求头,或使用 application/x-www-form-urlencoded、multipart/form-data、text/plain 等,返回 405 无效的请求内容。
    请求体字节必须是 UTF-8(RFC 3629),不得带 BOM(RFC 8259 §8.1)。四个字段的值都是 ASCII 字符。
    请求体必须是一个 JSON 对象(RFC 8259),只含上表 4 个成员,不得附加其他成员。成员名区分大小写(RFC 8259 §8.3);每个成员必须只出现一次(RFC 8259 §4 只是 SHOULD,本协议要求必须:成员重复时服务端取哪个值不确定)。成员顺序和成员之间的空白(RFC 8259 §2)不限。
    字符串值按 RFC 8259 §7 转义。服务端对 JSON 解析后的字符串值签名和解密,所以序列化器把 / 写成 \/(如 PHP json_encode 的默认行为)不影响结果;签名必须用转义前的原值计算。
    key、body、sign 的值首尾不得带空格、制表符或换行(body 首尾带空白时的结果见「Base64 变体与 URL 编码」)。
    任何字段都不做 URL 编码,见「Base64 变体与 URL 编码」。

    timestamp 格式#

    必须是 JSON 整数(RFC 8259 §6):只由十进制数字组成,不带引号、正负号、前导零、小数点和指数。1794370000000 正确;"1794370000000"、1794370000000.0、1.79437E12 都不符合本规范。
    取值为 Unix 纪元(1970-01-01T00:00:00Z)起的毫秒数,即 POSIX「Seconds Since the Epoch」(IEEE Std 1003.1,不计闰秒)× 1000,与 Java System.currentTimeMillis()、JavaScript Date.now() 相同。目前为 13 个数字,处于 RFC 8259 §6 所说的可互操作整数范围(绝对值不超过 2^53−1)内,JavaScript 的 Number 也能精确表示。
    参与签名的就是这串数字(见「签名规则」)。服务端把 timestamp 读成 64 位整数后,按十进制重新生成数字串来验签,所以带小数、指数或前导零的写法按原样签名会返回 1037;带引号且含小数点、指数或空格的写法返回 code 为 500。

    加密规则#

    对业务参数 JSON 字符串做 AES 加密,得到 body:
    项规则标准
    明文业务参数 JSON 文本,顶层为对象;按 UTF-8 编码成字节,不带 BOM。服务端按 UTF-8 解码解密后的字节,非法字节序列会被静默替换为 U+FFFD,不报错:明文用 GBK 等其他编码时,中文字段(如 refundReason、cancelReason)会变成乱码。请用示例与自测第五步含中文的向量核对 UTF-8 编码RFC 8259;RFC 3629
    算法AES,密钥 256 bit(32 字节),即 AES-256(Nk = 8)FIPS 197(2023 年更新版 FIPS 197-upd1)
    模式CBCNIST SP 800-38A §6.2
    填充PKCS#7:在明文末尾补 n 个值为 n 的字节,n = 16 − (明文字节数 mod 16),取值 1–16;明文恰为 16 字节的倍数时补一整块 16 个 0x10。Java 的名称 PKCS5Padding 沿用 PKCS #5(RFC 8018 §6.1.1,原定义为 8 字节块),用于 AES 时字节结果与 PKCS#7 相同;OpenSSL 和 .NET 的默认填充也是这种RFC 5652 §6.3(源自 PKCS #7 v1.5,RFC 2315 §10.3 注 2)
    密钥aesKey 字符串的 UTF-8 字节,共 32 字节。直接取字节,不得做 Hex 解码、Base64 解码或任何密钥派生,不得截断或补零—(本协议自定)
    IV密钥的前 16 个字节,即 aesKey 的前 16 个字符。IV 固定,不随请求传递。这一取法不符合 NIST SP 800-38A 附录 C「CBC 的 IV 必须不可预测」的要求,是本协议的约定:不得改用随机 IV 或加密库生成的默认 IV,不得把 IV 拼在密文前面发送;安全扫描工具提示「固定 IV」时也不要自行修改,否则返回 1020—(本协议自定)
    输出密文字节(16 的整数倍)按标准 Base64 编码:字母表 A–Z a–z 0–9 + /;必须带 = 填充;不得插入换行或任何空白;不得使用 URL-safe 字母表。结果是一行 ASCII 文本,长度为 4 的倍数RFC 4648 §4(§3.1 不换行,§3.2 带填充)
    业务参数 JSON 的字段顺序、是否带空格、非 ASCII 字符是否写成 \uXXXX,都不影响验签:服务端只对密文做签名校验,解密后再解析业务 JSON。业务参数必须按 RFC 8259 生成、顶层为对象;解密后不是 JSON 对象时(例如把 JSON 序列化了两次、顶层是数组),返回 1020,msg 为「业务参数格式有误,必须为JSON对象」。

    签名规则#

    sign = Base16大写( MD5( UTF-8字节( body + timestamp + secret ) ) )
    1.
    body:按「加密规则」得到的 Base64 文本,与请求中 body 的值逐字符相同。签名针对密文而不是明文,所以必须先加密、后签名。
    2.
    timestamp:请求中 timestamp 的十进制数字串,格式见「timestamp 格式」。
    3.
    secret:签名密钥原文,逐字符使用。
    4.
    三段按上述顺序直接拼接,中间没有分隔符,不做排序、不加字段名、不做 URL 编码。
    5.
    拼接结果按 UTF-8 取字节(三段都是 ASCII,字节数等于字符数),计算 MD5(RFC 1321),得到 16 字节摘要。这是对拼接串直接做 MD5,secret 只是拼在末尾的普通字符串;不是 HMAC-MD5(RFC 2104),不得使用 HmacMD5、hash_hmac、createHmac 之类的 API。
    6.
    摘要按 Base16(RFC 4648 §8,字母表见其 Table 5)编码为 32 个十六进制字符。RFC 4648 的 Base16 不区分大小写,本协议规定输出必须为大写 A–F。不得只取 16 个字符,不得用 Base64 表示摘要。

    安全#

    timestamp 只参与签名,服务端不校验时效,也没有防重放机制。 同一个已签名的请求可以被重复提交。申请退单、取消预约在订单状态不变时,重放只会得到已有结果;上一笔退单被关闭或取消后重新预约时,重放会被当作新请求办理。请务必开启 IP 白名单并保管好 secret,只通过 HTTPS 调用。
    网关按顺序校验请求,各步骤不通过时的返回见返回码与排查的「网关校验顺序」。

    编码规范与标准对照#

    标准索引#

    环节标准(章节)本协议的取值规则所在
    HTTP 方法、请求头RFC 9110 §9.3.3(POST)、§8.3(Content-Type)POST;application/json「HTTP 与 JSON 报文」
    字符编码RFC 3629(UTF-8);RFC 8259 §8.1(不带 BOM)UTF-8,无 BOM「HTTP 与 JSON 报文」、「加密规则」、响应处理
    JSONRFC 8259(§4 对象、§6 数字、§7 字符串、§8.3 成员名比较)请求报文、业务参数、统一响应「HTTP 与 JSON 报文」、「加密规则」
    时间戳RFC 8259 §6(整数文本形式);IEEE Std 1003.1(POSIX)「Seconds Since the Epoch」Unix 毫秒(不计闰秒),十进制整数「timestamp 格式」
    分组密码FIPS 197(2023 年更新版 FIPS 197-upd1)AES-256(Nk = 8)「加密规则」
    工作模式NIST SP 800-38A §6.2(CBC)CBC;IV 由本协议规定为密钥前 16 字节,不符合该标准附录 C 的 IV 要求「加密规则」
    填充RFC 5652 §6.3(源自 PKCS #7,通称 PKCS#7 填充)块长 16 字节,补 1–16 字节「加密规则」
    Base64RFC 4648 §4,及 §3.1(不换行)、§3.2(带填充)、§3.3(拒绝字母表外字符)标准字母表 + /,带 =,单行「加密规则」、响应处理
    摘要RFC 1321(MD5);不是 HMAC(RFC 2104)16 字节「签名规则」
    十六进制RFC 4648 §8(Base16,不区分大小写)32 个字符,大写(本协议规定)「签名规则」
    百分号编码 / 表单编码RFC 3986 §2.1;WHATWG URL Standard §5(application/x-www-form-urlencoded)不使用「Base64 变体与 URL 编码」
    标准自带的测试向量只用来单独检验各环节的基础实现,不能直接套用本协议的密钥和填充规则:
    MD5 用 RFC 1321 附录 A.5,Base64 用 RFC 4648 §10,可以直接使用;RFC 1321 的摘要是小写,比对时忽略大小写。
    AES-256-CBC 用 NIST SP 800-38A 附录 F.2.5。它的密钥、IV、明文都是十六进制,要先做 Hex 解码;明文正好 4 个整块、不含填充,自检时临时关闭填充并使用向量自带的 IV,或只比对带填充输出的前 64 字节。
    自检通过后,必须恢复 PKCS#7 填充,密钥取 aesKey 的 UTF-8 字节,IV 取密钥前 16 字节,再按示例与自测的自测判据核对整条流水线。

    字节流水线#

    请求
      业务参数 JSON 文本 ──UTF-8──▶ 明文字节
      明文字节 ──PKCS#7 填充 + AES-256-CBC(密钥 = aesKey 的 UTF-8 字节,IV = 密钥前 16 字节)──▶ 密文字节
      密文字节 ──Base64(RFC 4648 §4,带 =,单行)──▶ body
      body + timestamp 数字串 + secret ──UTF-8──▶ 签名输入字节 ──MD5──▶ 16 字节 ──Base16 大写──▶ sign
      {key, timestamp, body, sign} ──JSON 序列化 + UTF-8──▶ HTTP 请求体
    
    响应
      HTTP 响应体字节 ──UTF-8──▶ 文本 ──以 { 开头──▶ 明文 JSON,直接读取
                                    └─其他──▶ Base64 解码 ──▶ AES-256-CBC 解密,去 PKCS#7 填充 ──UTF-8──▶ 统一响应 JSON

    Base64 变体与 URL 编码#

    body 和 sign 写进 JSON 字符串时保持原样。百分号编码(RFC 3986 §2.1)和表单编码(application/x-www-form-urlencoded,空格写成 +,+ / = 写成 %2B %2F %3D)只用于 URL 和表单,本协议的请求体是 JSON,任何字段都不做 URL 编码,接口路径也不需要编码。
    服务端不对 key、timestamp、body、sign 做 URL 解码;URL-safe 字母表、百分号编码、表单编码和文本中间的任何空白或换行都会导致失败。body 必须按「加密规则」带 = 填充发送。
    签名在解密之前校验(见返回码与排查的「网关校验顺序」),所以出错时:
    body 中间被替换或插入了字符:签名按实际发送的值计算,返回 1020;按变换前的值计算,返回 1037。
    body 只在首尾多了空白或换行:服务端先去掉首尾空白再验签,按实际发送的值计算的签名也返回 1037(开发环境实测)。
    以示例与自测第一步的 body 为例:
    错误做法发送出去的 body返回
    URL-safe Base64(RFC 4648 §5,- _ 代替 + /)43EYRVvX8lxqL6rR_XZ-iBSNgsos0HHX8JUVQJsDDhk=1020 或 1037
    对 body 做百分号编码43EYRVvX8lxqL6rR%2FXZ%2BiBSNgsos0HHX8JUVQJsDDhk%3D1020 或 1037
    经过表单解码,+ 变成空格43EYRVvX8lxqL6rR/XZ iBSNgsos0HHX8JUVQJsDDhk=1020 或 1037
    MIME Base64(RFC 2045 §6.8,每 76 个字符插入 CRLF)、PEM 风格(RFC 7468,每 64 个字符换行)或每 60 个字符换行(Ruby encode64)超过换行宽度时,文本中间含 \r\n 或 \n1020 或 1037
    编码器在末尾加换行(上述编码器处理较短的密文,或 openssl enc -base64 不加 -A)43EYRVvX8lxqL6rR/XZ+iBSNgsos0HHX8JUVQJsDDhk=\n1037
    整个请求按表单提交(key=…&body=…)—405 无效的请求内容
    body 中出现 %、-、_、空格或换行,就不是本协议要求的形式。JSON 序列化器输出的 \/、+ 之类是 JSON 转义(RFC 8259 §7),解析后还原,不属于上述错误。

    各语言 API 对照#

    语言正确用法易混淆的错误用法
    Javajava.util.Base64.getEncoder() / getDecoder();Cipher.getInstance("AES/CBC/PKCS5Padding");getBytes(StandardCharsets.UTF_8);String.format("%02X", b)Base64.getMimeEncoder()(每 76 字符插入 CRLF)、getUrlEncoder()、getEncoder().withoutPadding();不带参数的 getBytes()(JDK 18 之前取平台默认字符集);new BigInteger(1, digest).toString(16)(丢掉前导 0);Mac.getInstance("HmacMD5");URLEncoder.encode
    Androidandroid.util.Base64.encodeToString(bytes, Base64.NO_WRAP)Base64.DEFAULT(输出带换行)、Base64.URL_SAFE
    C# / .NETConvert.ToBase64String / Convert.FromBase64String;Aes.Create() 后显式设置 Key = keyBytes、IV = keyBytes[0..16]、Mode = CipherMode.CBC、Padding = PaddingMode.PKCS7;Encoding.UTF8.GetBytes;Convert.ToHexString(.NET 5+,大写);new StringContent(json, Encoding.UTF8, "application/json")不设置 IV(沿用 Aes.Create() 随机生成的 IV);Base64FormattingOptions.InsertLineBreaks;WebEncoders.Base64UrlEncode;BitConverter.ToString(字节间带 -);HMACMD5;FormUrlEncodedContent;HttpUtility.UrlEncode
    Gobase64.StdEncoding;crypto/aes + cipher.NewCBCEncrypter(标准库不带 PKCS#7,需自行补齐);fmt.Sprintf("%X", md5.Sum(b));strconv.FormatInt(ts, 10)base64.URLEncoding、RawStdEncoding(不带填充)、RawURLEncoding;hmac.New(md5.New, …);url.Values、http.PostForm。注意 StdEncoding.DecodeString 会忽略 \r、\n,不能用来检验 body 是否合规
    PHPopenssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv) 后 base64_encode;strtoupper(md5($s));json_encode 生成请求体,CURLOPT_POSTFIELDS 传该字符串;时间戳先转成 intopenssl_encrypt 的 options 传 0 时已输出 Base64,再 base64_encode 会变成双重 Base64;chunk_split(base64_encode(...))(每 76 字符插入 \r\n,末尾也有);md5($s, true)(原始 16 字节);hash_hmac('md5', …);CURLOPT_POSTFIELDS 传数组或 http_build_query 的结果(按表单提交);urlencode
    Pythonbase64.b64encode;base64.b64decode(s, validate=True);hashlib.md5(b).hexdigest().upper();int(time.time() * 1000);requests.post(url, json=payload)base64.urlsafe_b64encode;base64.encodebytes(每 76 字符换行,末尾也有换行);binascii.b2a_base64(默认末尾带换行);b64decode 不加 validate=True 会静默丢弃字母表外字符;hmac.new(…, hashlib.md5);requests.post(url, data=payload)(表单编码);未取整的 time.time() * 1000(JSON 和签名串里都成为 1794420120822.102 这样的小数)
    Node.jsBuffer.toString('base64');crypto.createCipheriv('aes-256-cbc', key, iv)(默认 PKCS#7);digest('hex').toUpperCase();Date.now();fetch + JSON.stringify'base64url';crypto.createHmac('md5', …);请求体用 URLSearchParams 或 querystring.stringify;encodeURIComponent。注意 Buffer.from(s, 'base64') 接受 URL-safe 字母表并忽略空白,不能用来检验 body 是否合规
    RubyBase64.strict_encode64 / strict_decode64Base64.encode64(每 60 字符换行,末尾也有换行)、urlsafe_encode64;OpenSSL::HMAC
    PerlMIME::Base64::encode_base64($bytes, "");uc(Digest::MD5::md5_hex($s))encode_base64($bytes)(每 76 字符换行,末尾带 \n);md5_base64;URI::Escape::uri_escape
    C / OpenSSLEVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)(默认 PKCS#7);EVP_EncodeBlock(不换行);EVP_Digest(..., EVP_md5(), NULL);printf("%02X");命令行 openssl enc -base64 -A。用 EVP_DecodeBlock 解码响应时,返回长度包含 = 填充产生的 0 字节,必须减去末尾 = 的个数(88 个字符的示例响应:66 − 2 = 64 字节),或改用 EVP_DecodeUpdate / EVP_DecodeFinalBIO_f_base64 未设置 BIO_FLAGS_BASE64_NO_NL、EVP_EncodeUpdate / EVP_EncodeFinal、命令行不加 -A(每 64 字符换行,末尾也有换行);EVP_CIPHER_CTX_set_padding(ctx, 0);直接用 EVP_DecodeBlock 的返回值作为密文长度;HMAC(EVP_md5(), …)
    其他(Delphi、IVR 平台脚本等)Base64 选「标准字母表、带填充、不换行」;HTTP 组件选「原始请求体 / raw JSON」;MD5 输出转大写组件默认按行折返的 Base64;「表单参数」「URL 参数」方式提交;HMAC-MD5
    无论用哪种语言,都按示例与自测的两条自测判据核对。
    修改于 2026-11-11 19:17:40
    上一页
    快速开始
    下一页
    响应处理
    Built with