1、开头语
最近对接了下微信刚出的电商收付通接口,过程也是不容易,踩了几个小坑,所以在此记录下来,希望能帮到有需要的人,限于本人经验,如有错误,欢迎指正。
2、介绍
关于电商收付通,官方的介绍是微信支付专为电商行业场景打造的支付、结算解决方案。电商平台的平台商户入驻微信支付成为二级商户。电商收付通支持将多个二级商户的订单进行合单支付(如电商购物车中的多笔订单合并支付),合单支付款项分别进入到二级商户各自的账户(资金为冻结状态,可用于实现二级商户账期);电商平台在满足业务流程条件下(如确认收货等),可将二级商户的冻结状态的资金解冻,并收取平台佣金。
3、业务场景
社会化分销、网红带货如火如荼,加入这个大浪潮的你还在汇总记账然后转账给达人吗?分账功能,拯救濒临崩溃的财务小姐姐,拿去不谢。开通「电商收付通」后,电商平台直接拥有分账功能,可根据与卖家的协议,实现卖家交易款在线抽成。此外,还可将分销方等角色添加为分账接收方,满足多渠道分销、网红达人带货场景下的多方灵活分账。关于如何开通电商收付通、产品介绍、接入流程、接口规则、开发指引 请前往官网 https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/guide.shtml 地址查看,这里就不再赘述。
4、签名生成
文档地址:
https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-sheng-cheng
微信支付API v3要求商户对请求进行签名。微信支付会在收到请求后进行签名的验证。如果签名验证不通过,微信支付API v3将会拒绝处理请求,并返回401 Unauthorized。也就是说请求电商收付通的每个接口都需要在请求头传入Authorization,否则请求不会成功。下面看签名生成的代码
import com.smartMap.media.common.utils.UuidUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Map;
/**
* @description 签名相关
*/
public class SignUtils {
private static final Logger logger = LoggerFactory.getLogger(SignUtils.class);
/**
* 签名生成
* @param method 请求方法 如POST
* @param urlSuffix 请求地址后缀 如/v3/certificates
* @param mchId 电商平台商户号
* @param serialNo 电商平台商户API证书序列号
* @param body 请求请求报文主体,如果没有,就传空字符串
* @param mchPrivateKeyPath 电商平台商户API私钥
* @return
*/
public static String authorization(String method,String urlSuffix,String mchId,String serialNo,String body,String mchPrivateKeyPath) {
try {
//商户私钥
String mchPrivateKey = CertificateUtils.getPrivateKey(mchPrivateKeyPath);
//时间戳
String timestamp = Long.toString(System.currentTimeMillis()/1000);
//随机数
String nonceStr = UuidUtils.randomUUID();
//拼签名串
StringBuilder sb = signMessage(method,urlSuffix,timestamp,nonceStr,body);
logger.info("sign original string:{}",sb.toString());
//计算签名
String sign = new String(Base64.encodeBase64(v3signRSA(sb.toString(),mchPrivateKey)));
logger.info("sign result:{}",sign);
//拼装http头的Authorization内容
String authorization ="WECHATPAY2-SHA256-RSA2048 mchid=\""+mchId+"\",nonce_str=\""+nonceStr+"\",signature=\""+sign+"\",timestamp=\""+timestamp+"\",serial_no=\""+serialNo+"\"";
logger.info("authorization result:{}",authorization);
return authorization;
} catch (Exception e) {
logger.error("authorization Exception result:{}",e);
e.printStackTrace();
return null;
}
}
/**
* Authorization 签名串
* @param method
* @param urlSuffix
* @param timestamp
* @param nonceStr
* @param body
* @return
*/
private static StringBuilder signMessage(String method,String urlSuffix,String timestamp,String nonceStr,String body) {
return new StringBuilder()
.append(method)
.append("\n")
.append(urlSuffix)
.append("\n")
.append(timestamp)
.append("\n")
.append(nonceStr)
.append("\n")
.append(body)
.append("\n");
}
/**
* 私钥签名
* @param data 需要加密的数据
* @param mchPriKey
* @return
* @throws Exception
*/
public static byte[] v3signRSA(String data, String mchPriKey) throws Exception {
//签名的类型
Signature sign = Signature.getInstance("SHA256withRSA");
//读取商户私钥,该方法传入商户私钥证书的内容即可
byte[] keyBytes = Base64.decodeBase64(mchPriKey);
PKCS8EncodedKeySpec keySpec =new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(keySpec);
sign.initSign(priKey);
sign.update(data.getBytes(StandardCharsets.UTF_8));
return sign.sign();
}
}
针对这个CertificateUtils.getPrivateKey方法说明一下,这个方式是用代码获取商户API证书私钥。也可以下载好商户API证书,然后打开apiclient_key.pem文件,复制出来,代码如下。
5、结果
GET
/v3/certificates
1554208460
593BEC0C930BF1AFEB40B4A08C8FB242
签名sign值:Aabu2HuhQh8+4f8lyBIlg7HcXRTweVqy86RS34jVLnKiekDeWDy1YFwmqnLShvFwRidw9F/cgKCU4zpkXV/NilZSrHlFtHhDw4vInIj8GJmg/USCa6b+rqBe2rnLrF9iORyU04dbt3MGyybRahkr0LqFuS+bqn9IgNjFPRgaqS68krlwTuuay2LJtTyfTrqpfbqmFCDw1Ge1wfGor81H6nCIAaoHzFHC/N3EOqgYypARu5nZVOAxcMkP1jSScLvZQaCdv4cgJ+0xcmE9SHgsGGQHjVbWl81LY4QlbxloLw5RoHFTdaPOYtINttReBY4bgY00NSJJBbHQmd2hLHm8iQ==
authorization值:WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海