JAVA微信扫码支付模式二功能实现完整例子
概述
详细
一、准备工作
先开通微信公众号,再开通微信公众号里面的微信支付功能,这些是前提条件,多说一句,申请开通微信公众号需要等待审核,然后在开通微信支付功能,还得等待审核,前前后后耗时得好几天。
关于准备工作,再看看微信官方关于“微信支付”的介绍,官方地址 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1。这个是文档的准备,大概可以理解到微信支付有哪些模式,然后大概是怎样一个东东。
然后重点看看如下几个,实际上需要准备的东西,红色花掉的部分(PayConfigUtil类里面),需要根据自己的实际情况填写:
其中APP_ID和APP_SECRET可以在公众平台找着,MCH_ID和API_KEY则在商户平台找到,特别是API_KEY要在商户平台设置好,对于“微信扫码支付模式二”(支付与回调)实际只会用到APP_ID、MCH_ID和API_KEY,其他的都不用。
二、程序实现
这里使用spring mvc做一个购买商品,微信扫码支付的演示。先项目代码截图,
以下摘取重点环节的代码说明下:
1、首先是接入微信接口,获取微信支付二维码。
- package com.demodashi;
- import java.util.Map;
- import java.util.SortedMap;
- import java.util.TreeMap;
- import javax.inject.Named;
- import com.demodashi.pay.util.HttpUtil;
- import com.demodashi.pay.util.PayToolUtil;
- import com.demodashi.pay.util.PayConfigUtil;
- import com.demodashi.pay.util.XMLUtil4jdom;
- @Named("userService")
- public class UserServiceImpl implements UserService {
- @Override
- public String weixinPay(String userId, String productId) throws Exception {
- String out_trade_no = "" + System.currentTimeMillis(); //订单号 (调整为自己的生产逻辑)
- // 账号信息
- String appid = PayConfigUtil.APP_ID; // appid
- //String appsecret = PayConfigUtil.APP_SECRET; // appsecret
- String mch_id = PayConfigUtil.MCH_ID; // 商业号
- String key = PayConfigUtil.API_KEY; // key
- String currTime = PayToolUtil.getCurrTime();
- String strTime = currTime.substring(8, currTime.length());
- String strRandom = PayToolUtil.buildRandom(4) + "";
- String nonce_str = strTime + strRandom;
- // 获取发起电脑 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", "可乐"); //(调整为自己的名称)
- packageParams.put("out_trade_no", out_trade_no);
- packageParams.put("total_fee", "10"); //价格的单位为分
- packageParams.put("spbill_create_ip", spbill_create_ip);
- packageParams.put("notify_url", notify_url);
- packageParams.put("trade_type", trade_type);
- String sign = PayToolUtil.createSign("UTF-8", packageParams,key);
- packageParams.put("sign", sign);
- String requestXML = PayToolUtil.getRequestXml(packageParams);
- System.out.println(requestXML);
- String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
- Map map = XMLUtil4jdom.doXMLParse(resXml);
- String urlCode = (String) map.get("code_url");
- return urlCode;
- }
- }
以上代码会按照微信支付的协议,生成类似这样格式的URL:weixin://wxpay/bizpayurl?pr=pIxXXXX
2、根据以上方法所产生的URL生成二维码,这里采用我采用的是google的core.jar包来生成二维码
- @ResponseBody
- @RequestMapping("/qrcode.do")
- public void qrcode(HttpServletRequest request, HttpServletResponse response,
- ModelMap modelMap) {
- try {
- String productId = request.getParameter("productId");
- String userId = "user01";
- String text = userApplication.weixinPay(userId, productId);
- //根据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);
- QRUtil.writeToStream(bitMatrix, format, response.getOutputStream());
- } catch (WriterException e) {
- e.printStackTrace();
- }
- } catch (Exception e) {
- }
- }
上面代码中涉及到几个工具类:PayConfigUtil、PayCommonUtil、HttpUtil和XMLUtil,其中PayConfigUtil放的就是上面提到一些配置及路径,PayCommonUtil涉及到了获取当前事件、产生随机字符串、获取参数签名和拼接xml几个方法,代码如下:
- package com.demodashi.pay.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 PayToolUtil {
- /**
- * 是否签名正确,规则是:按参数名称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.demodashi.pay.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;
- /**
- * http工具类,负责发起post请求并获取的返回
- */
- public class HttpUtil {
- 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;
- }
- }
XMLUtil4jdom类如下:
- package com.demodashi.pay.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 XMLUtil4jdom {
- /**
- * 解析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<String, String> m = new HashMap<String, String>();
- 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 = XMLUtil4jdom.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(XMLUtil4jdom.getChildrenText(list));
- }
- sb.append(value);
- sb.append("</" + name + ">");
- }
- }
- return sb.toString();
- }
- }
2、支付回调
支付完成后,微信会把相关支付结果和用户信息发送到我们上面指定的那个回调地址,我们需要接收处理,并返回应答。对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
关于支付回调接口,我们首先要对于支付结果通知的内容进行签名验证,然后根据支付结果进行相应的处理流程即可。
支付回调需要在微信公众号的微信支付里面设置回调地址:
- /**
- * 微信平台发起的回调方法,
- * 调用我们这个系统的这个方法接口,将扫描支付的处理结果告知我们系统
- * @throws JDOMException
- * @throws Exception
- */
- public void weixinNotify(HttpServletRequest request, HttpServletResponse response) throws JDOMException, Exception{
- //读取参数
- 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>();
- m = XMLUtil4jdom.doXMLParse(sb.toString());
- //过滤空 设置 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(PayToolUtil.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("通知签名验证失败");
- }
- }
3、支付后网页自动跳转
web页面弹出二维码后,就开启轮询,询问系统后台支付有微信平台的成功支付返回了,如果有,则跳转到支付成功的页面。
- <%@ page language="java" pageEncoding="UTF-8"%>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <script type="text/javascript" charset="utf-8" src="/resource/js/jquery-2.min.js"></script>
- <script type="text/javascript" charset="utf-8" src="/resource/js/layer/layer.js"></script>
- <title>微信扫码支付例子</title>
- </head>
- <body>
- <form id="pay_form" method="post" >
- <h1>可乐特价:0.1元/罐 <input id="pay_submit" name="but" type="button" value="微信支付"/></h1>
- </form>
- </body>
- <script>
- $(function(){
- $("#pay_submit").click(function(){
- buy('001');//传入可乐的ID号
- });
- });
- /**
- * 购买
- */
- function buy(productId){
- //打开付费二维码 -- 微信二维码
- layer.open({
- area: ['300px', '300px'],
- type: 2,
- closeBtn: false,
- title: false,
- shift: 2,
- shadeClose: true,
- content:'../user/qrcode.do?productId=' + productId
- });
- //重复执行某个方法
- var t1 = window.setInterval("getPayState('" + productId + "')",1500);
- }
- function getPayState(productId){
- var url = '../user/hadPay.do?productId=' + productId;
- //轮询是否已经付费
- $.ajax({
- type:'post',
- url:url,
- data:{productId:productId},
- cache:false,
- async:true,
- success:function(json){
- if(json.result == 0){
- location.href = '/result.jsp';
- }
- },
- error:function(){
- layer.msg("执行错误!", 8);
- }
- });
- }
- </script>
- </html>
三、运行效果
项目导入eclipse后,发表到tomcat中运行,或者通过jetty运行,跑起来后,访问:
点击微信支付:
这个时候在手机上用微信扫码:
支付成功后:
然后web网页会跳转到购买成功的页面,这里需要注意,微信支付回调接口,最好部署在公网的服务器上,这样能被回调,我本地使用改hosts的方法来让支付回调,不成功。
四、注意点
本例子为了演示,所以一些业务逻辑特别简单,例如:订单号的生产,这里只是简单的用当前时间long数字来表示:
- String out_trade_no = "" + System.currentTimeMillis(); //订单号 (调整为自己的生产逻辑)
实际开发的时候需要考虑并且情况下的订单号的唯一性。
还有,回调接口,考虑很简单:
- //////////执行自己的业务逻辑////////////////
- //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
- //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
- request.getSession().setAttribute("_PAY_RESULT", "OK");
- System.out.println("支付成功");
实际开发,要把支付成功DB保存下来,以及回调信息log下来等等
注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权
JAVA微信扫码支付模式二功能实现完整例子的更多相关文章
- C# 微信扫码支付API (微信扫码支付模式二)
一.SDK下载地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1,下载.NET C#版本: 二.微信相关设置:(微信扫码 ...
- .NET MVC结构框架下的微信扫码支付模式二 API接口开发测试
直接上干货 ,我们的宗旨就是为人民服务.授人以鱼不如授人以渔.不吹毛求疵.不浮夸.不虚伪.不忽悠.一切都是为了社会共同进步,繁荣昌盛,小程序猿.大程序猿.老程序猿还是嫩程序猿,希望这个社会不要太急功近 ...
- .NET微信扫码支付模式二API接口开发测试
主要实现微信扫码支付,官网的SDKdemo 就不要使用 一直不能调试通过的,还是自己按照API接口文档一步一步来实现,吐槽下微信一点责任感都木有,能不能demo搞个正常的吗,不要坑惨了一大群码农们有点 ...
- thinkphp5.0 微信扫码支付模式二
仅供个人参考,方便大家. 一.1)https://pay.weixin.qq.com/index.php/core/home/login 复制此地址 打开微信商户平台. 2)下载安全操作证书(最好在 ...
- Python实现微信扫码支付模式二(NativePay)
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/7649207.html 核心代码github地址:https://github.com/ygj0930/Pyth ...
- 微信公众号 扫码支付 模式二 demo
扫码支付 本文附有代码,在下方,如果不熟悉场景的可以看看下面的场景介绍 场景介绍 官网介绍地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?ch ...
- Java之微信支付(扫码支付模式二)案例实战
摘要:最近的一个项目中涉及到了支付业务,其中用到了微信支付和支付宝支付,在做的过程中也遇到些问题,所以现在总结梳理一下,分享给有需要的人,也为自己以后回顾留个思路. 一:微信支付接入准备工作: 首先, ...
- 微信支付Native扫码支付模式二之CodeIgniter集成篇
CI:3.0.5 微信支付API类库来自:https://github.com/zhangv/wechat-pay 请先看一眼官方场景及支付时序图:https://pay.weixin.qq.com/ ...
- java微信扫码支付Native(模式二)
官方开发文档模式二的地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5 pom文件的依赖: <?xml versio ...
随机推荐
- ASP.NET MVC:无法向会话状态服务器发出会话状态请求
ylbtech-Error-ASP.NET MVC: 无法向会话状态服务器发出会话状态请求 无法向会话状态服务器发出会话状态请求.请确保 ASP.NET State Service (ASP.NET ...
- WebFormViewEngine及用户控件寻址bug
在做我的网站的时候遇到了主题切换的问题,特总结与大家共享. 熟悉asp.net mvc的朋友都知道,mvc中,默认情况下视图都在views文件夹下放着.要想改变文件必须重写WebFormViewEng ...
- FPC and Qt
Introduction There are a number of Qt bindings available: Qt3 A QtC based binding by Theo Another Qt ...
- GetTextMetrics与GetTextExtent的区别
GetTextMetrics:获取当前字体的信息 GetTextExtent:获取特定的字符串在屏幕上所占的宽度和高度 CDC::GetTextMetrics 作用: 返回当前设备描述表中的当前所用的 ...
- lstm(一) 演化之路
递归神经网络引入了时序的反馈机制,在语音.音乐等时序信号的分析上有重要的意义. Hochreiter(应该是Schmidhuber的弟子)在1991年分析了bptt带来的梯度爆炸和消失问题,给学习算法 ...
- Python基础案例教程
一.超市买薯片 # 用户输入薯片的单价 danjia = float(input("薯片的单价")) # 用户输入购买袋数 daishu = int(input("购买的 ...
- (文档)Shader.Find (在编译时,只包含那些使用中的shader或位置在"Resources"文件夹中shader)
Shader.Find 查找 static function Find (name : string) : Shader Description描述 Finds a shader with the g ...
- 上机题目(0基础)- Java网络操作-打印网页(Java)
打印一个网页,熟悉Java网络编程: import java.io.BufferedReader; import java.io.IOException; import java.io.InputSt ...
- Idea不能新建package的解决
右键–>new –> Mark Directory As –> Sources Root (idea需要修改一下目录的性质,改为源文件 )
- (转)C/C++ 程序设计员应聘常见 面试笔试 试题深入剖析
C/C++ 程序设计员应聘常见 面试笔试 试题深入剖析 http://www.nowcoder.com/discuss/1826?type=2&order=0&pos=23&p ...