微信小程序支付+java后台实现(完整版)

前言:本人是从android 转java的,第一次加入项目就遇到了微信支付对接,微信支付的对接文档让我感觉很差,也遇到了很多的坑,所以在此记录一下。

 

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一:先了解微信小程序支付的流程,官方给的流程图还是可以看的

å°ç¨åºæ¯ä»æ¶åºå¾

我们后台要做的就只有三点:

1.通过小程序的appid 秘钥  code 三个参数拿到小程序的openid(每个登录小程序的用户openid是唯一的)

2.然后通过openid,还有订单参数,小程序appid,商户mch_id等(开发文档有明确必传参数,下面会给代码)拿到支付id prepay_id返回给前端

(此处需要注意的就是两次签名的生成)

3.对微信后台返回的支付结果进行数据处理

二:主要代码部分

1.如何拿到小程序的openid

puclic class WeiXinUtil{

    public static String getOpenid(String code){  
        Map params = new HashMap<>();
        params.put("appid", "微信小程序开发平台注册信息");
        params.put("secret", "微信小程序开发平台的秘钥");
        params.put("grant_type", "authorization_code");
        params.put("js_code", "微信小程序登录后传来的code参数");
        Map map =  JSON.parseObject(HttpClientUtil.postMap("https://api.weixin.qq.com/sns/jscode2session", params),Map.class);
        System.out.println(map);
        assert map != null;
        if (map.get("openid") == null) {
            throw new BusinessException(map.get("errmsg").toString());
        }
        return map.get("openid").toString();
    }
}

代码段解释:params中的参数都是小程序那边给出的,grant_type:参数固定为autorization_code,然后将参数使用post请求发到微信的接口中https://api.weixin.qq.com/sns/jscode2session,从返回值中拿到openid

2.如何将商品信息签名,去微信后台验证拿到支付id prepay_id(这一步的操作就叫统一下单)

微信小程序支付+java后台实现(完整版)_第1张图片

统一下单的参数给了很多,我们只传他必填参数

接口代码

 @ApiOperation("微信支付,预下单")
    @PostMapping("/xxxxx")
    public RestResult wxpay(@RequestBody PayRequestBodyVo vo) {
        try {
            String unifiedorder = WeiXinUtil.getUnifiedorder(openid, outTradeNo, Double.valueOf(vo.getSumValue()));
            Map map = PayUtil.doXMLParse(unifiedorder);
            SortedMap param = WeiXinUtil.prepayId(map);
            return new RestResult("微信支付", param);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new RestResult("微信支付");
    }
public class WeiXinUtil{
    public static String getUnifiedorder(String openid, String outTradeNo, double money) throws Exception {
        SortedMap params = new TreeMap<>();
        params.put("appid", "小程序appid");
        params.put("body", "商品名称");
        params.put("mch_id", "商户mch_id");
        params.put("nonce_str", PayUtil.makeUUID(32));
        params.put("notify_url", "支付结果返回接口地址,需要自己写个接口,然后做外网映射(外网映射建议做http的,https有的人因为缺少ssl证书会导致接受不到返回值)");
        params.put("openid", "上一步获取到的openid");
        params.put("out_trade_no", PayUtil.generateOrderNo());//商品订单号
        params.put("spbill_create_ip", PayUtil.getLocalIp());//服务部署的ip
        params.put("total_fee", PayUtil.moneyToIntegerStr(money));//费用的参数转型
        params.put("trade_type", "JSAPI");//对接类型
        params.put("sign", PayUtil.createSign("UTF-8", params, "商户秘钥"));//MD5签名
        //转换成xml
        String xmlData = PayUtil.getRequestXml(params);
        //请求微信后台,获取支付id
        String resXml = HttpClientUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xmlData);

        return resXml;
    }
    public static SortedMap prepayId(Map map) {
        SortedMap parameters = new TreeMap<>();
        parameters.put("appId", "");
        parameters.put("timeStamp", PayUtil.create_timestamp());
        parameters.put("nonceStr", map.get("nonce_str"));
        parameters.put("package", "prepay_id=" + map.get("prepay_id"));
        parameters.put("signType", "MD5");
        String sign = PayUtil.createSign("UTF-8", parameters, "商户秘钥");
        parameters.put("prepay_id", "prepay_id=" + map.get("prepay_id"));
        parameters.put("paySign", sign);
        return parameters;
    }
}

然后给出这里使用到的工具方法 

public class PayUtil{
    /**
     * 获取当前机器的ip
     */
    public static String getLocalIp() {
        InetAddress ia = null;
        String localip = null;
        try {
            ia = ia.getLocalHost();
            localip = ia.getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return localip;
    }
@SuppressWarnings("rawtypes")
    public static String getRequestXml(SortedMap parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "");
            } else {
                sb.append("<" + k + ">" + v + "");
            }
        }
        sb.append("");
        return sb.toString();
    }
    /**
     * 创建签名Sign
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(String characterEncoding,SortedMap parameters, String key) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            if (entry.getValue() != null || !"".equals(entry.getValue())) {
                String v = String.valueOf(entry.getValue());
                if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                    sb.append(k + "=" + v + "&");
                }
            }
        }
        sb.append("key=" + key);
        String sign = MD5Util.MD5Encode(sb.toString()).toUpperCase();
        return sign;
    }
     /**
     * 生成随机数
     */
    public static String makeUUID(int len) {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
    }
    /**
     * 生成订单号
     */
    public static String generateOrderNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
        return sdf.format(new Date()) + makeUUID(16);
    }
    /**
     * 解析xml
     */
    public static Map doXMLParse(String strxml) throws Exception {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        org.jdom2.Document doc = builder.build(in);
        org.jdom2.Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            org.jdom2.Element e = (org.jdom2.Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }
    /**
     * 获取子节点的xml
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                org.jdom2.Element e = (org.jdom2.Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("");
            }
        }
        return sb.toString();
    }
    /**
     * 转换金额到整型
     */
    public static String moneyToIntegerStr(Double money) {
        BigDecimal decimal = new BigDecimal(money);
        int amount = decimal.multiply(new BigDecimal((100)))
                .setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
        return String.valueOf(amount);
    }
   /**
     * 微信下单,map to xml
     * @param params 参数
     * @return String
     */
    public static String mapToXml(Map params) {
        StringBuilder xml = new StringBuilder();
        xml.append("");
        for (Map.Entry entry : params.entrySet()) {
            String key   = entry.getKey();
            String value = entry.getValue();
            // 略过空值
            if (StringUtils.isEmpty(value)) continue;
            xml.append("<").append(key).append(">");
        }
        xml.append("");
        return xml.toString();
    }

    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

}

本人在此踩过的坑,1.由于是两个人合作,商户mch_id写错了,请求下单返回结果 商户有违规,限制支付功能,改正商户id即解决

2.签名错误:此处代码已经尝试过了 签名没有错误,但是本人在做这个地方的时候也遇到了些坑,请求结果为签名错误,然后去微信签名验证工具中验证:

微信小程序支付+java后台实现(完整版)_第2张图片

将转化后的xml签名信息放入,然后将商户平台设置的秘钥放入商户key中,若验证通过,两个key相同 即你的签名没有问题,

若此处代码中还返回签名错误,请重新到商户账号中,重新设置秘钥,(这个时候一定是你设置的秘钥,和你代码总验证的秘钥不一致,这里的错误就多种原因了:可能设置后复制粘贴的错误,可能是商户那边操作人员给你的时候就是错的,所以在这里请重新设置一次)

三:微信返回结果接口

返回结果的接口就不上代码了,这里都是项目的个人操作,唯一需要注意的就是能否拿到微信后台返回的数据

1.返回数据是null的:本人遇到的原因是(使用花生壳做了这个接口的外网映射,由于微信开发平台要求说要https的,但测试的时候就拿不到返回值),解决方式:将映射类型变成http就可以拿到返回值了

-------------------------------------------------------------------------

---------------若有地方表达不明白,欢迎加QQ:574774295 询问,请备注验证消息

 

你可能感兴趣的:(java后台)