UDN-企业互联网技术人气社区

板块导航

浏览  : 2493
回复  : 2

[原生js] Java SpringMVC实现PC端网页微信扫码支付(完整版)

[复制链接]
西北的风的头像 楼主
发表于 2017-1-17 16:36:18 | 显示全部楼层 |阅读模式
  这篇文章主要介绍了Java SpringMVC实现PC端网页微信扫码支付(完整版)的相关资料,非常不错具有一定的参考借鉴价值,需要的朋友可以参考下

  一:前期微信支付扫盲知识
  
  前提条件是已经有申请了微信支付功能的公众号,然后我们需要得到公众号APPID和微信商户号,这个分别在微信公众号和微信支付商家平台上面可以发现。其实在你申请成功支付功能之后,微信会通过邮件把Mail转给你的,有了这些信息之后,我们就可以去微信支付服务支持页面:https://pay.weixin.qq.com/service_provider/index.shtml
  
  打开这个页面,点击右上方的链接【开发文档】会进入到API文档说明页面,看起来如下
 
201611011617106.png
 
  选择红色圆圈的扫码支付就是我们要做接入方式,鼠标移动到上面会提示你去查看开发文档,如果这个都不知道怎么查看,可以洗洗睡了,你真的不合适做程序员,地址如下:
  
  https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 在浏览器中打开之后会看到
 
201611011617107.png
 
  我们重点要关注和阅读的内容我已经用红色椭圆标注好了,首先阅读【接口规则】里面的协议规范,开玩笑这个都不读你就想做微信支付,这个就好比你要去泡妞,得先收集点基本背景信息,了解对方特点,不然下面还怎么沟通。事实证明只有会泡妞得程序员才是好销售。跑题了我们接下来要看一下【场景介绍】中的案例与规范,只看一下记得一定要微信支付的LOGO下载下来,是为了最后放到我们自己的扫码支付网页上,这样看上去比较专业一点。之后重点关注【模式二】
  
  我们这里就是要采用模式二的方式实现PC端页面扫码支付功能。
  
  微信官方对模式二的解释是这样的“商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付”。看明白了吧就是我们首先要调用微信提供统一下单接口,得到一个关键信息code_url(至于这个code_url是什么鬼,我也不知道),然后我们通过自己的程序把这个URL生成一个二维码,生成二维码我这里用了Google的zxing库。然后把这个二维码显示在你的PC端网页上就行啦。这样终端用户一扫码就支付啦,支付就完成啦,看到这里你肯定很激动,发现微信支付如此简单,等等还有个事情我们还不知道,客户知道付钱了,我们服务器端还不知道呢,以微信开发人员的智商他们早就想到这个问题了,所以让你在调用统一下单接口的时候其中有个必填的参数就是回调URL,就是如果客户端付款成功之后微信会通过这个URL向我们自己的服务器提交一些数据,然后我们后台解析这些数据,完成我们自己操作。这样我们才知道客户是否真的已经通过微信付款了。这样整个流程才结束,这个就是模式二。微信用一个时序图示这样表示这个过程的。
201611011617108.png
  
  表达起来比较复杂,看上去比较吃力,总结一下其实我们服务器该做的事情就如下件:
  
  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列表】中的统一下单会看到这样页面:
 
201611011617109.png
 
  以本人调用实际情况为例,如下的参数是必须要有的,为了大家的方便我已经把它变成一个POJO的对象, 代码如下:
  1. public class UnifiedorderDto implements WeiXinConstants {
  2. private String appid;
  3. private String body;
  4. private String device_info;
  5. private String mch_id;
  6. private String nonce_str;
  7. private String notify_url;
  8. private String openId;
  9. private String out_trade_no;
  10. private String spbill_create_ip;
  11. private int total_fee;
  12. private String trade_type;
  13. private String product_id;
  14. private String sign;
  15. public UnifiedorderDto() {
  16. this.appid = APPID;
  17. this.mch_id = WXPAYMENTACCOUNT;
  18. this.device_info = DEVICE_INFO_WEB;
  19. this.notify_url = CALLBACK_URL;
  20. this.trade_type = TRADE_TYPE_NATIVE;
  21. }
  22. public String getAppid() {
  23. return appid;
  24. }
  25. public void setAppid(String appid) {
  26. this.appid = appid;
  27. }
  28. public String getBody() {
  29. return body;
  30. }
  31. public void setBody(String body) {
  32. this.body = body;
  33. }
  34. public String getDevice_info() {
  35. return device_info;
  36. }
  37. public void setDevice_info(String device_info) {
  38. this.device_info = device_info;
  39. }
  40. public String getMch_id() {
  41. return mch_id;
  42. }
  43. public void setMch_id(String mch_id) {
  44. this.mch_id = mch_id;
  45. }
  46. public String getNonce_str() {
  47. return nonce_str;
  48. }
  49. public void setNonce_str(String nonce_str) {
  50. this.nonce_str = nonce_str;
  51. }
  52. public String getNotify_url() {
  53. return notify_url;
  54. }
  55. public void setNotify_url(String notify_url) {
  56. this.notify_url = notify_url;
  57. }
  58. public String getOpenId() {
  59. return openId;
  60. }
  61. public void setOpenId(String openId) {
  62. this.openId = openId;
  63. }
  64. public String getOut_trade_no() {
  65. return out_trade_no;
  66. }
  67. public void setOut_trade_no(String out_trade_no) {
  68. this.out_trade_no = out_trade_no;
  69. }
  70. public String getSpbill_create_ip() {
  71. return spbill_create_ip;
  72. }
  73. public void setSpbill_create_ip(String spbill_create_ip) {
  74. this.spbill_create_ip = spbill_create_ip;
  75. }
  76. public int getTotal_fee() {
  77. return total_fee;
  78. }
  79. public void setTotal_fee(int total_fee) {
  80. this.total_fee = total_fee;
  81. }
  82. public String getTrade_type() {
  83. return trade_type;
  84. }
  85. public void setTrade_type(String trade_type) {
  86. this.trade_type = trade_type;
  87. }
  88. public String getSign() {
  89. return sign;
  90. }
  91. public void setSign(String sign) {
  92. this.sign = sign;
  93. }
  94. public String getProduct_id() {
  95. return product_id;
  96. }
  97. public void setProduct_id(String product_id) {
  98. this.product_id = product_id;
  99. }
  100. public String generateXMLContent() {
  101. String xml = "<xml>" +
  102. "" + this.appid + "</appid>" +
  103. "" + this.body + "" +
  104. "<device_info>WEB</device_info>" +
  105. "<mch_id>" + this.mch_id + "</mch_id>" +
  106. "<nonce_str>" + this.nonce_str + "</nonce_str>" +
  107. "<notify_url>" + this.notify_url + "</notify_url>" +
  108. "<out_trade_no>" + this.out_trade_no + "</out_trade_no>" +
  109. "<product_id>" + this.product_id + "</product_id>" +
  110. "<spbill_create_ip>" + this.spbill_create_ip+ "</spbill_create_ip>" +
  111. "<total_fee>" + String.valueOf(this.total_fee) + "</total_fee>" +
  112. "<trade_type>" + this.trade_type + "</trade_type>" +
  113. "<sign>" + this.sign + "</sign>" +
  114. "</xml>";
  115. return xml;
  116. }
  117. public String makeSign() {
  118. String content ="appid=" + this.appid +
  119. "&body=" + this.body +
  120. "&device_info=WEB" +
  121. "&mch_id=" + this.mch_id +
  122. "&nonce_str=" + this.nonce_str +
  123. "?ify_url=" + this.notify_url +
  124. "&out_trade_no=" + this.out_trade_no +
  125. "&product_id=" + this.product_id +
  126. "&spbill_create_ip=" + this.spbill_create_ip+
  127. "&total_fee=" + String.valueOf(this.total_fee) +
  128. "&trade_type=" + this.trade_type;
  129. content = content + "&key=" + WeiXinConstants.MD5_API_KEY;
  130. String esignature = WeiXinPaymentUtil.MD5Encode(content, "utf-8");
  131. return esignature.toUpperCase();
  132. }
  133. }
复制代码

  其中各个成员变量的解释可以参见【统一下单接口】的说明即可。
  
  有这个之后我们就要要设置的内容填写进去,去调用该接口得到返回数据,从中拿到code_url的数据然后据此生成一个二维图片,把图片的地址返回给PC端网页,然后它就会显示出来,这里要特别说明一下,我们自己PC端网页在点击微信支付的时候就会通过ajax方式调用我们自己后台的SpringMVC Controller然后在Controller的对应方法中通过HTTPClient完成对微信统一下单接口调用解析返回的XML数据得到code_url的值,生成二维码之后返回给前台网页。Controller中实现的代码如下:
  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;</string,></string,></string,object></string,object>
复制代码

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

  此时如何客户端微信扫码之后,微信就会通过回调我们制定URL返回数据给我们。在回调方法中完成我们自己的处理,这里要特别注意的是你的回调接口必须通过HTTP POST方法实现,否则无法接受到XML数据。回调处理的代码如下:
  1. @RequestMapping(value = "/your_callback_url", method = RequestMethod.POST)
  2. @ResponseBody
  3. public void finishPayment(HttpServletRequest request, HttpServletResponse response) {
  4. try { logger.info("start to callback from weixin server: " + request.getRemoteHost());
  5. Map<string, string=""> resultMap = new HashMap<string, string="">();
  6. InputStream inputStream = request.getInputStream();
  7. // 读取输入流
  8. SAXBuilder saxBuilder= new SAXBuilder();
  9. Document document = saxBuilder.build(inputStream);
  10. // 得到xml根元素
  11. Element root = document.getRootElement();
  12. // 得到根元素的所有子节点
  13. List list = root.getChildren();
  14. Iterator it = list.iterator();
  15. while(it.hasNext()) {
  16. Element e = (Element) it.next();
  17. String k = e.getName();
  18. String v = "";
  19. List children = e.getChildren();
  20. if(children.isEmpty()) {
  21. v = e.getTextNormalize();
  22. } else {
  23. v = XMLUtil.getChildrenText(children);
  24. }
  25. resultMap.put(k, v);
  26. }
  27. // 验证签名!!!
  28. /*
  29. String[] keys = resultMap.keySet().toArray(new String[0]);
  30. Arrays.sort(keys);
  31. String kvparams = "";
  32. for(int i=0; i<keys.length; i++)="" {="" if(keys[i].equals("esign"))="" continue;="" }="" 签名算法="" if(i="=" 0)="" kvparams="" +="(keys[i]" "=" + resultMap.get(keys[i]));
  33. } else {
  34. kvparams += (" &"="" keys[i]="" &key=" + WeiXinConstants.MD5_API_KEY;
  35. String md5esign = WeiXinPaymentUtil.MD5Encode(esign, " utf-8");="" if(!md5esign.equals(resultmap.get("sign")))="" return;="" }*="" 关闭流="" 释放资源="" inputstream.close();="" inputstream="null;" string="" returncode="resultMap.get("return_code");" outtradeno="resultMap.get("out_trade_no");" 以分为单位="" int="" nfee="Integer.parseInt(resultMap.get("total_fee"));" logger.info("out="" trade="" no="" :="" outtradeno);="" logger.info("total_fee="" nfee);="" 业务处理流程="" if("success".equals(returncode))="" todo:="" your="" business="" process="" add="" here="" response.getwriter().print(xmlutil.getretresultxml(resultmap.get("return_code"),="" resultmap.get("return_code")));="" else="" resultmap.get("return_msg")));="" catch(ioexception="" ioe)="" ioe.printstacktrace();="" catch="" (jdomexception="" e1)="" e1.printstacktrace();="" }
复制代码

  微信官方java版demo用到的xmlutil和md5util的两个类记得拿过来改一下,演示代码可以在它的官方演示页面找到,相关maven依赖如下:
  1. <dependency>
  2. <groupid>jdom</groupid>
  3. jdom</artifactid>
  4. <version>1.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupid>com.google.zxing</groupid>
  8. core</artifactid>
  9. <version>3.3.0</version>
  10. </dependency>
复制代码

  最后要特别注意的是关于签名,签名生成MD5的类我是从微信官网直接下载Java版Demo程序获取的,建议你也是,因为这个是确保MD5签名是一致的最佳选择。具体的生成签名的算法可以查看微信官方文档,这里也强烈建议大家一定要官方API说明,你开发中所遇到各种问题90%都是因为不看官方文档,而是轻信某人博客!这个才是我写这篇文章的真正目的和用意,根据官方文档,用我的Java代码实现,微信PC端网页扫码支付必定在你的WEB应用中飞起来。
  
  以上所述是小编给大家介绍的Java SpringMVC实现PC端网页微信扫码支付(完整版),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关帖子

发表于 2017-1-17 16:36:47 | 显示全部楼层
貌似看过类似的文章恩,排版更清晰点就更好了
使用道具 举报

回复

发表于 2017-1-18 06:41:23 | 显示全部楼层
还是挺有借鉴意义的
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部