1. 开发者服务器接收消息推送
开发者需要按照如下步骤完成:
- 填写服务器配置
- 验证服务器地址的有效性
- 据接口文档实现业务逻辑,接收消息和事件
第一步:填写服务器配置
登录销赞云商家后,在「开发平台」-「消息推送」中,填写服务器地址(URL)、令牌(Token) 和 消息加密密钥(EncodingAESKey)等信息。
- URL: 开发者用来接收销赞云消息和事件的接口 URL。开发者所填写的URL 必须以 http:// 或 https:// 开头,分别支持 80 端口和 443 端口。
- Token: 可由开发者可以任意填写,用作生成签名(该 Token 会和接口 URL 中包含的 Token 进行比对,从而验证安全性)。
- 消息加密密钥: 由开发者手动填写或随机生成,将用作消息体加解密密钥。
同时,开发者可选择消息加解密方式:明文模式(默认)、兼容模式和安全模式。切换加密方式和数据格式需要提前配置好相关代码。
模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。切换加密方式和数据格式需要提前配置好相关代码,具体见消息加解密
第二步:验证消息的确来自销赞云
提交信息后,销赞云将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 | 描述 |
---|---|
signature | 签名串,signature由填写的token参数和请求中的timestamp参数、nonce参数组合而成。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验 signature 对请求进行校验(下面有校验方式)。若确认此次 GET 请求来自销赞云,请原样返回echostr参数内容,则接入生效,否则接入失败。 加密/校验流程如下:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,两者一致则表明该请求来源于销赞云,参数没有被篡改
使用本文中Encryptor类的signature方法来生成签名比较,示例PHP代码:
$sign = Encryptor::signature(Token, $timestamp, $nonce);
var_dump($sign == $_GET['signature']);
第三步:接收消息和事件
当某些特定的用户操作引发事件推送时(如订单创建、支付、取消等情况),销赞云服务器会将消息(或事件)的数据包以 POST 请求发送到开发者配置的 URL,开发者可以依据自身业务逻辑进行响应。 销赞云服务器在将消息发给开发者服务器地址后,销赞云服务器在五秒内收不到响应会断掉连接。如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。服务器收到请求必须做出下述回复,这样销赞云服务器才不会对此作任何处理,并且不会发起重试,详见下面说明:
- 直接回复success(推荐方式)
- 直接回复空串(指字节长度为0的空字符串,而不是结构体中content字段的内容为空)
如果开发者希望增强安全性,可以在开发者中心处开启消息加密。
1.1. 消息加解密
开发者在代替授权小程序接收和处理消息时,出于安全考虑,必须对消息收发的过程进行必须的加解密。 具体消息解密的步骤如下:
1.1.1. 验证消息签名
目的是保证消息是来自于销赞云服务器。 根据不同的加密方式,返回的消息格式也有所不同。 销赞云服务会在开发者填写的消息接收URL上会附带上参数nonce、timeStamp、signature、msgSignature(兼容模式和安全模式时返回) 可以使用以上genSignature方法,根据消息验证TOKEN、nonce、timeStamp、encrypt获取签名,和msgSignature比较,如果相同则证明来源于销赞云服务器。 假设用户的clientId、Token、EncodingAESKey分别是:
{
"clientId": "48ca17b00473d5e595ab",
"Token": "b303c15a3f6ff8c6d4cde9ba65ccff4d",
"EncodingAESKey": "EhhkrBZ7zX2rgwRcXIwWSN08ZCGMvwJYN0KzVFgUlUE"
}
销赞云推送消息到URL附带参数为:URL?nonce=57034211×tamp=1609430400&signature=a4a9fe2142277ef8c06269af6cb261e183a8a597&msgSignature=d04ca45202849b835a6d06ede5644977e022e448
1.明文模式返回消息体:
{
"clientId": "48ca17b00473d5e595ab",
"createTime": 1609430400,
"msgId": 100,
"msgType": 1,
"event": "ORDER_CREATE_SUCCESS",
"content": {
"id": 1000,
"orderNo": "1609430400",
"orderType": 1,
"orderStatus": 1,
"orderAmount": 100,
"closeTime": 1609431000,
"updateTime": 1609430400
}
}
2.兼容模式返回消息体:
{
"clientId": "48ca17b00473d5e595ab",
"encrypt": "Fy8jMNWDHPqf6Rpj7MQ0s2nXfaCIcnDYrQIx5QOCX0ETw9D5JZE4Q/VwBCsKOARTgXUeMTI0Da4YG2EaqB9m6/6YGEbcqQuECC1ccGmRlNAEDmPaLYoBZcpxvQu6ZJ8J7av7os8xNNGA/QpyOw9xqqOlYOkhZg48+kbCpLjbskw4skFdSIR2jbBUM+iY+lM7EN8SAIQN6Xl0+/m7k0wZgI6ksQdflMLrceAei/zUpK1tLh1wx0TybrNPFFu/9Unn3nXKfQkxfcIMWpgAhUPsGZrgdImEAuH3TCErsHXLOOs3VnGPUupwwUsyOcCP/GI/zROz3hQpPLpX3HlPi9KxLrTZ40znU0RSQ15pOwzHQQjjlVSIOf4DHU2w1gLXKfep",
"createTime": 1609430400,
"msgId": 100,
"msgType": 1,
"event": "ORDER_CREATE_SUCCESS",
"content": {
"id": 1000,
"orderNo": "1609430400",
"orderType": 1,
"orderStatus": 1,
"orderAmount": 100,
"closeTime": 1609431000,
"updateTime": 1609430400
}
}
3.安全模式返回消息体:
{
"clientId": "48ca17b00473d5e595ab",
"encrypt": "Fy8jMNWDHPqf6Rpj7MQ0s2nXfaCIcnDYrQIx5QOCX0ETw9D5JZE4Q/VwBCsKOARTgXUeMTI0Da4YG2EaqB9m6/6YGEbcqQuECC1ccGmRlNAEDmPaLYoBZcpxvQu6ZJ8J7av7os8xNNGA/QpyOw9xqqOlYOkhZg48+kbCpLjbskw4skFdSIR2jbBUM+iY+lM7EN8SAIQN6Xl0+/m7k0wZgI6ksQdflMLrceAei/zUpK1tLh1wx0TybrNPFFu/9Unn3nXKfQkxfcIMWpgAhUPsGZrgdImEAuH3TCErsHXLOOs3VnGPUupwwUsyOcCP/GI/zROz3hQpPLpX3HlPi9KxLrTZ40znU0RSQ15pOwzHQQjjlVSIOf4DHU2w1gLXKfep"
}
1.1.2. 解密消息
encrypt字段为加密消息体,可以使用decrypt方法进行解密,示例PHP代码:
//1.先验证msgSignature,如有
$sign = Encryptor::signature(Token, $timestamp, $nonce, $encrypt);
var_dump($sign == $_GET['msgSignature']);
//2.解密
$aesKey = 'EhhkrBZ7zX2rgwRcXIwWSN08ZCGMvwJYN0KzVFgUlUE';
$res = Encryptor::decrypt($decrypt, EncodingAESKey);
var_dump($res);
注意:
1、AESKey(消息加解密Key)长度固定为43个字符,从a-z,A-Z,0-9共62个字符中选取。 2、出于安全考虑,开放平台网站提供了修改AESKey(消息加解密Key)的功能。
签名和解密类Encryptor,PHP为例:
<?php
/**
* PHP AES 消息加解密
*/
class Encryptor
{
public static $blockSize = 32;
/**
* SHA1签名.
* @return string
*/
public static function signature(): string
{
$array = func_get_args();
sort($array, SORT_STRING);
return sha1(implode($array));
}
/**
* 对密文进行解密
* @param string $encrypt
* @param string $aesKey
* @return bool|string
*/
public static function decrypt($encrypt, $aesKey)
{
$aseKey = base64_decode($aesKey.'=', true);
//使用BASE64对需要解密的字符串进行解码
$cipherText = base64_decode($encrypt);
$iv = substr($aseKey, 0, 16);
//解密
$decrypted = openssl_decrypt($cipherText, 'aes-256-cbc', $aseKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
//去除补位字符
$result = self::pkcs7Unpad($decrypted);
//去除16位随机字符串,网络字节序和clientId
$content = substr($result, 16, strlen($result));
$contentLen = unpack('N', substr($content, 0, 4))[1];
return substr($content, 4, $contentLen);
}
/**
* PKCS#7 unpad.
* @param string $text
* @return string
*/
public static function pkcs7Unpad(string $text): string
{
$pad = ord(substr($text, -1));
if ($pad < 1 || $pad > self::$blockSize) {
$pad = 0;
}
return substr($text, 0, (strlen($text) - $pad));
}
}