电商收付通系列①,对请求进行签名,拼接Authorization
发布于 4 年前 作者 fshen 452 次浏览 来自 分享



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 cn.hutool.http.HttpResponse;
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](/user/description) 签名相关
 */
public class SignUtils {
private static final Logger logger = LoggerFactory.getLogger(SignUtils.class);

    /**
     * 签名生成
     * [@param](/user/param) method 请求方法 如POST
     * [@param](/user/param) urlSuffix 请求地址后缀 如/v3/certificates
     * [@param](/user/param) mchId 电商平台商户号
     * [@param](/user/param) serialNo 电商平台商户API证书序列号
     * [@param](/user/param) body 请求请求报文主体,如果没有,就传空字符串
     * [@param](/user/param) mchPrivateKeyPath 电商平台商户API私钥
     * [@return](/user/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](/user/param) method
* [@param](/user/param) urlSuffix
* [@param](/user/param) timestamp
* [@param](/user/param) nonceStr
* [@param](/user/param) body
* [@return](/user/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](/user/param) data 需要加密的数据
* [@param](/user/param) mchPriKey
* [@return](/user/return)
* [@throws](/user/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"


谢谢阅读🤝,未完待续

回到顶部