zoukankan      html  css  js  c++  java
  • Java SpringMVC实现PC端网页微信扫码支付完整版

    一:前期微信支付扫盲知识

    前提条件是已经有申请了微信支付功能的公众号,然后我们需要得到公众号APPID和微信商户号,这个分别在微信公众号和微信支付商家平台上面可以发现。其实在你申请成功支付功能之后,微信会通过邮件把Mail转给你的,有了这些信息之后,我们就可以去微信支付服务支持页面:https://pay.weixin.qq.com/service_provider/index.shtml

    打开这个页面,点击右上方的链接【开发文档】会进入到API文档说明页面,看起来如下

    选择红色圆圈的扫码支付就是我们要做接入方式,鼠标移动到上面会提示你去查看开发文档,如果这个都不知道怎么查看,可以洗洗睡了,你真的不合适做程序员,地址如下:

    https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1在浏览器中打开之后会看到

    我们重点要关注和阅读的内容我已经用红色椭圆标注好了,首先阅读【接口规则】里面的协议规范,开玩笑这个都不读你就想做微信支付,这个就好比你要去泡妞,得先收集点基本背景信息,了解对方特点,不然下面还怎么沟通。事实证明只有会泡妞得程序员才是好销售。跑题了我们接下来要看一下【场景介绍】中的案例与规范,只看一下记得一定要微信支付的LOGO下载下来,是为了最后放到我们自己的扫码支付网页上,这样看上去比较专业一点。之后重点关注【模式二】

    我们这里就是要采用模式二的方式实现PC端页面扫码支付功能。

        微信官方对模式二的解释是这样的“商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付”。看明白了吧就是我们首先要调用微信提供统一下单接口,得到一个关键信息code_url(至于这个code_url是什么鬼,我也不知道),然后我们通过自己的程序把这个URL生成一个二维码,生成二维码我这里用了Google的zxing库。然后把这个二维码显示在你的PC端网页上就行啦。这样终端用户一扫码就支付啦,支付就完成啦,看到这里你肯定很激动,发现微信支付如此简单,等等还有个事情我们还不知道,客户知道付钱了,我们服务器端还不知道呢,以微信开发人员的智商他们早就想到这个问题了,所以让你在调用统一下单接口的时候其中有个必填的参数就是回调URL,就是如果客户端付款成功之后微信会通过这个URL向我们自己的服务器提交一些数据,然后我们后台解析这些数据,完成我们自己操作。这样我们才知道客户是否真的已经通过微信付款了。这样整个流程才结束,这个就是模式二。微信用一个时序图示这样表示这个过程的。

    表达起来比较复杂,看上去比较吃力,总结一下其实我们服务器该做的事情就如下件:

    1. 通过统一下单接口传入正确的参数(当然要包括我们的回调URL)与签名验证,从返回数据中得到code_url的对应数据

    2. 根据code_url的数据我们自己生成一个二维码图片,显示在浏览器网页上

    3. 在回调的URL中添加我们自己业务逻辑处理。

    至此扫盲结束了,你终于知道扫码支付什么个什么样的流程了,下面我们就一起来扒扒它的相关API使用,做好每步处理。

    二:开发过程

    在开发代码之前,请先准备几件事情。

    1. 添加ZXing的maven依赖

    2. 添加jdom的maven依赖

    3.下载Java版本SDK演示程序,地址在这里

    https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1

    我们需要MD5Util.java和XMLUtil.java两个文件

    4. 我们使用HttpClient版本是4.5.1,记得添加Maven依赖

    上面准备工作做好以后,继续往下看:

    首先我们要调用微信的统一下单接口,我们点击【API列表】中的统一下单会看到这样页面:

    以本人调用实际情况为例,如下的参数是必须要有的,为了大家的方便我已经把它变成一个POJO的对象, 代码如下:

    [java] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. public class UnifiedorderDto implements WeiXinConstants {  
    2.   
    3.     private String appid;  
    4.     private String body;  
    5.     private String device_info;  
    6.     private String mch_id;  
    7.     private String nonce_str;  
    8.     private String notify_url;  
    9.     private String openId;  
    10.     private String out_trade_no;  
    11.     private String spbill_create_ip;  
    12.     private int total_fee;  
    13.     private String trade_type;  
    14.     private String product_id;  
    15.     private String sign;  
    16.       
    17.     public UnifiedorderDto() {  
    18.         this.appid = APPID;  
    19.         this.mch_id = WXPAYMENTACCOUNT;  
    20.         this.device_info = DEVICE_INFO_WEB;  
    21.         this.notify_url = CALLBACK_URL;  
    22.         this.trade_type = TRADE_TYPE_NATIVE;  
    23.     }  
    24.   
    25.     public String getAppid() {  
    26.         return appid;  
    27.     }  
    28.   
    29.     public void setAppid(String appid) {  
    30.         this.appid = appid;  
    31.     }  
    32.   
    33.     public String getBody() {  
    34.         return body;  
    35.     }  
    36.   
    37.     public void setBody(String body) {  
    38.         this.body = body;  
    39.     }  
    40.   
    41.     public String getDevice_info() {  
    42.         return device_info;  
    43.     }  
    44.   
    45.     public void setDevice_info(String device_info) {  
    46.         this.device_info = device_info;  
    47.     }  
    48.   
    49.     public String getMch_id() {  
    50.         return mch_id;  
    51.     }  
    52.   
    53.     public void setMch_id(String mch_id) {  
    54.         this.mch_id = mch_id;  
    55.     }  
    56.   
    57.     public String getNonce_str() {  
    58.         return nonce_str;  
    59.     }  
    60.   
    61.     public void setNonce_str(String nonce_str) {  
    62.         this.nonce_str = nonce_str;  
    63.     }  
    64.   
    65.     public String getNotify_url() {  
    66.         return notify_url;  
    67.     }  
    68.   
    69.     public void setNotify_url(String notify_url) {  
    70.         this.notify_url = notify_url;  
    71.     }  
    72.   
    73.     public String getOpenId() {  
    74.         return openId;  
    75.     }  
    76.   
    77.     public void setOpenId(String openId) {  
    78.         this.openId = openId;  
    79.     }  
    80.   
    81.     public String getOut_trade_no() {  
    82.         return out_trade_no;  
    83.     }  
    84.   
    85.     public void setOut_trade_no(String out_trade_no) {  
    86.         this.out_trade_no = out_trade_no;  
    87.     }  
    88.   
    89.     public String getSpbill_create_ip() {  
    90.         return spbill_create_ip;  
    91.     }  
    92.   
    93.     public void setSpbill_create_ip(String spbill_create_ip) {  
    94.         this.spbill_create_ip = spbill_create_ip;  
    95.     }  
    96.   
    97.     public int getTotal_fee() {  
    98.         return total_fee;  
    99.     }  
    100.   
    101.     public void setTotal_fee(int total_fee) {  
    102.         this.total_fee = total_fee;  
    103.     }  
    104.   
    105.     public String getTrade_type() {  
    106.         return trade_type;  
    107.     }  
    108.   
    109.     public void setTrade_type(String trade_type) {  
    110.         this.trade_type = trade_type;  
    111.     }  
    112.   
    113.     public String getSign() {  
    114.         return sign;  
    115.     }  
    116.   
    117.     public void setSign(String sign) {  
    118.         this.sign = sign;  
    119.     }  
    120.   
    121.     public String getProduct_id() {  
    122.         return product_id;  
    123.     }  
    124.   
    125.     public void setProduct_id(String product_id) {  
    126.         this.product_id = product_id;  
    127.     }  
    128.     public String generateXMLContent() {  
    129.         String xml = "<xml>" +  
    130.            "<appid>" + this.appid + "</appid>" +   
    131.            "<body>" + this.body + "</body>" +   
    132.            "<device_info>WEB</device_info>" +   
    133.            "<mch_id>" + this.mch_id + "</mch_id>" +   
    134.            "<nonce_str>" + this.nonce_str + "</nonce_str>" +  
    135.            "<notify_url>" + this.notify_url + "</notify_url>" +   
    136.            "<out_trade_no>" + this.out_trade_no + "</out_trade_no>" +   
    137.            "<product_id>" + this.product_id + "</product_id>" +  
    138.            "<spbill_create_ip>" + this.spbill_create_ip+ "</spbill_create_ip>" +  
    139.            "<total_fee>" + String.valueOf(this.total_fee) + "</total_fee>" +   
    140.            "<trade_type>" + this.trade_type + "</trade_type>" +   
    141.            "<sign>" + this.sign + "</sign>" +   
    142.         "</xml>";  
    143.         return xml;  
    144.     }  
    145.       
    146.     public String makeSign() {  
    147.         String content ="appid=" + this.appid +   
    148.                    "&body=" + this.body +   
    149.                    "&device_info=WEB" +   
    150.                    "&mch_id=" + this.mch_id +   
    151.                    "&nonce_str=" + this.nonce_str +   
    152.                    "¬ify_url=" + this.notify_url +  
    153.                    "&out_trade_no=" + this.out_trade_no +   
    154.                    "&product_id=" + this.product_id +   
    155.                    "&spbill_create_ip=" + this.spbill_create_ip+  
    156.                    "&total_fee=" + String.valueOf(this.total_fee) +  
    157.                    "&trade_type=" + this.trade_type;  
    158.         content = content + "&key=" + WeiXinConstants.MD5_API_KEY;  
    159.         String esignature = WeiXinPaymentUtil.MD5Encode(content, "utf-8");  
    160.         return esignature.toUpperCase();  
    161.     }  
    162.       
    163. }  

    其中各个成员变量的解释可以参见【统一下单接口】的说明即可。

    有这个之后我们就要要设置的内容填写进去,去调用该接口得到返回数据,从中拿到code_url的数据然后据此生成一个二维图片,把图片的地址返回给PC端网页,然后它就会显示出来,这里要特别说明一下,我们自己PC端网页在点击微信支付的时候就会通过ajax方式调用我们自己后台的SpringMVC Controller然后在Controller的对应方法中通过HTTPClient完成对微信统一下单接口调用解析返回的XML数据得到code_url的值,生成二维码之后返回给前台网页。Controller中实现的代码如下:

    [java] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. Map<String,Object> result=new HashMap<String,Object>();  
    2.         UnifiedorderDto dto = new UnifiedorderDto();  
    3.         if(cash == null || "".equals(cash)) {  
    4.             result.put("error", "cash could not be zero");  
    5.             return result;  
    6.         }  
    7.         int totalfee = 100*Integer.parseInt(cash);  
    8.         logger.info("total recharge cash : " + totalfee);  
    9.         dto.setProduct_id(String.valueOf(System.currentTimeMillis()));  
    10.         dto.setBody("repair");  
    11.         dto.setNonce_str(String.valueOf(System.nanoTime()));  
    12.         LoginInfo loginInfo = LoginInfoUtil.getLoginInfo();  
    13.         // 通过我们后台订单号+UUID为身份识别标志  
    14.         dto.setOut_trade_no("你的订单号+关键信息,微信回调之后传回,你可以验证");  
    15.         dto.setTotal_fee(totalfee);  
    16.         dto.setSpbill_create_ip("127.0.0.1");  
    17.         // generate signature  
    18.         dto.setSign(dto.makeSign());  
    19.         logger.info("sign : " + dto.makeSign());  
    20.         logger.info("xml content : " + dto.generateXMLContent());  
    21.         try {  
    22.             HttpClient httpClient = HttpClientBuilder.create().build();   
    23.             HttpPost post = new HttpPost(WeiXinConstants.UNIFIEDORDER_URL);  
    24.             post.addHeader("Content-Type", "text/xml; charset=UTF-8");  
    25.             StringEntity xmlEntity = new StringEntity(dto.generateXMLContent(), ContentType.TEXT_XML);  
    26.             post.setEntity(xmlEntity);  
    27.             HttpResponse httpResponse = httpClient.execute(post);  
    28.             String responseXML = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");  
    29.             logger.info("response xml content : " + responseXML);  
    30.             // parse CODE_URL CONTENT  
    31.             Map<String, String> resultMap = (Map<String, String>)XMLUtil.doXMLParse(responseXML);  
    32.             logger.info("response code_url : " + resultMap.get("code_url"));  
    33.             String codeurl = resultMap.get("code_url");  
    34.             if(codeurl != null && !"".equals(codeurl)) {  
    35.                 String imageurl = generateQrcode(codeurl);  
    36.                 result.put("QRIMAGE", imageurl);  
    37.             }  
    38.             post.releaseConnection();  
    39.         } catch(Exception e) {  
    40.             e.printStackTrace();  
    41.         }  
    42.         result.put("success", "1");  
    43.         return result;  

    生成二维码的代码如下:

    [java] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. private String generateQrcode(String codeurl) {  
    2.     File foldler = new File(basePath + "qrcode");  
    3.     if(!foldler.exists()) {  
    4.         foldler.mkdirs();  
    5.     }  
    6.       
    7.     String f_name = UUIDUtil.uuid() + ".png";  
    8.        try {  
    9.         File f = new File(basePath + "qrcode", f_name);  
    10.         FileOutputStream fio = new FileOutputStream(f);  
    11.         MultiFormatWriter multiFormatWriter = new MultiFormatWriter();  
    12.         Map hints = new HashMap();  
    13.         hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); //设置字符集编码类型  
    14.         BitMatrix bitMatrix = null;  
    15.            bitMatrix = multiFormatWriter.encode(codeurl, BarcodeFormat.QR_CODE, 300, 300,hints);  
    16.            BufferedImage image = toBufferedImage(bitMatrix);  
    17.            //输出二维码图片流  
    18.            ImageIO.write(image, "png", fio);  
    19.            return ("qrcode/" + f_name);  
    20.        } catch (Exception e1) {  
    21.            e1.printStackTrace();  
    22.            return null;  
    23.        }       
    24. }  

    此时如何客户端微信扫码之后,微信就会通过回调我们制定URL返回数据给我们。在回调方法中完成我们自己的处理,这里要特别注意的是你的回调接口必须通过HTTP POST方法实现,否则无法接受到XML数据。回调处理的代码如下:

    [java] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. @RequestMapping(value = "/your_callback_url", method = RequestMethod.POST)  
    2. @ResponseBody  
    3. public void finishPayment(HttpServletRequest request, HttpServletResponse response) {  
    4.     try {  
    5.         logger.info("start to callback from weixin server: " + request.getRemoteHost());  
    6.         Map<String, String> resultMap = new HashMap<String, String>();  
    7.         InputStream inputStream = request.getInputStream();  
    8.         // 读取输入流  
    9.         SAXBuilder saxBuilder= new SAXBuilder();  
    10.         Document document = saxBuilder.build(inputStream);  
    11.         // 得到xml根元素  
    12.         Element root = document.getRootElement();  
    13.         // 得到根元素的所有子节点  
    14.         List list = root.getChildren();  
    15.         Iterator it = list.iterator();  
    16.         while(it.hasNext()) {  
    17.             Element e = (Element) it.next();  
    18.             String k = e.getName();  
    19.             String v = "";  
    20.             List children = e.getChildren();  
    21.             if(children.isEmpty()) {  
    22.                 v = e.getTextNormalize();  
    23.             } else {  
    24.                 v = XMLUtil.getChildrenText(children);  
    25.             }  
    26.             resultMap.put(k, v);  
    27.         }  
    28.           
    29.         // 验证签名!!!  
    30.         /* 
    31.         String[] keys = resultMap.keySet().toArray(new String[0]); 
    32.         Arrays.sort(keys); 
    33.         String kvparams = ""; 
    34.         for(int i=0; i<keys.length; i++) { 
    35.             if(keys[i].equals("esign")) { 
    36.                 continue; 
    37.             } 
    38.             // 签名算法 
    39.             if(i == 0) { 
    40.                 kvparams += (keys[i] + "=" + resultMap.get(keys[i])); 
    41.             } else { 
    42.                 kvparams += ("&" + keys[i] + "=" + resultMap.get(keys[i])); 
    43.             } 
    44.         } 
    45.         String esign = kvparams + "&key=" + WeiXinConstants.MD5_API_KEY; 
    46.         String md5esign = WeiXinPaymentUtil.MD5Encode(esign, "UTF-8"); 
    47.         if(!md5esign.equals(resultMap.get("sign"))) { 
    48.             return; 
    49.         }*/  
    50.           
    51.         //关闭流  
    52.         // 释放资源  
    53.         inputStream.close();  
    54.         inputStream = null;  
    55.         String returnCode = resultMap.get("return_code");  
    56.         String outtradeno = resultMap.get("out_trade_no");  
    57.         // 以分为单位  
    58.         int nfee = Integer.parseInt(resultMap.get("total_fee"));  
    59.         logger.info("out trade no : " + outtradeno);  
    60.         logger.info("total_fee : " + nfee);  
    61.         // 业务处理流程  
    62.         if("SUCCESS".equals(returnCode)) {                
    63.             // TODO: your business process add here  
    64.             response.getWriter().print(XMLUtil.getRetResultXML(resultMap.get("return_code"), resultMap.get("return_code")));  
    65.         } else {  
    66.             response.getWriter().print(XMLUtil.getRetResultXML(resultMap.get("return_code"), resultMap.get("return_msg")));  
    67.         }  
    68.     }  
    69.     catch(IOException ioe) {  
    70.         ioe.printStackTrace();  
    71.     } catch (JDOMException e1) {  
    72.         e1.printStackTrace();  
    73.     }  
    74. }  

    微信官方Java版Demo用到的XMLUtil和MD5Util的两个类记得拿过来改一下,演示代码可以在它的官方演示页面找到,相关maven依赖如下:

    [html] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. <dependency>  
    2.     <groupId>jdom</groupId>  
    3.     <artifactId>jdom</artifactId>  
    4.     <version>1.1</version>  
    5. </dependency>  
    6. <dependency>  
    7.     <groupId>com.google.zxing</groupId>  
    8.     <artifactId>core</artifactId>  
    9.     <version>3.3.0</version>  
    10. </dependency>  

    最后要特别注意的是关于签名,签名生成MD5的类我是从微信官网直接下载Java版Demo程序获取的,建议你也是,因为这个是确保MD5签名是一致的最佳选择。具体的生成签名的算法可以查看微信官方文档,这里也强烈建议大家一定要官方API说明,你开发中所遇到各种问题90%都是因为不看官方文档,而是轻信某人博客!这个才是我写这篇文章的真正目的和用意,根据官方文档,用我的Java代码实现,微信PC端网页扫码支付必定在你的WEB应用中飞起来。

  • 相关阅读:
    网页中防拷贝、屏蔽鼠标右键代码
    Enterprise Library Exception Handling Application Block Part 1
    vs debug 技巧
    Winforms:消除WebBrowser的GDI Objects泄露
    WinForm窗体之间交互的一些方法
    TableLayoutPanel用法
    Winforms:把长ToolTip显示为多行
    Winforms: 不能在Validating时弹出有模式的对话框
    Winforms: 复杂布局改变大小时绘制错误
    读取Excel默认工作表导出XML
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/6249185.html
Copyright © 2011-2022 走看看