微信支付签名、统一下单流程与代码

注意事项:

如果所有参数都是按照微信要求,且可以通过微信提供的签名验证工具,但仍然报错:签名错误

可能原因:使用 restTemplate(springboot 封装的用于发送请求的对象)如果不设置请求头,编码格式默认为ISO8859-1,会导致签名算法验证通过,但是微信仍然会返回签名错误的提示

 

开发步骤

1、自己是先把必要的参数要的参数放到了Map中,下面是简化的代码,至于具体怎么封装返回值与参数的,大家根据自己需要

2、通过参数生成签名:将参数(key=val格式)按照字典循序排序--->在末尾加上keys键值对--->进行加密算法

3、将参数形成的xml,并调用微信统一下单接口,获得返回值

        Map<String,Object> data = new HashMap<>();
        data.put("body", "产品支付");
        data.put("attach", "产品支付");
        data.put("total_fee", 10);
        Date nowTime = new Date();
        SimpleDateFormat sdf= new SimpleDateFormat("yyyyMMddHHmmss");
        data.put("out_trade_no", sdf.format(nowTime));
        data.put("time_start", sdf.format(nowTime));
        data.put("time_expire", sdf.format(nowTime.getTime() + 600000));
        data.put("goods_tag", "产品支付");
        data.put("trade_type", "JSAPI");
        data.put("openid", jsApiPay.getOpenid());//
        data.put("notify_url", wxPayConfig.GetConfig().GetNotifyUrl());//异步通知url
        
        data.put("appid", wxPayConfig.GetConfig().GetAppID());//公众账号ID
        data.put("mch_id", wxPayConfig.GetConfig().GetMchID());//商户号
        data.put("spbill_create_ip", wxPayConfig.GetConfig().GetIp());//终端ip
        data.put("nonce_str", GenerateNonceStr(32));//随机字符串  方法见下文
        data.put("sign_type", SIGN_TYPE_HMAC_SHA256);//签名类型
        //SIGN_TYPE_HMAC_SHA256 全局变量 =  "HMAC-SHA256" 
        data.put("sign", makeSign(SIGN_TYPE_HMAC_SHA256,data));  //生成签名  方法见下文

        //将参数map转化成xml格式,并调用微信统一下单接口(要求参数为xml字符串)
        String xml = toXml(data); //方法见下文
        //进行请求
        URI uri = null;
        try {
             uri = new URI("https://api.mch.weixin.qq.com/pay/unifiedorder") ;
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        //使用  restTemplate(springboot 封装的用于发送请求的对象)如果不设置请求头,编码格式默认为ISO8859-1,会导致签名算法验证通过,但是微信仍然会返回签名错误的提示
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/xml; charset=UTF-8");
        headers.setContentType(type);
        HttpEntity<String> requestEntity = new HttpEntity<>(xml, headers);

        ResponseEntity<String> responseEntity =  restTemplate.postForEntity(uri,requestEntity,String.class);
        String s1 = "";
        try {
          s1 = new String(responseEntity.getBody().getBytes("ISO8859-1"), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //见返回结果的字符串封装成自定义对对象,
        Map<String, String> map = xmlToMap(s1);//将xml转化成map对象,方法见下文
        JSONObject jsonObject = null;
        String jsonStr ="";
        if(map !=null){
            jsonObject = JSONObject.fromObject(map);
            if(jsonObject!=null){
                jsonStr = jsonObject.toString();
            }
        }
        //将返回结果转成对象   objectMapper(springboot 带有的对象,可将json字符串反射到自定义对象中,) 
        try {
        //wxPayBaseResult 是根据微信接口返回的结果封装的自定义对象
            wxPayBaseResult = objectMapper.readValue(jsonStr, WxPayBaseResult.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

生成随机字符串nonce_str的方法:

    /**
     * 参数 len 需要生成字符串的长度
     * 生成随机串,随机串包含字母或数字
     * @return 随机串
     */
    public  String GenerateNonceStr(int len)
    {
        String val = "";
        Random random = new Random(); // 随机生成器
        for (int i = 0; i < len; i++) {
            // 在[0,2)值域随机生成一个数除2,得到以下要判断的格式
            String str = random.nextInt(2) % 2 == 0 ? "num" : "char";
            if ("char".equalsIgnoreCase(str)) {
                // 产生字母(大小写判断)
                int nextInt = random.nextInt(2) % 2 == 0 ? 65 : 97;
                // 字符串拼接
                val += (char) (nextInt + random.nextInt(26));
            } else if ("num".equalsIgnoreCase(str)) { // 产生随机数字并转成字符串
                val += String.valueOf(random.nextInt(10));
            }
        }
        return val;
    }

生成签名

    /**
     * signType 生成签名方式  inputObj:必须参数集合
     * @生成签名,详见签名生成算法
     * @return 签名, sign字段不参加签名 SHA256
     */
    public  String makeSign(String signType,Map<String,Object> inputObj)
    {
        StringBuffer sb = new StringBuffer();
        StringBuffer sbkey = new StringBuffer();
        //将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
        Map<String, Object> sortedMap = new TreeMap<String, Object>(parameters);
        Set es = sortedMap.entrySet();
        //转url格式
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            Object v = entry.getValue();
            //空值不传递,不参与签名组串
            if(null != v && !"".equals(v)) {
                sb.append(k + "=" + v + "&");
                sbkey.append(k + "=" + v + "&");
            }
        }
        //在string后加入API KEY,获取自己的支付key
        sbkey.append("key="+this.wxPayConfig.GetConfig().GetKey());
        System.out.println("#2.连接商户key:"+sbkey.toString());
        //str += "&key=" + WxPayConfig.GetConfig().GetKey();
        if (signType == SIGN_TYPE_MD5)
        {
            //MD5加密,结果转换为大写字符  MD5Util工具 在方法在下文 
            String sign = MD5Util.crypt(sbkey.toString()).toUpperCase();
            return sign;
        }
        else if(signType==SIGN_TYPE_HMAC_SHA256)
        {   //自己写的加密方法在下文
            return CalcHMACSHA256Hash(sbkey.toString(), this.wxPayConfig.GetConfig().GetKey());
        }else{
            return "";//sign_type 不合法

        }
    }

自己写的HMAC-SHA256加密方法

    //SIGN_TYPE_HMAC_SHA256  加密生成签名字符串
    //参数plaintext  将参数转化为url格式后的字符串
    //参数 salt  自己的商户key
    private   String CalcHMACSHA256Hash(String plaintext, String salt)
    {
        String result = "";
        byte[] baText2BeHashed =null;
        byte[] baSalt =null;
        try {
            baText2BeHashed = plaintext.getBytes("UTF-8");
            baSalt = salt.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        Mac sha256_HMAC = null;
        try {
            sha256_HMAC = Mac.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        SecretKeySpec secret_key = new SecretKeySpec(baSalt, "HmacSHA256");
        try {
            sha256_HMAC.init(secret_key);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        //对消息进行UTF-8转化  为了防止中文加密与微信的算法不匹配
        byte[] bytes = sha256_HMAC.doFinal(baText2BeHashed);
        result = Hex.encodeHexString(bytes, true).toUpperCase();
        System.out.println("3.生成sign并转成大写:"+result);

        return result;
    }

MD5加密工具类:

public class MD5Util {
    /**
     * Encodes a string 2 MD5
     *
     * @param str String to encode
     * @return Encoded String
     * @throws NoSuchAlgorithmException
     */
    public static String crypt(String str) {
        if (str == null || str.length() == 0) {
            throw new IllegalArgumentException("String to encript cannot be null or zero length");
        }
        StringBuffer hexString = new StringBuffer();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            try {
                md.update(str.getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            byte[] hash = md.digest();
            for (int i = 0; i < hash.length; i++) {
                if ((0xff & hash[i]) < 0x10) {
                    hexString.append("0" + Integer.toHexString((0xFF & hash[i])));
                } else {
                    hexString.append(Integer.toHexString(0xFF & hash[i]));
                }
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return hexString.toString();
    }
    /**
     * 对字符串md5加密
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String getMD5Str(String str){
        try {
            // 生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 计算md5函数
            md.update(str.getBytes());
            // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
            // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
            return new BigInteger(1, md.digest()).toString(16);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        getMD5Str("http://storyrobot.oss-cn-shanghai.aliyuncs.com/json/6c670076da1540d4943377127ea26a1d.json");
    }
}

2、将map集合转化成xml,并当成参数调用微信统一下单接口

    /**
     * @将Dictionary转成xml
     * @return 经转换得到的xml串
     **/
    public  String toXml(Map<String,Object> parm)
    {
        StringBuffer xml = new StringBuffer();

        xml.append("<xml>");
        if(parm != null){
            for (Map.Entry<String, Object> entry : parm.entrySet()) {
                if(entry != null  ){

                }
                xml.append("<").append(entry.getKey()).append(">");
                //isNotNullOrEmptyStr是判断不为空的方法
                if(entry.getValue() instanceof  String){
                    xml.append("<![CDATA[");

                        xml.append(entry.getValue());

                    xml.append("]]>");
                }else{
                        xml.append(entry.getValue());
                }
                xml.append("</").append(entry.getKey()).append(">");
            }

        }
        xml.append("</xml>");
        String  result ="";
       if(xml != null){
           try {
               result = xml.toString();
               //result = new String(xml.toString().getBytes("utf-8"),"ISO8859-1");
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
        return result;
    }

xml转化成map集合

    /**
     * xml转成map
     * @return Map<String, String>
     **/
    public  Map<String, String> xmlToMap(String xml) {
        try {
            Map<String, String> data = new HashMap<>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            stream.close();
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

自己的xml

<xml>
  <time_expire><![CDATA[20200318171406]]></time_expire>
  <nonce_str><![CDATA[r5392r99S0IEtZF91Arl5994068153Pn]]></nonce_str>
  <time_start><![CDATA[20200318170406]]></time_start>
  <openid><![CDATA[**********]]></openid>
  <sign><![CDATA[3B42CACF29F2F1F741A828F769D5F0580F9B46F4711EB964AB08BF50C8796947]]></sign>
  <body><![CDATA[产品支付]]></body>
  <notify_url><![CDATA[*****/resultNotify]]></notify_url>
  <mch_id><![CDATA[**********]]></mch_id>
  <spbill_create_ip><![CDATA[127.0.01]]></spbill_create_ip>
  <out_trade_no><![CDATA[**********]]></out_trade_no>
  <goods_tag><![CDATA[产品支付]]></goods_tag>
  <total_fee>10</total_fee>
  <appid><![CDATA[**********]]></appid>
  <trade_type><![CDATA[JSAPI]]></trade_type>
  <attach><![CDATA[产品支付]]></attach>
  <sign_type><![CDATA[HMAC-SHA256]]></sign_type>
</xml>

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值