抖音开放平台Logo
开发者文档
控制台
  • 接入前准备
  • 通用能力
  • 餐饮
  • 大交通
  • 酒旅
  • 酒店行业新预售券解决方案
  • 酒店行业日历房解决方案
  • 度假行业解决方案
  • 景区行业解决方案
  • 景区创建订单
  • 创建订单(SPI)
  • 景区订单状态查询
  • 景区回调审核退款
  • 景区履约验券
  • 景区确认订单
  • 景区取消订单通知
  • 景区退款结果通知
  • 景区退款审核
  • 景区支付通知
  • 景区预订信息校验
  • 日历票价格库存属性发布
  • 日历票商品查询
  • 三方码发布
  • 日历票商品发布
  • 团购核销
  • 门店管理
  • 景区日历票商品类目
  • 通用错误码
  • 综合
  • 历史版本文档(不推荐)
  • 接口说明

    抖音侧通知第三方创建景区订单。
      1.ClientKey 维度默认配置是 抖音侧支付成功后 通知第三方创单,不额外通知支付成功。
      2.抖音请求第三方超时,如支付前创单则默认创单成功,抖音会走后续链路支付订单;如支付后创单,重试仍超时,则会拒单。

    基本信息

    Scope
    life.capacity.trip_order_create
    权限要求
    景区行业解决方案-景区创建订单
    回调场景
    景区创建订单

    请求头

      Content-Type:application/json
      X-Bytedance-Logid: 请求 logid, 用于问题排查用
      x-life-clientkey: 服务商应用的 client_key

    请求参数

    名称
    类型
    是否必填
    描述
    示例值
    order_id
    string
    抖音侧订单 ID(可作幂等键)
    100001
    account_id
    string
    商家ID
    poi_id
    string
    抖音POI_ID(即门店id)
    product_id
    string
    抖音侧商品 ID
    1773200310436864
    product_out_id
    string
    第三方商品 ID
    1773200310436864
    sku_id
    string
    抖音侧规格 ID
    200001
    sku_out_id
    string
    第三方规格 ID
    300001
    count
    int
    购买份数(等于「发放凭证」接口的 copies)
    3
    traveler_info
    object
    出行人群(对应商品 crowds)
    -total_num
    int32
    出行人数
    1
    -diff_target_crowd
    bool
    是否区分人群
    -crowd_list
    list
    人群列表
    --crowd_type
    int32
    人群类型
    Child=1 儿童
    Adult=2 成人
    Old=3 老人
    Student=4 学生
    Special=5 特殊人群
    Male=6 男士
    Female=7 女士
    Group=8 团体
    Couple=9 情侣
    Military = 10 军人
    Teacher = 11 教师
    Disabled = 12 残疾
    --traveler_num
    int32
    此人群的适用人数
    biz_type
    int
    业务类型,1 预售券,2 日历票
    2
    amount
    struct
    金额信息
    - pay_amount
    int
    支付金额,分
    8000
    - origin_amount
    int
    原始金额,分
    10000
    buyer
    struct
    购买人信息,默认只强制留手机号
    - name
    string
    姓名,加密,可能为空串,默认不强制购买人留姓名
    小明
    - phone
    string
    联系电话,加密,肯定有
    17812342702
    create_order_time_unix
    int
    创建订单时间戳,秒
    1678856518
    book_start_day
    string
    日历票下单参数,预定开始日期,yyyy-MM-dd
    2023-03-15
    book_end_day
    string
    日历票下单参数,预定结束日期,yyyy-MM-dd
    2023-03-16
    tourists
    list<struct>
    日历票下单参数,游玩人信息,根据商品配置留资
    - name
    string
    姓名,加密
    小红
    - phone
    string
    联系电话,加密
    18112345678
    - license_type
    int
    证件类型
    1 身份证
    2 港澳通行证
    3 台湾通行证
    4 回乡证
    5 台胞证
    6 护照
    7 外籍护照
    8 外国人永久居留证
    1
    - license_id
    string
    证件 ID,加密
    11204416541220243X
    remark
    string
    日历票下单参数,备注要求
    需要 xxxxx
    refund_rule
    struct
    日历票下单参数,退改规则
    -auto_refund_time
    int
    自动退,离园日 24 时往后推的秒数
    -can_refund_partly
    bool
    是否支持部分退
    -auto_verify_timestamp
    int64
    自动核销时间
    - refund_type
    int
    退改类型,1 未使用随时退,2 不可退,3 有条件退
    3
    - refund_details
    list<struct>
    退改详情,支持多个阶梯退
    -- refund_time
    int
    当退改类型为有条件退时必填,入园日 24 时往前倒推的秒数
    21600
    -- refund_fee_type
    int
    当退改类型为有条件退时必填,退改手续费类型,1 金额,2 比例
    2
    -- refund_fee
    int
    当退改类型为有条件退时必填,退改手续费,如果类型为金额则单位为分,如果类型为比例则单位为万分位
    1234
    ticket_rule
    struct
    票务规则,包含凭证方式、券码类型、券码服务商
    - code_sending_info
    list<int32>
    凭证发放方式,多选
    (建议全部写入list,凭证回调时,如果有额外类型的凭证会报错)
    1 身份证
    2 券号
    3 券码
    6 URL 链接
    - code_type
    int32
    券码类型,日历票 code_type = 2
    2 第三方券
    - url_type
    int32
    如果code_sending_info=6 凭证类型为url,则必填
    1 静态二维码
    2 其他
    ticket_specification
    struct
    日历票下单参数,票种规格说明
    - ticket_session
    strcut
    场次
    -- ticket_session_name
    string
    名称
    上午场
    -- ticket_session_time
    string
    时间
    8:00-9:00
    - ticket_seat
    string
    坐席
    普通座
    - ticket_area
    string
    区域
    上行
    解密方法
      1.根据 ClientKey 找到 ClientSecret,将 ClientSecret 向左右使用字符补齐 32 位/裁剪至 32 位,
      a.补齐:补位字符:#, 先补左侧再补右侧再补左侧······直到补满 32 位。
      b.裁剪:先裁剪左侧再裁右侧再裁左侧······直到剩余 32 位。
      c.(正常不需要补齐,secret 默认为 32 位,此举是为了以防万一)
      2.将 ClientSecret 作为 Key, 右侧 16 位为向量 IV
      3.将密文进行 base64 解码。
      4.使用 AES-256-CBC 模式解密解码后的密文,对齐使用 PKCS5Padding 方式
    Golang SDK
    package utils import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/base64" ) // AesDecrypt 解密函数 // encryptedStr:base64后的密文 // secret:appid/client_key对应的client_secret // return: []byte 明文 func AesDecrypt(encryptedStr string, secret string) ([]byte, error) { // 加密字符串进行base64解码 decodeBytes, err := base64.StdEncoding.DecodeString(encryptedStr) if err != nil { return nil, err } key, iv := parseSecret(secret) block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() blockMode := cipher.NewCBCDecrypter(block, iv[:blockSize]) origData := make([]byte, len(decodeBytes)) blockMode.CryptBlocks(origData, decodeBytes) origData = PKCS5UnPadding(origData) return origData, nil } // parseSecret 将secret解析为key和iv func parseSecret(secret string) ([]byte, []byte) { // secret对齐为32位 secret = cutSecret(secret) secret = fillSecret(secret) key, iv := secret, secret[16:] return []byte(key), []byte(iv) } func fillSecret(secret string) string { if len(secret) >= 32 { return secret } rightCnt := (32 - len(secret)) / 2 leftCnt := 32 - len(secret) - rightCnt var byt bytes.Buffer byt.Write(bytes.Repeat([]byte("#"), leftCnt)) byt.WriteString(secret) byt.Write(bytes.Repeat([]byte("#"), rightCnt)) return byt.String() } func cutSecret(secret string) string { if len(secret) <= 32 { return secret } rightCnt := (len(secret) - 32) / 2 leftCnt := len(secret) - 32 - rightCnt return secret[leftCnt: 32+leftCnt] } func PKCS5UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] }
    Java SDK
    package com.douyin.open.goodlife; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class SignUtil { /* * appid/client_key对应的client_secret TODO 这里换成服务商的appsecret */ private static final String secret = "12345678901234566543210987654321"; private static final String key; private static final String iv; static { key = parseSecret(secret); iv = key.substring(16); } /** * @Description AES解密 * @param data base64后的密文 * @return 明文 */ public static String decryptAES(String data) throws Exception { try { byte[] encrypted1 = decode(data);//先用base64解密 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original); return originalString.trim(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * base64编码 */ public static String encode(byte[] byteArray) { return new String(Base64.getEncoder().encode(byteArray)); } /** * base64解码 */ public static byte[] decode(String base64EncodedString) { return Base64.getDecoder().decode(base64EncodedString); } private static String parseSecret(String secret) { secret = fillSecret(secret); secret = cutSecret(secret); return secret; } private static String cutSecret(String secret) { if (secret.length() <= 32) { return secret; } int rightCnt = (secret.length() - 32) / 2; int leftCnt = secret.length() - 32 - rightCnt; return secret.substring(leftCnt, 32 + leftCnt); } private static String fillSecret(String secret) { if (secret.length() >= 32) { return secret; } int rightCnt = (32 - secret.length()) / 2; int leftCnt = 32 - secret.length() - rightCnt; StringBuilder sb = new StringBuilder(""); for (int i = 0; i < leftCnt; i++) { sb.append('#'); } sb.append(secret); for (int i = 0; i < rightCnt; i++) { sb.append('#'); } return sb.toString(); } }

    响应参数

    名称
    类型
    是否必填
    描述
    示例值
    data
    struct
    - error_code
    int
    错误码,合法范围为 [0,999999]
    0
    - description
    string
    错误信息
    - order_out_id
    string
    外部订单 ID,当处理成功时必须返回且不为空
    - confirm_info
    struct
    确认接单信息,支付后创单模式必须返回
    -- confirm_mode
    int
    接单模式,1 同步接单,2 异步接单
    1
    -- confirm_result
    int
    接单结果,同步接单时必填,1 接单,2 拒单
    1

    响应示例

    { "data": { "error_code": 0, "description": "", "order_out_id": "123123", "confirm_info": { "confirm_mode": 1, "confirm_result": 1 } } }

    错误码

      重试-最多12次
      创建订单失败时,若请求未触达商家(如网络错误)或商家返回明确错误码(error_code=100)抖音会进行重试
      重试策略:采用退避策略第一次间隔 5s,之后每次之间间隔 10s、20s、40、60s、60s(60s 最多重试12次)。
      幂等
      为防止重试导致的重复下单,服务商可根据抖音订单 Id 做幂等处理
    error_code
    错误场景描述
    商家原因描述(可自定义返回)
    0
    成功
    1
    库存不足
    2
    商品已下架
    3
    当前出行人已购票
    4
    当前日期不可预订
    5
    年龄不符合,仅限指定年龄的用户购买
    6
    性别不符合,仅限指定性别的用户购买
    7
    身份证限购,用户已达购买上限
    8
    手机号限购,用户已达购买上限
    9
    订单购买数量超过上限
    10
    用户地区不符合,仅限特定地区的用户购买
    11
    其他原因限购(请返回具体原因)
    12
    缺少手机号信息
    13
    缺少证件信息
    14
    缺少出行人姓名
    15
    出行人数和份数不匹配
    19
    手机号格式问题
    20
    证件号格式问题
    21
    姓名格式问题
    22
    商家账户余额不足,无法下单(服务商场景使用)
    23
    价格不一致
    100
    商家系统内部异常,需要抖音侧重试调用
    999999
    其他原因不可预订,请返回具体原因(抖音不重试调用)