JAVA WEB实现微信扫码支付(模式二)

一.准备微信扫码支付要用到的相关参数,这里将其全部写入一个配置类,代码如下:

public class ZbWxPayConfig {
    public static String APP_ID = "***********************";
    public static String APP_SECRET = "***********************";
    public static String MCH_ID = "***********************";    //商户号
    public static String MCH_KEY = "***********************";    //商户平台设置的密钥
    public static String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单接口
    public static String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";//统一查询订单接口 
    public static String NOTIFY_URL = "***********************";//支付成功微信回调地址
}

二.相关工具类

public class WxPayUtil {
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:20:37
     * @param      : @return
     * @Description: 生成随机字符串
     */
    public static String getNonceStr(){
        String allChar = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        int maxPos = allChar.length();
        String nonceStr = "";
        for ( int i = 0; i < 16; i++) {
            nonceStr += allChar.charAt((int) Math.floor(Math.random() * maxPos));
        }
        return nonceStr;    
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:26:04
     * @param      : @return
     * @Description: 获取当前时间字符串
     */
    public static String getCurrTime() {
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:30:48
     * @param      : @return
     * @Description: 获取10分钟后的时间字符串
     */
    public static String getAddCueeTime(){
        long currentTime = System.currentTimeMillis() ;
        currentTime +=10*60*1000;
        Date date=new Date(currentTime);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = dateFormat.format(date);
        return s;
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:26:58
     * @param      : @param length
     * @param      : @return
     * @Description: 生成指定长度随机数
     */
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:43:23
     * @param      : @param params
     * @param      : @param mchKey
     * @param      : @return
     * @Description: 生成微信支付签名
     */
    @SuppressWarnings("unchecked")
    public static String createSign(SortedMap<String, String> params, String mchKey) {
        StringBuffer sb = new StringBuffer();
        Set set = params.entrySet();
        Iterator it = set.iterator();
        while(it.hasNext()){
            Entry entry = (Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + mchKey);
        String sign = ZbMD5Util.EncryptionStr(sb.toString()).toUpperCase();
        return sign;
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-4-下午03:21:07
     * @param      : @param packageParams
     * @param      : @param mchKey
     * @param      : @return
     * @Description: 验证签名  是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     */
    @SuppressWarnings("unchecked")
    public static boolean isTenpaySign(SortedMap<String, String> packageParams,String mchKey) {
        if(packageParams != null){
            StringBuffer sb = new StringBuffer();
            Set es = packageParams.entrySet();
            Iterator it = es.iterator();
            while (it.hasNext()) {
                Entry entry = (Map.Entry) it.next();
                String k = (String) entry.getKey();
                String v = (String) entry.getValue();
                if (!"sign".equals(k) && null != v && !"".equals(v)) {
                    sb.append(k + "=" + v + "&");
                }
            }
            sb.append("key=" + mchKey);
            // 算出摘要
            String mysign = MD5Util.EncryptionStr(sb.toString()).toLowerCase();
            String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
            return tenpaySign.equals(mysign);
        }else{
            return false;
        }
    }
    
    
    @SuppressWarnings("unchecked")
    public static SortedMap<String, String> xmlConvertToMap(String rxml) {
        
        // 解析xml成map
        Map<String, String> resultMap = new HashMap<String, String>();
        try {
            Document dom = DocumentHelper.parseText(rxml);
            Element root = dom.getRootElement();
            List<Element> elementList = root.elements();
            for (Element e : elementList) {
                resultMap.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 过滤空 设置 SortedMap
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        Iterator it = resultMap.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = resultMap.get(parameter);

            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        return packageParams;
    }
    
    
    /**
     * @author     : YY
     * @date       : 2018-7-3-下午04:48:23
     * @param      : @param arr
     * @param      : @return
     * @Description: 将map数据转化为xml格式字符串
     */
    public static String MapToXml(Map<String, String> arr) {
        String xml = "<xml>";  
        Iterator<Entry<String, String>> iter = arr.entrySet().iterator();  
        while (iter.hasNext()) {  
            Entry<String, String> entry = iter.next();  
            String key = entry.getKey();  
            String val = entry.getValue();  
            if (IsNumeric(val)) {  
                xml += "<" + key + ">" + val + "</" + key + ">";  
            } else  
                xml += "<" + key + "><![CDATA[" + val + "]]></" + key + ">";  
        }  
        xml += "</xml>";  
        return xml;  
    }
    
    private static boolean IsNumeric(String str) {  
        if (str.matches("\\d *")) {  
            return true;  
        } else {  
            return false;  
        }  
    }

}




public class MD5Util {
    /**
     * @author      : YY
     * @time        : 2018-7-3-下午04:44:24
     * @package     : package com.tcmce.zibo.wxpay.utils;
     * @description : MD5加密字符串
     */
    public static String EncryptionStr(String str){

        StringBuffer sb = new StringBuffer();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[] bytes = md.digest();
            for (byte aByte : bytes) {
                String s=Integer.toHexString(0xff & aByte);
                if(s.length()==1){
                    sb.append("0"+s);
                }else{
                    sb.append(s);
                }
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

}





public class HttpUtil {
    
    /**
     * @author     : YY
     * @date       : 2018-7-6-上午11:25:01
     * @param      : @param urlStr
     * @param      : @param data
     * @param      : @return
     * @param      : @throws DocumentException
     * @Description: HTTP请求微信接口,返回map格式数据
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> postWx(String requestUrl, String outputStr) throws DocumentException{
        try {
            URL url = new URL(requestUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
            conn.setDoOutput(true);  
            conn.setDoInput(true);  
            conn.setUseCaches(false);  
            // 设置请求方式(GET/POST)  
            //conn.setRequestMethod(requestMethod);  
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");  
            // 当outputStr不为null时向输出流写数据  
            if (null != outputStr) {  
                OutputStream outputStream = conn.getOutputStream();  
                // 注意编码格式  
                outputStream.write(outputStr.getBytes("UTF-8"));  
                outputStream.close();  
            }  
            // 从输入流读取返回内容  
            InputStream inputStream = conn.getInputStream();  
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
            String str = null;
            StringBuffer buffer = new StringBuffer();  
            while ((str = bufferedReader.readLine()) != null) {  
                buffer.append(str);  
            }  
            // 释放资源  
            bufferedReader.close();  
            inputStreamReader.close();  
            inputStream.close();  
            inputStream = null;  
            conn.disconnect();  
            if(buffer != null && buffer.toString().length() != 0){
                Map<String, String> preMap = new HashMap<String, String>();
                Document dom = DocumentHelper.parseText(buffer.toString());
                Element root = dom.getRootElement();
                List<Element> elementList = root.elements();
                for (Element e : elementList) {
                    preMap.put(e.getName(), e.getText());
                }
                return preMap;
            }
        } catch (Exception e) {
            logger.error("HTTP请求:["+requestUrl+"]异常: " + e.getMessage());
        }
        return null;
    }
    
}

三.编写调用微信下单接口(使用AJAX请求该地址,返回JSON格式数据,返回支付二维码地址,用于下一步生成二维码图片)

public class UnifiedOrderServlet extends HttpServlet{
    
    private static Logger logger = Logger.getLogger(UnifiedOrderServlet.class);
    
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        Map<String, String> msgMap = new HashMap<String, String>();
        try {
            String prodcutId = request.getParameter("prodcutId").trim();//商品ID
            String orderType = request.getParameter("orderType").trim();//订单类型
                //生成商户订单号
                String outTradeNo = WxPayUtil.getCurrTime() + WxPayUtil.buildRandom(4);
                //获取商品价格
                String totalFee = "999";
                //回调参数     长度有限制
                Map<String, String> attachMap = new HashMap<String, String>();
                attachMap.put("memberId", decryMemberId);
                attachMap.put("prodcutId", prodcutId);
                JSONObject attachJson = JSONObject.fromObject(attachMap);
                //完善微信下单所需参数
                SortedMap<String, String> orderParams =  new TreeMap<String, String>();
                orderParams.put("appid", WxPayConfig.APP_ID);
                orderParams.put("mch_id", WxPayConfig.MCH_ID);
                orderParams.put("nonce_str", WxPayUtil.getNonceStr());
                orderParams.put("body", "商品描述");//商品描述
                orderParams.put("attach", attachJson.toString());
                orderParams.put("out_trade_no", outTradeNo);
                orderParams.put("total_fee", new BigDecimal(totalFee).multiply(new BigDecimal(100)).intValue() + "");//订单总金额,单位为分
                orderParams.put("spbill_create_ip", request.getRemoteAddr());
                orderParams.put("time_start", WxPayUtil.getCurrTime());交易起始时间
                orderParams.put("time_expire", WxPayUtil.getAddCueeTime());//交易结束时间
                orderParams.put("notify_url", WxPayConfig.NOTIFY_URL);//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
                orderParams.put("trade_type", "NATIVE");//交易类型 JSAPI 公众号支付    NATIVE 扫码支付    APP APP支付
                orderParams.put("device_info", "WEB");//终端设备号(门店号或收银设备ID),默认请传"WEB"
                orderParams.put("product_id", prodcutId);//商品ID
                
                String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);//将所有参数进行签名
                orderParams.put("sign", sign);//签名
                String xmlContent = WxPayUtil.MapToXml(orderParams);
                //调用微信下单接口
                Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.UFDODER_URL, xmlContent);
                logger.info("下单结果:" + resultMap.toString());
                //下单成功
                if("SUCCESS".equals(resultMap.get("result_code"))){
                    //二维码链接
                    orderParams.put("code_url", resultMap.get("code_url"));
                    //预支付ID
                    orderParams.put("prepay_id", resultMap.get("prepay_id"));
                    orderParams.put("total_fee", totalFee.toString());
                    orderParams.put("payType", payType);
                    orderParams.put("memberId", decryMemberId);
                    orderParams.put("trade_state", "NOTPAY");
                    orderParams.put("trade_state_desc", "未支付");
                    orderParams.put("wx_pc", "PC");
                    orderParams.put("pay_source", "ZB");
                    //商户系统记录订单信息
                    此处代码省略
                    //以下参数为了页面显示所用,
                    msgMap.put("body", body);
                    msgMap.put("outTradeNo", outTradeNo);
                    msgMap.put("totalFee", totalFee.toString());
                    msgMap.put("codeUrl", resultMap.get("code_url"));//二维码支付地址
                    msgMap.put("success", "true");
                }else{
                    msgMap.put("success", "false");
                    msgMap.put("message", "下单失败,您可以尝试稍后再进行下单!");
                } 
        } catch (Exception e) {
            msgMap.put("success", "false");
            msgMap.put("message", "系统异常,您可以稍后再进行尝试下单!");
            logger.error("调用微信下单接口异常:" + e.getMessage());
        }
        Gson gson = new Gson();
        String json = gson.toJson(msgMap);
        PrintWriter out = response.getWriter();
        out.write(json);
        out.close();
    }

}

三.利用上一步获得的二维码路径生成二维码,用于页面展示:

public class CreateCodeServlet extends HttpServlet{
    
    //二维码默认尺寸
    private static int defaultWidthAndHeight = 250;

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        
        try {
            String codeUrl = request.getParameter("code_url").trim();
            //以下开始生成二维码
            Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
            //指定纠错等级
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
            //指定编码格式
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            //hints.put(EncodeHintType.MARGIN, 1);
            try {
                BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl, BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight,hints);
                OutputStream outputStream = response.getOutputStream();
                MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);//输出二维码
                outputStream.flush();
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }    

}

四.实现支付成功后微信后台回调的函数,第一个配置文件里最后一个参数就是指向这里,必须可以外网访问的地址:

public class WxPayNotifyServlet extends HttpServlet{
    
    private static Logger logger = Logger.getLogger(WxPayNotifyServlet.class);
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        String resXml = "";
        try {
            InputStream inputStream = request.getInputStream();
            StringBuffer sb = new StringBuffer();
            String s;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            while((s = in.readLine()) != null){
                sb.append(s);
            }
            in.close();
            inputStream.close();
            logger.error("支付结果:" + sb.toString());
            if(sb != null && sb.toString().length() != 0){


                SortedMap<String, String> resultMap = ZbWxPayUtil.xmlConvertToMap(sb.toString());
                //验证签名
                if(ZbWxPayUtil.isTenpaySign(resultMap, ZbWxPayConfig.MCH_KEY)){
                    if("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))){
                        //调用支付成功后执行业务逻辑代码
                        Integer rows = this.executePayNotify(resultMap);
                        if(rows != 0){
                            resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                        }else{
                            logger.error("----业务结果执行失败  返回FALL 通知微信继续重试----");
                            resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商户系统出错]]></return_msg></xml> ";
                        }
                    }else{
                        logger.error("----支付失败----" + resultMap.get("return_msg"));
                        resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失败]]></return_msg></xml>";
                    }
                }else{
                    logger.error("----签名验证失败----");
                    resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml> ";
                }
            }else{
                logger.error("----未接收到微信通知信息----");
                resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[未接收到回调信息]]></return_msg></xml> ";
            }
        } catch (Exception e) {
            logger.error("----商户系统异常----");
            resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商户系统异常]]></return_msg></xml> ";
            e.printStackTrace();
        }
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());  
        out.write(resXml.getBytes());  
        out.flush();  
        out.close(); 
    }
    
    /**
     * @author     : YY
     * @date       : 2018-7-6-下午12:59:58
     * @param      : @param resultMap
     * @param      : @return
     * @param      : @throws Exception
     * @Description: 微信扫码支付成功后执行业务代码
     */
    private Integer executePayNotify(Map<String, String> resultMap) throws Exception{
        
        Double cashFee = Double.parseDouble(resultMap.get("cash_fee")) / 100,//现金支付金额
        totalFee = Double.parseDouble(resultMap.get("total_fee")) / 100;//总金额
        String bankType = resultMap.get("bank_type"),//付款银行
        feeType = resultMap.get("fee_type"),//货币种类
        openid = resultMap.get("openid"),//用户标识
        timeEnd = resultMap.get("time_end"),//支付完成时间
        transactionId = resultMap.get("transaction_id"),//微信支付订单号
        out_trade_no = resultMap.get("out_trade_no"),//商户订单号
        attach = resultMap.get("attach");//商家数据包  传入的是JSON格式字符串
        //解析订单携带参数
        JSONObject attachJsonObj = JSONObject.fromObject(attach);
        Map<String, String> argsMap = new HashMap<String, String>();
        argsMap.put("bankType", bankType);
        argsMap.put("cashFee", cashFee.toString());
        argsMap.put("feeType", feeType);
        argsMap.put("openid", openid);
        argsMap.put("timeEnd", timeEnd);
        argsMap.put("memberId", memberId);
        argsMap.put("transactionId", transactionId);
        argsMap.put("out_trade_no", out_trade_no);
        argsMap.put("trade_state", "SUCCESS");
        argsMap.put("trade_state_desc", "支付成功");

        此处开始执行商户系统逻辑业务代码,自主实现


        return 0;
    }
}

五.支付功能到此结束,此处添加一个支付状态查询功能,支付成功后微信后台不会主动通知支付页面,需要自主添加查询按钮或定时查询功能:

public class QueryOrderStatusServlet extends HttpServlet{

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        Map<String, String> result = new HashMap<String, String>();
        
        try {
            String outTradeNo = request.getParameter("outTradeNo");
            if(StringUtils.isNotBlank(outTradeNo)){
                String nonceStr = WxPayUtil.getNonceStr();
                SortedMap<String, String> orderParams =  new TreeMap<String, String>();
                orderParams.put("appid", WxPayConfig.APP_ID);
                orderParams.put("mch_id", WxPayConfig.MCH_ID);
                orderParams.put("out_trade_no", outTradeNo);
                orderParams.put("nonce_str", nonceStr);
                //参数签名
                String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);
                orderParams.put("sign", sign);
                String xmlContent = WxPayUtil.MapToXml(orderParams);
                Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.ORDER_QUERY_URL, xmlContent);
                
                if("SUCCESS".equals(resultMap.get("return_code"))){
                    if("SUCCESS".equals(resultMap.get("result_code"))){
                        String tradeState = resultMap.get("trade_state");
                        if("SUCCESS".equals(tradeState)){
                            result.put("code", "success");
                            result.put("msg", "支付成功 [ 即将跳转至学习页面......请稍后!]。");
                        }else{
                            result.put("code", "fail");
                            result.put("msg", resultMap.get("trade_state_desc") + "  [ 请您在微信上确认支付成功后,再执行该操作。]");
                        }
                    }else{
                        result.put("code", "fail");
                        result.put("msg", resultMap.get("err_code_des"));
                    }
                }else{
                    result.put("code", "fail");
                    result.put("msg", "网络异常,您可以稍后再试!");
                }
            }else{
                result.put("code", "fail");
                result.put("msg", "订单号有误!");
            }
            
        } catch (Exception e) {
            result.put("code", "fail");
            result.put("msg", "系统异常,您可以稍后再试!");
            e.printStackTrace();
        }
        
        Gson gson = new Gson();
        String json = gson.toJson(result);
        PrintWriter out = response.getWriter();
        out.write(json);
        out.close();
    }
    

}

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值