抖音开放平台Logo
开发者文档
“/”唤起搜索
控制台
  • 交易能力
  • 支付产品介绍
  • 支付能力接入准备
  • 支付产品接入
  • 常见问题
  • 留资订单组件
  • 广告组件
  • 线索组件配置
  • 支付能力接入准备
    收藏
    我的收藏

    开发者接入准备

    步骤
    说明
      1.入驻开发者平台
    开发者需要完成抖音开发平台的入驻才能获取相应服务。操作指引参考入驻开发者平台
      2.创建小程序
    需要提前在抖音开放平台创建小程序,获取小程序 appid,此时只是完成了小程序的命名。操作指引参考抖音开放平台
      3.完成小程序主体认证
    创建小程序后,需要完成小程序主体认证
      4.完善并配置小程序基础信息
    完善小程序基础信息,包括「小程序简介」、「小程序头像」、「小程序图标」、「服务类目」,关键字搜索配置等。
      5.加签验签开发
      如果是非生活服务行业的小程序,需要先在「能力 > 支付 > 支付能力选择支付解决方案,之后可以操作进件。
      如果是生活服务行业的小程序,不需要此步骤。
    开发测试阶段可以新建商户号,新商户号作为测试商户号,正式使用的商户号不会受到测试数据影响。
    完成进件后,在「能力 > 支付 > 支付产品管理 > 支付设置」中查看支付系统密钥 SALT。
    调用支付相关接口需要带上签名参数,推荐使用 IDE 对加签逻辑进行校验,详细加签规则参考 DEMO 示例
    构造交易数据并签名必须在开发者服务端完成,应用私钥绝对不能保存在开发者客户端/前端中,也不能从开发者服务端下发。
    推荐使用签名校验工具,辅助验证生产签名的准确性。

    服务商接入准备

    步骤
    说明
      1.入驻服务商平台
    服务商需要先完成抖音平台的入驻才能获取相应服务。操作指南参考入驻服务商平台
      2.创建服务商应用
    服务商需要在「抖音开放平台-服务商平台」创建第三方应用,这个条件是服务商完成后续工作的前提。
      3.完成小程序授权
    小程序拥有者确认授权消息加解密后,服务商才能代为开发管理。
      4.为授权小程序开发并部署代码包
    服务商需要为授权小程序开发代码并进行部署,详见开发流程
      5.加签验签开发
    服务商通过服务商获取服务商进件页面接口为自己进件成功后,可以在第三方平台-【第三方平台】-【设置】-【开发设置】中查看分配的密钥。
    调用支付相关接口需要带上签名参数,推荐使用小程序开发者工具对加签逻辑进行校验,详细加签规则参考 DEMO 示例
    构造交易数据并签名必须在服务商的服务端完成,应用私钥绝对不能保存在服务商客户端/前端中,也不能从服务商服务端下发。

    签名 DEMO

    DEMO 示例

    为方便开发者快速接入,我们提供了 Java 和 Go 语言对应的签名 DEMO 示例供参考。

    回调签名算法

    支付回调通知开发者服务端时,会使用如下的算法进行签名,供开发者验证请求的来源。
      1.在「抖音开放平台 > 小程序 > 能力 > 支付 > 支付产品管理 > 支付设置」中获取「Token(令牌)」, 按照接口文档将所有请求字段(验证时注意不包含签名本身,不包含空字段与回调类型标记 type 常量字段)内容与平台上配置的 token 一起,进行字典序排序。
      2.所有字段内容连接成一个字符串。
      3.使用 SHA-1 算法计算字符串摘要作为签名。
    上述步骤计算出的签名 signature,和支付回调请求体里面的 signature 对比,如果不一致,说明请求不可信任(如被篡改)。

    请求示例

    支付结果回调接口为例,小程序的 token 值为 token,请求回调参数如下:
    { "timestamp": "timestamp", "nonce": "nonce", "msg": "msg", "msg_signature": "msg_signature", "type": "payment" }
    Java(JDK 1.8)示例
    Golang 示例
    Node.js 示例(Koa)
    List<String> sortedString = Arrays.asList(token, timestamp, nonce, msg); String concat = sortedString.stream().sorted().collect(Collectors.joining("")); byte[] arrayByte = concat.getBytes(StandardCharsets.UTF_8); MessageDigest mDigest = MessageDigest.getInstance("SHA1"); byte[] digestByte = mDigest.digest(arrayByte); StringBuffer signBuilder = new StringBuffer(); for (byte b : digestByte) { signBuilder.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); } String signature = signBuilder.toString();

    回调响应

    在开发者服务端收到回调且处理成功后,需要按以下 json 返回表示处理成功,否则会认为通知失败进行重试。
    { "err_no": 0, "err_tips": "success" }

    请求签名算法

    发往小程序服务端的请求,在没有特殊说明时,均需要使用担保支付密钥进行签名,用于保证请求的来源:
      1.signapp_id thirdparty_idprod_id 字段用于标识身份字段,不参与签名。将其他请求参数的字段内容(不包含请求参数的 key)与支付 SALT 一起进行字典序排序后,使用&符号链接。
      2.使用 MD5 算法对该字符串计算摘要,作为结果。
      3.部分请求字段非必填或条件选填,在值为空的情况下不需要参与签名;参与加签的字段均以 POST 请求中的 body 内容为准,不考虑参数默认值等规则。对于对象类型与数组类型的参数,使用 POST 中的字符串原串进行左右去除空格后进行加签。
      4.如有其他安全性需要,可以在请求中添加 nonce 字段,该字段无任何业务影响,仅影响加签内容,使同一请求的多次签名不同。
      5.推荐使用签名校验工具,辅助验证生产签名的准确性。
    PHP 示例加签
    Golang 示例加签
    Java(JDK 1.8)示例加签
    <?php function sign($map) { $rList = []; foreach($map as $k =>$v) { if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id") continue; $value = trim(strval($v)); if (is_array($v)) { $value = arrayToStr($v); } $len = strlen($value); if ($len > 1 && substr($value, 0,1)=="\"" && substr($value, $len-1)=="\"") $value = substr($value,1, $len-1); $value = trim($value); if ($value == "" || $value == "null") continue; $rList[] = $value; } $rList[] = "your_payment_salt"; sort($rList, SORT_STRING); return md5(implode('&', $rList)); } function arrayToStr($map) { $isMap = isArrMap($map); $result = ""; if ($isMap){ $result = "map["; } $keyArr = array_keys($map); if ($isMap) { sort($keyArr); } $paramsArr = array(); foreach($keyArr as $k) { $v = $map[$k]; if ($isMap) { if (is_array($v)) { $paramsArr[] = sprintf("%s:%s", $k, arrayToStr($v)); } else { $paramsArr[] = sprintf("%s:%s", $k, trim(strval($v))); } } else { if (is_array($v)) { $paramsArr[] = arrayToStr($v); } else { $paramsArr[] = trim(strval($v)); } } } $result = sprintf("%s%s", $result, join(" ", $paramsArr)); if (!$isMap) { $result = sprintf("[%s]", $result); } else { $result = sprintf("%s]", $result); } return $result; } function isArrMap($map) { foreach($map as $k =>$v) { if (is_string($k)){ return true; } } return false; } ?>