支付能力接入准备
收藏我的收藏
开发者接入准备
步骤 | 说明 |
| |
| |
| |
| 完善小程序基础信息,包括「小程序简介」、「小程序头像」、「小程序图标」、「服务类目」,关键字搜索配置等。 |
|
开发测试阶段可以新建商户号,新商户号作为测试商户号,正式使用的商户号不会受到测试数据影响。 完成进件后,在「能力 > 支付 > 支付产品管理 > 支付设置」中查看支付系统密钥 SALT。 构造交易数据并签名必须在开发者服务端完成,应用私钥绝对不能保存在开发者客户端/前端中,也不能从开发者服务端下发。 |
服务商接入准备
步骤 | 说明 |
| |
| |
| |
| |
| 构造交易数据并签名必须在服务商的服务端完成,应用私钥绝对不能保存在服务商客户端/前端中,也不能从服务商服务端下发。 |
签名 DEMO
DEMO 示例
为方便开发者快速接入,我们提供了 Java 和 Go 语言对应的签名 DEMO 示例供参考。
- •open-pay-signature-java-demo 适用于 Java 开发者。
- •open-pay-signature-go-demo 适用于 Go 开发者。
回调签名算法
支付回调通知开发者服务端时,会使用如下的算法进行签名,供开发者验证请求的来源。
- 1.在「抖音开放平台 > 小程序 > 能力 > 支付 > 支付产品管理 > 支付设置」中获取「Token(令牌)」, 按照接口文档将所有请求字段(验证时注意不包含签名本身,不包含空字段与回调类型标记 type 常量字段)内容与平台上配置的 token 一起,进行字典序排序。
- 2.所有字段内容连接成一个字符串。
- 3.使用 SHA-1 算法计算字符串摘要作为签名。
上述步骤计算出的签名 signature,和支付回调请求体里面的 signature 对比,如果不一致,说明请求不可信任(如被篡改)。
请求示例
{ "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.
sign
、app_id
、thirdparty_id
、prod_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; } ?>