开发
API

接入准备

一、开发者接入准备

1.1 创建小程序

需要提前在字节小程序开放平台创建小程序,获取小程序 appid,此时只是完成了小程序的命名;

操作指引:https://microapp.bytedance.com/

1.2 完成小程序主体认证

创建小程序后,需要完成小程序主体认证,主体认证逻辑详见文档;

操作指引:https://microapp.bytedance.com/docs/zh-CN/mini-app/introduction/plug-in/register-auth/entity-authentication

1.3 完善并配置小程序基础信息

完善小程序基础信息,包括「小程序简介」、「小程序头像」、「小程序图标」、「服务类目」,关键字搜索配置等;

1.4 加签验签开发

  1. 完成进件后,开发者可在字节开放平台-【某小程序】-【功能】-【支付】-【担保交易设置】中查看支付系统秘钥 SALT;
  1. 调用支付相关接口需要需要带上签名参数,推荐使用小程序开发者工具对加签逻辑进行校验,详细加签规则参考下方 DEMO;
  2. 构造交易数据并签名必须在开发者服务端完成,应用私钥绝对不能保存在开发者客户端/前端中,也不能从开发者服务端下发。

二、服务商接入准备

2.1 创建服务商应用

服务商需要在【字节小程序开放平台-第三方平台】创建应用,这个条件是服务商完成后续工作的前提;

操作指引:https://microapp.bytedance.com/docs/zh-CN/mini-app/thirdparty/overview/create/

2.2 完成小程序授权

小程序拥有者确认授权后,服务商才能代为开发管理;

操作指引:

https://microapp.bytedance.com/docs/zh-CN/mini-app/thirdparty/overview-guide/authorization

https://microapp.bytedance.com/docs/zh-CN/mini-app/thirdparty/overview-guide/encryption

2.3 为授权小程序开发并部署代码包

服务商需要为授权小程序开发代码并进行部署;

操作指引:https://microapp.bytedance.com/docs/zh-CN/mini-app/thirdparty/overview-guide/development-process

2.4 加签验签开发

  1. 服务商为自己进件成功后,可以在第三方平台-【某第三方平台】-【设置】-【开发设置】中查看分配的秘钥;
  1. 调用支付相关接口需要需要带上签名参数,推荐使用小程序开发者工具对加签逻辑进行校验,详细加签规则参考下方 DEMO;
  2. 构造交易数据并签名必须在服务商的服务端完成,应用私钥绝对不能保存在服务商客户端/前端中,也不能从服务商服务端下发。

三、签名 DEMO

3.1 回调签名算法

支付回调通知开发者服务端时,会使用如下的算法进行签名,供开发者验证请求的来源:

  1. 将所有字段(验证时注意不包含 sign 签名本身,不包含空字段与 type 常量字段)内容与平台上配置的 token 一起,按照字典序排序
  2. 所有字段内容连接成一个字符串
  3. 使用 sha-1 算法计算字符串摘要作为签名

上述步骤计算出的签名 signature,和支付回调请求体里面的 signature 对比,如果不一致,说明请求不可信任(如被篡改)。

java(jdk 1.8)示例

 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));
 }
 if (Signature.equals(signBuilder.toString())) {
     return echostr;
 }
 return "";

golang 示例

 sortedString := make([]string, 4)
 sortedString = append(sortedString, token)
 sortedString = append(sortedString, timestamp)
 sortedString = append(sortedString, nonce)
 sortedString = append(sortedString, msg)
 sort.Strings(sortedString)
 h := sha1.New()
 h.Write([]byte(strings.Join(sortedString, "")))
 bs := h.Sum(nil)
 _signature := fmt.Sprintf("%x", bs)

nodejs 示例(Koa)

// http handler GET
function handler(ctx) {
   const { signature, timestamp, msg: '', nonce, echostr } = ctx.query;
   const strArr = [token, timestamp, nonce, msg].sort();
   const str = strArr.join('');
   const _signature = require('crypto').createHash('sha1').update(str).digest('hex');
}

3.2 回调响应

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

{
  "err_no": 0,
  "err_tips": "success"
}

3.3 请求签名算法

发往小程序服务端的请求,在没有特殊说明时,均需要使用担保支付秘钥进行签名,用于保证请求的来源:

  1. sign, app_id , thirdparty_id 字段用于标识身份字段,不参与签名。将其他字段内容(不包含 key)与支付 SALT 一起进行字典序排序后,使用&符号链接
  2. 使用 md5 算法对该字符串计算摘要,作为结果
  3. 参与加签的字段均以 POST 请求中的 body 内容为准, 不考虑参数默认值等规则. 对于对象类型与数组类型的参数, 使用 POST 中的字符串原串进行左右去除空格后进行加签
  4. 如有其他安全性需要, 可以在请求中添加 nonce 字段, 该字段无任何业务影响, 仅影响加签内容, 使同一请求的多次签名不同.

php 示例加签

<?php
function sign($map) {
    $rList = array();
    foreach($map as $k =>$v) {
        if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id")
            continue;
        $value = trim(strval($v));
        $len = strlen($value);
        if ($len > 1 && substr($value, 0,1)=="\"" && substr($value,$len, $len-1)=="\"")
            $value = substr($value,1, $len-1);
        $value = trim($value);
        if ($value == "" || $value == "null")
            continue;
        array_push($rList, $value);
    }
    array_push($rList, "your_payment_salt");
    sort($rList, 2);
    return md5(implode('&', $rList));
}
?>

golang 示例加签

import (
   "crypto/md5"
   "fmt"
   "sort"
   "strings"
)

// 支付密钥值
const salt = "your_payment_salt"

/*
    paramsMap: POST 请求中的字符串转换为 map
    eg:
        "{\"a\":\"string\",\"b\":1,\"c\":true}"
                ==>
        map[string]interface {}{"a":"string", "b":1, "c":true}
*/
func getSign(paramsMap map[string]interface{}) string {
   var paramsArr []string
   for k, v := range paramsMap {
      if k == "other_settle_params" {
          continue
      }
      value := strings.TrimSpace(fmt.Sprintf("%v", v))
      if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") && len(value) > 1 {
         value = value[1 : len(value)-1]
      }
      value = strings.TrimSpace(value)
      if value == "" || value == "null" {
         continue
      }
      switch k {
      // app_id, thirdparty_id, sign 字段用于标识身份,不参与签名
      case "app_id", "thirdparty_id", "sign":
      default:
         paramsArr = append(paramsArr, value)
      }
   }

   paramsArr = append(paramsArr, salt)
   sort.Strings(paramsArr)
   return fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(paramsArr, "&"))))
}

nodejs 示例(Koa)

// params 代表请求内容
var skip_arr = ["thirdparty_id", "app_id", "sign"];
var paramArray = new Array();
for (var k in params) {
  if (skip_arr.indexOf(k) != -1) {
    continue;
  }
  if (params[k] == "") {
    continue;
  }
  paramArray.push(params[k]);
}
paramArray.push(SALT);
paramArray.sort();
var signStr = paramArray.join("&");
return md5(signStr);

Java(jdk 1.8) 示例加签

// 支付密钥值
private static final String SALT = "your_payment_salt";

// paramsMap 参数含义解释同 golang
public static String getSign(Map<String, Object> paramsMap) {
    List<String> paramsArr = new ArrayList<>();
    for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
        String key = entry.getKey();
        if (key.equals("other_settle_params")) {
            continue;
        }
        String value = entry.getValue().toString();

        value = value.trim();
        if (value.startsWith("\"") && value.endsWith("\"") && value.length() > 1) {
            value = value.substring(1, value.length() - 1);
        }
        value = value.trim();
        if (value.equals("") || value.equals("null")) {
            continue;
        }
        switch (key) {
            case "app_id":
            case "thirdparty_id":
            case "sign":
                break;
            default:
                paramsArr.add(value);
                break;
        }
    }
    paramsArr.add(SALT);
    Collections.sort(paramsArr);
    StringBuilder signStr = new StringBuilder();
    String sep = "";
    for (String s : paramsArr) {
        signStr.append(sep).append(s);
        sep = "&";
    }
    return md5FromStr(signStr.toString());
}

public static String md5FromStr(String inStr) {
    MessageDigest md5;
    try {
        md5 = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return "";
    }

    byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8);
    byte[] md5Bytes = md5.digest(byteArray);
    StringBuilder hexValue = new StringBuilder();
    for (byte md5Byte : md5Bytes) {
        int val = ((int) md5Byte) & 0xff;
        if (val < 16) {
            hexValue.append("0");
        }
        hexValue.append(Integer.toHexString(val));
    }
    return hexValue.toString();
}

python 示例加签(python2 和 python3 注意 get_sign 返回值的处理!!!)

# -*- coding: UTF-8 -*-
import hashlib

# 支付密钥值
SALT = "your_payment_salt"


# params_map 参数含义解释同 golang
def get_sign(params_map):  # type: (dict[str, any]) -> str
    params_list = []
    for k, v in params_map.items():
        if k == "other_settle_params":
            continue
        value = str(v).strip()
        if value.startswith("\"") and value.endswith("\"") and len(value) > 1:
            value = value[1: len(value) - 1]
        value = value.strip()
        if value == "" or value == "null":
            continue
        if k not in ("app_id", "thirdparty_id", "sign"):
            params_list.append(value)
    params_list.append(SALT)
    params_list.sort()
    original_str = str("&").join(params_list)
    # python2
    return hashlib.md5(original_str).hexdigest()
    # python3
    # return hashlib.md5(original_str.encode("utf-8")).hexdigest()

.net C# 示例加签

// 支付密钥值
private const string Salt = "your_payment_salt";

private static string GetSign(Dictionary<string, Object> paramsDict)
{
    var paramsList = new ArrayList();
    foreach (var (k, v) in paramsDict)
    {
        if ("other_settle_params".Equals(k))
        {
            continue;
        }

        var value = v.ToString().Trim();
        if (value.StartWith("\"") && value.EndsWith("\"") && value.Length > 1)
        {
            value = value.Substring(1, value.Length - 1);
        }

        value = value.Trim();
        if ("".Equals(value) || "null".Equals(value))
        {
            continue;
        }

        switch (k)
        {
            case "app_id":
            case "thirdparty_id":
            case "sign":
                break;
            default:
                paramsList.Add(value);
                break;
        }
    }

    paramsList.Add(Salt);
    // 按照 ASCII 编码进行排序
    var paramsArray = (string[]) paramsList.ToArray(typeof(string));
    Array.Sort(paramsArray, string.CompareOrdinal);
    var signStr = string.Join("&", paramsArray);

    return GetMd5(signStr);
}

private static string GetMd5(string sDataIn)
{
    var md5 = new MD5CryptoServiceProvider();
    byte[] bytValue, bytHash;
    bytValue = Encoding.UTF8.GetBytes(sDataIn);
    bytHash = md5.ComputeHash(bytValue);
    md5.Clear();
    var sTemp = bytHash.Aggregate("", (current, t) => current + t.ToString("X").PadLeft(2, '0'));

    return sTemp.ToLower();
}
点击纠错
该文档是否对你的开发有所帮助?
有帮助
没帮助
该文档是否对你的开发有所帮助?
有帮助
没帮助