0
点赞
收藏
分享

微信扫一扫

【微信】Java 微信扫码支付(模式二)


一、前言

      今天是一个有意义的日子,八月十五中秋节。

      在这个这么有意义的日子里,小编写下这篇博客,自己的记忆肯定非常深,哈哈哈,先自嘲一下。

      记得小编的前一篇关于微信的博客中提到了,微信是一种生活方式,有九亿人在使用。中国各个地区都已经使用上了微信支付。工行和建行斗了那么多年,最后败给了微信和支付宝。好了,在这篇博客中,小编分享一下,微信支付。

二、环境需求

      开通微信服务号需要下面的信息:

  • appid
  • appsecret
  • 商业号
  • 商户平台配置API

      需要相关的jar包:

  • jdom-1.1.3.jar (xml文件生成和解析相关jar包)
  • core-3.3.0.jar (Google 二维码生成相关jar包)
  • zxing-javase.jar (Google 二维码生成相关jar包)

三、实现

3.0 流程介绍

      商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。【引用自微信开发文档】




【微信】Java 微信扫码支付(模式二)_支付


      业务流程说明:

(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。

3.1 微信方面

      欢迎大家关注“大米时代官微”,会推荐很多很火的技术文章



【微信】Java 微信扫码支付(模式二)_支付宝_02




【微信】Java 微信扫码支付(模式二)_java_03




【微信】Java 微信扫码支付(模式二)_博客_04


3.1 建立一个web项目

      小编使用的是Eclipse,打开Eclipse –> File –> new –> Dynamic Web Project,建立一个名字为WCPay的web项目。然后在WebRoot –> WEB-INF –> lib下添加小编上面的jar包。



【微信】Java 微信扫码支付(模式二)_支付_05


3.2 扫码页面建立index.jsp

      建立一下jsp页面,用于生成订单费用的二维码,供用户扫描:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">

    <title>My JSP 'index.jsp' starting page</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
  </head>

  <body>
    <h1>扫码支付:</h1>
    <img  src="${pageContext.request.contextPath}/PayServelt" />
  </body>
</html>

3.3 建立PayServlet

      在PayServlet.java中,doPost方法调用doGet方法;doGet方法用于调用weixinPay方法,生成二维码。用户扫描后,支付后,自动调用weixinPay方法配置的回调方法。



【微信】Java 微信扫码支付(模式二)_支付_06


      建立一个PayServelt,用于向微信的“统一下单”接口发送信息。然后收到返回的信息。

      PayServelt:

package com.dmsd.wechat.pay;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.dmsd.wechat.util.HttpUtil;
import com.dmsd.wechat.util.PayCommonUtil;
import com.dmsd.wechat.util.PayConfigUtil;
import com.dmsd.wechat.util.XMLUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;

/**
 * 支付的servlet-2017年10月4日
 * @author Ares
 */
public class PayServelt extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * 调用微信支付的接口
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          try {
               //String productId = request.getParameter("productId");
               String productId = "5533675";
               String userId = "user01";
               String text = weixinPay(userId, productId); 
               System.out.println("********************成功获取二维码url****************************************");
               //根据url来生成生成二维码
               int width = 300; 
               int height = 300; 
               //二维码的图片格式 
               String format = "gif"; 
               Hashtable hints = new Hashtable(); 
               //内容所使用编码 
               hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
               BitMatrix bitMatrix;
               try {
                    bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
                    MatrixToImageWriter.writeToStream(bitMatrix, format, response.getOutputStream());
               } catch (WriterException e) {
                   e.printStackTrace();
               }

            } catch (Exception e) {
            }
    }

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

    /**
     * 调用微信统一支付接口-王雷-2017年10月4日23:18:04 
     * @param userId 用户id
     * @param productId 商品id
     * @return
     * @throws Exception
     */
    public static String weixinPay(String userId, String productId) throws Exception {
            // 账号信息  
            String appid = PayConfigUtil.appid;  // appid  
            String appsecret = PayConfigUtil.APP_SECRET; // appsecret  
            String mch_id = PayConfigUtil.MCH_ID; // 商业号  
            String key = PayConfigUtil.API_KEY; // key  

            String currTime = PayCommonUtil.getCurrTime();  //获取当前时间
            String strTime = currTime.substring(8, currTime.length());  
            String strRandom = PayCommonUtil.buildRandom(4) + "";    //取出一个指定长度大小的随机正整数
            String nonce_str = strTime + strRandom;  

            String order_price = "1"; // 价格   注意:价格的单位是分  
            String body = "可乐";   // 商品名称  
            String out_trade_no = "789456"; // 订单号  

            //获取发起电脑 ip  
            String spbill_create_ip = PayConfigUtil.CREATE_IP;  
            //回调接口   
            String notify_url = PayConfigUtil.NOTIFY_URL;  
            String trade_type = "NATIVE";  

            SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();  
            packageParams.put("appid", appid);  
            packageParams.put("mch_id", mch_id);  
            packageParams.put("nonce_str", nonce_str);  
            packageParams.put("body", body);  
            packageParams.put("out_trade_no", out_trade_no);  
            packageParams.put("total_fee", order_price);  
            packageParams.put("spbill_create_ip", spbill_create_ip);  
            packageParams.put("notify_url", notify_url);  
            packageParams.put("trade_type", trade_type);  

            String sign = PayCommonUtil.createSign("UTF-8", packageParams,key);  
            packageParams.put("sign", sign);  

            String requestXML = PayCommonUtil.getRequestXml(packageParams);  
            System.out.println(requestXML);  

            String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);  
            System.out.println(resXml);

            Map map = XMLUtil.doXMLParse(resXml);  
            //String return_code = (String) map.get("return_code");  
            //String prepay_id = (String) map.get("prepay_id");  
            String urlCode = (String) map.get("code_url");  

            return urlCode;  
      }

}

3.4 工具类

      在PayServelt的weixinPay方法中,用到了HttpUtil、MD5Util、PayCommonUtil、PayConfigUtil、XMLUtil 五个相关的方法类。

      PayCommonUtil:

package com.dmsd.wechat.util;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

public class  PayCommonUtil {
     /** 
     * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 
     * @return boolean 
     */  
    public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
        StringBuffer sb = new StringBuffer();  
        Set es = packageParams.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(!"sign".equals(k) && null != v && !"".equals(v)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  

        sb.append("key=" + API_KEY);  

        //算出摘要  
        String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();  
        String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();  

        //System.out.println(tenpaySign + "    " + mysign);  
        return tenpaySign.equals(mysign);  
    }  

    /** 
     * @author 
     * @date 2016-4-22 
     * @Description:sign签名 
     * @param characterEncoding 
     *            编码格式 
     * @param parameters 
     *            请求参数 
     * @return 
     */  
    public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
        StringBuffer sb = new StringBuffer();  
        Set es = packageParams.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 (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  
        sb.append("key=" + API_KEY);  
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();  
        return sign;  
    }  

    /** 
     * @author 
     * @date 2016-4-22 
     * @Description:将请求参数转换为xml格式的string 
     * @param parameters 
     *            请求参数 
     * @return 
     */  
    public static String getRequestXml(SortedMap<Object, Object> parameters) {  
        StringBuffer sb = new StringBuffer();  
        sb.append("<xml>");  
        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 + ">" + "<![CDATA[" + v + "]]></" + k + ">");  
            } else {  
                sb.append("<" + k + ">" + v + "</" + k + ">");  
            }  
        }  
        sb.append("</xml>");  
        return sb.toString();  
    }  

    /** 
     * 取出一个指定长度大小的随机正整数. 
     *  
     * @param length 
     *            int 设定所取出随机数的长度。length小于11 
     * @return int 返回生成的随机数。 
     */  
    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));  
    }  

    /** 
     * 获取当前时间 yyyyMMddHHmmss 
     *  
     * @return String 
     */  
    public static String getCurrTime() {  
        Date now = new Date();  
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");  
        String s = outFormat.format(now);  
        return s;  
    }  
}

      HttpUtil:

package com.dmsd.wechat.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

public class HttpUtil {
    //private static final Log logger = Logs.get();  
    private final static int CONNECT_TIMEOUT = 5000; // in milliseconds  
    private final static String DEFAULT_ENCODING = "UTF-8";  

    public static String postData(String urlStr, String data){  
        return postData(urlStr, data, null);  
    }  

    public static String postData(String urlStr, String data, String contentType){  
        BufferedReader reader = null;  
        try {  
            URL url = new URL(urlStr);  
            URLConnection conn = url.openConnection();  
            conn.setDoOutput(true);  
            conn.setConnectTimeout(CONNECT_TIMEOUT);  
            conn.setReadTimeout(CONNECT_TIMEOUT);  
            if(contentType != null)  
                conn.setRequestProperty("content-type", contentType);  
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);  
            if(data == null)  
                data = "";  
            writer.write(data);   
            writer.flush();  
            writer.close();    

            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));  
            StringBuilder sb = new StringBuilder();  
            String line = null;  
            while ((line = reader.readLine()) != null) {  
                sb.append(line);  
                sb.append("\r\n");  
            }  
            return sb.toString();  
        } catch (IOException e) {  
            //logger.error("Error connecting to " + urlStr + ": " + e.getMessage());  
        } finally {  
            try {  
                if (reader != null)  
                    reader.close();  
            } catch (IOException e) {  
            }  
        }  
        return null;  
    }  
}

      MD5Util:

package com.dmsd.wechat.util;

import java.security.MessageDigest;

public class MD5Util {
     private static String byteArrayToHexString(byte b[]) {  
            StringBuffer resultSb = new StringBuffer();  
            for (int i = 0; i < b.length; i++)  
                resultSb.append(byteToHexString(b[i]));  

            return resultSb.toString();  
        }  

        private static String byteToHexString(byte b) {  
            int n = b;  
            if (n < 0)  
                n += 256;  
            int d1 = n / 16;  
            int d2 = n % 16;  
            return hexDigits[d1] + hexDigits[d2];  
        }  

        public static String MD5Encode(String origin, String charsetname) {  
            String resultString = null;  
            try {  
                resultString = new String(origin);  
                MessageDigest md = MessageDigest.getInstance("MD5");  
                if (charsetname == null || "".equals(charsetname))  
                    resultString = byteArrayToHexString(md.digest(resultString  
                            .getBytes()));  
                else  
                    resultString = byteArrayToHexString(md.digest(resultString  
                            .getBytes(charsetname)));  
            } catch (Exception exception) {  
            }  
            return resultString;  
        }  

        private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",  
                "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };  
}

      PayConfigUtil:

package com.dmsd.wechat.util;

public class PayConfigUtil {
     // 账号信息  
    public static String appid = "********";  // appid 填写自己的 
    public static String APP_SECRET = "********"; // appsecret 填写自己的
    public static String MCH_ID = "********"; // 商业号 填写自己微信的 
    public static String API_KEY ="*********"; // Api key 填写自己微信的 
    public static String CREATE_IP ="192.168.21.95";  //ip地址
    //public static String NOTIFY_URL ="http://www.weixin.qq.com/wxpay/pay.php"; //回调url,这个是微信自己的回调接口,小编测试使用
    public static String NOTIFY_URL ="http://tfjybj.com/WCPay/"; //回调url
    public static String UFDODER_URL ="https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一下单url,微信提供的
}

      XMLUtil:

package com.dmsd.wechat.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;



public class XMLUtil {
    /** 
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 
     * @param strxml 
     * @return 
     * @throws JDOMException 
     * @throws IOException 
     */  
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {  
        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();  
        Document doc = builder.build(in);  
        Element root = doc.getRootElement();  
        List list = root.getChildren();  
        Iterator it = list.iterator();  
        while(it.hasNext()) {  
            Element e = (Element) it.next();  
            String k = e.getName();  
            String v = "";  
            List children = e.getChildren();  
            if(children.isEmpty()) {  
                v = e.getTextNormalize();  
            } else {  
                v = XMLUtil.getChildrenText(children);  
            }  

            m.put(k, v);  
        }  

        //关闭流  
        in.close();  

        return m;  
    }  

    /** 
     * 获取子结点的xml 
     * @param children 
     * @return String 
     */  
    public static String getChildrenText(List children) {  
        StringBuffer sb = new StringBuffer();  
        if(!children.isEmpty()) {  
            Iterator it = children.iterator();  
            while(it.hasNext()) {  
                Element e = (Element) it.next();  
                String name = e.getName();  
                String value = e.getTextNormalize();  
                List list = e.getChildren();  
                sb.append("<" + name + ">");  
                if(!list.isEmpty()) {  
                    sb.append(XMLUtil.getChildrenText(list));  
                }  
                sb.append(value);  
                sb.append("</" + name + ">");  
            }  
        }  

        return sb.toString();  
    }  
}

3.5 用户支付后,回调方法

      异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。

      小编通过阿里云来进行外网访问,然后通过Nginx来做反向代理,映射内网地址。回调地址是访问一个Servlet,通过这个Servlet来解析返回的信息,根据自己的业务在Servlet中做自己的业务,比如把信息存储到数据库,或者其他的操作。

      NotifyServlet:

package com.dmsd.wechat.pay;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jdom.JDOMException;

import com.dmsd.wechat.util.PayCommonUtil;
import com.dmsd.wechat.util.PayConfigUtil;
import com.dmsd.wechat.util.XMLUtil;

/**
 * 微信支付回调url,调用这个方法,获取返回的是否支付成功的数据-王雷-2017年10月5日08:05:26
 * @author Ares
 *
 */
public class NotifyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;



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

//      request.setAttribute("test", "跳转成功");
//      // 信息界面显示
//      request.getRequestDispatcher("/result.jsp").forward(request, response);



          //读取参数  
           InputStream inputStream ;  
           StringBuffer sb = new StringBuffer();  
           inputStream = request.getInputStream();  
           String s ;  
           BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));  
           while ((s = in.readLine()) != null){  
               sb.append(s);
           }
           in.close();
           inputStream.close();

           //解析xml成map  
           Map<String, String> m = new HashMap<String, String>();  
           try {
               m = XMLUtil.doXMLParse(sb.toString());
            } catch (JDOMException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }  

           //过滤空 设置 TreeMap  
           SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();        
           Iterator it = m.keySet().iterator();  
           while (it.hasNext()) {  
               String parameter = (String) it.next();
               String parameterValue = m.get(parameter);

               String v = "";  
               if(null != parameterValue) {
                   v = parameterValue.trim();  
               }  
               packageParams.put(parameter, v);  
           }  

           // 账号信息  
           String key = PayConfigUtil.API_KEY; //key  

           //判断签名是否正确  
           if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) {  
               //------------------------------  
               //处理业务开始  
               //------------------------------  
               String resXml = "";  
               if("SUCCESS".equals((String)packageParams.get("result_code"))){  
                   // 这里是支付成功  
                   //执行自己的业务逻辑  
                   String mch_id = (String)packageParams.get("mch_id");  
                   String openid = (String)packageParams.get("openid");  
                   String is_subscribe = (String)packageParams.get("is_subscribe");  
                   String out_trade_no = (String)packageParams.get("out_trade_no");  
                   String total_fee = (String)packageParams.get("total_fee");  

                   //执行自己的业务逻辑 
                   //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
                   //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
                   request.getSession().setAttribute("_PAY_RESULT", "OK");

                   System.out.println("支付成功");  
                   //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.  
                   resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
                           + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";  

               } else {
                   resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
                           + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";  
               }
               //------------------------------  
               //处理业务完毕  
               //------------------------------  
               BufferedOutputStream out = new BufferedOutputStream(  
                       response.getOutputStream());  
               out.write(resXml.getBytes());  
               out.flush();  
               out.close();  
           } else{  
             System.out.println("通知签名验证失败");  
           }
    }


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

}

四、运行结果


用手机扫描后,就可以进行支付了,这个支付的流程相信大家都很熟悉了:









五、小结

      小编想吐槽一下,申请微信支付的时候,各种提交的信息问题,联系客服。有一个是刚提交了资料,等微信给小编打一笔钱,作为验证码。一直迟迟没有打过来,其实是小编的信息填错了。所以,还是要多多的研究公司的一些信息。谢谢大家。


举报

相关推荐

0 条评论