JAVA版微信支付V3-完全版

本人用的开发框架是:struts2(用了struts2的0配置,对于struts的0配置不熟悉的可以看看这个博客了解下 http://www.cnblogs.com/fpjason/archive/2009/08/01/1536671.html

本人做的是微信V3版本的微信支付,也是目前最新的微信支付接口。官方文档下载地址

https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course3_tmpl&lang=zh_CN

微信支付成功后  你的邮件会有以下信息:

1、 信息包括:商户ID(mch_id)、申请编号、登录帐号、登录密码、商户API密码(key)

2.、证书包括:商户API证书、证书密钥、CA证书

开发前,我们先登录自己的服务号,点击微信支付----->开发配置。

如果这里的授权路径和下面参数的notify_url不对  调用付款接口时会弹出access_denied。

比如,我的授权目录是http://183.33.212.175/wxweb/config/,那么我对应的notify_url的回调方法必须是String notify_url = "http://183.33.212.175:8016/wxweb/config/pay!paySuccess.action";这样的

下图是微信支付JSAPI支付方法的流程图。

登陆Oauth2授权的问题,我会另外写篇博客。在此略过。需要注意一点的是,微信支付是微信5.0以上版本才能支持,所以,可能有些用户的微信版本是低版本无法进行微信支付而用户又不知道为什么就是不能支付,我们需要进行一个版本判断,如果用户的微信版本低于5.0就告知用户一些提示信息。那么,如何判断用户的微信版本是多少呢?我们可以通过这个方法可以获取一些信息然后进行判断:request.getHeader("user-agent")获取信息。

以 iPhone 版本为例,可以通过 user agent 可获取如下微信版本示例信息:

"Mozilla/5.0(iphone;CPU iphone OS 5_1_1 like Mac OS X) AppleWebKit/534.46(KHTML,like Geocko) Mobile/9B206 MicroMessenger/5.0"

其中 5.0 为用户安装的微信版本号,商户可以判定版本号是否高于或者等于 5.0。具体判断代码请留意后面代码。

流程图中,我们看到需要先调用一次统一接口,然后微信会返回一个prepayId给我们。那么我们先来调用统一接口。调用接口的URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder,调用统一接口需要以下参数。


看到这些参数,不难理解需要传入什么。那么参数中的sign需要以下规则进行生成。

下面是我生成sign的方法。注意,参数是必填项的必须加进去。sign的生成规则是,除了上除列表中sign字段为所有的参数都可以参与sign的生成且参与生成sign的参数值不为空.

/**
     * @author 李欣桦
     * @date 2014-12-5下午2:29:34
     * @Description:sign签名
     * @param characterEncoding 编码格式
     * @param parameters 请求参数
     * @return
     */
    public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            Object v = entry.getValue();
            if(null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + ConfigUtil.API_KEY);
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

MD5Util工具类中相关的方法

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" };
}

各位需要记住,我们发送给微信服务器的参数是xml格式的string,微信返回来的信息也是xml格式的string。

我们提交的正确数据应该是这样的。

<xml>

< appid>wx2421b1c4370ec43b</appid>

< attach><![CDATA[att1]]></attach>

< body><![CDATA[JSAPI 支付测试]]></body>

< device_info>1000</device_info>

< mch_id>10000100</mch_id>

< nonce_str>b927722419c52622651a871d1d9ed8b2</nonce_str>

< notify_url>http://wxpay.weixin.qq.com/pub_v2/pay/notify.php<;/notify_url>

< out_trade_no>1405713376</out_trade_no>

< spbill_create_ip>127.0.0.1</spbill_create_ip>

< total_fee>1</total_fee>

< trade_type>JSAPI</trade_type>

< sign><![CDATA[3CA89B5870F944736C657979192E1CF4]]></sign>

< /xml>

那么。我们来生成这样的post data

SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", ConfigUtil.APPID);

parameters.put("mch_id", ConfigUtil.MCH_ID);
parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
parameters.put("body", "LEO测试");
parameters.put("out_trade_no", "201412051510");
parameters.put("total_fee", "1");
parameters.put("spbill_create_ip",IpAddressUtil.getIpAddr(request));
parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
parameters.put("trade_type", "JSAPI");
parameters.put("openid", "o7W6yt9DUOBpjEYogs4by1hD_OQE");
String sign = PayCommonUtil.createSign("UTF-8", parameters);
parameters.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(parameters);

PayCommonUtil.getRequestXml(parameters)方法如下:

/**
     * @author 李欣桦
     * @date 2014-12-5下午2:32:05
     * @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();
    }

最后我们要将这些参数以POST方式调用微信统一支付接口:代码如下

String result =CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);

CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);方法如下:

/**
     * 发送https请求
     * @param requestUrl 请求地址
     * @param requestMethod 请求方式(GET、POST)
     * @param outputStr 提交的数据
     * @return 返回微信服务器响应的信息
     */
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);
            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();
            return buffer.toString();
        } catch (ConnectException ce) {
            log.error("连接超时:{}", ce);
        } catch (Exception e) {
            log.error("https请求异常:{}", e);
        }
        return null;
    }

接着,我们来解析微信返回给我们的信息。返回信息是xml格式的String

XMLUtil.工具类如下:

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();
    }
    
}

Map<String, String> map = XMLUtil.doXMLParse(result);//解析微信返回的信息,以Map形式存储便于取值

现在我们可以拿到想要拿的值,那么可以调用微信H5网页端调用支付接口。在微信浏览器里面打开 H5 网页中执行 JS 调起支付。接口输入输出数据格式为 JSON。
注意:WeixinJSBridge 内置对象在其他浏览器中无效;列表中参数名区分大小。

现在我们将返回的值传入到支付jsp页面,在支付jsp页面调用支付接口。

       SortedMap<Object,Object> params = new TreeMap<Object,Object>();
        params.put("appId", "wx0953bae287adfeee");
        params.put("timeStamp", Long.toString(new Date().getTime()));
        params.put("nonceStr", PayCommonUtil.CreateNoncestr());
        params.put("package", "prepay_id="+map.get("prepay_id"));
        params.put("signType", ConfigUtil.SIGN_TYPE);
        String paySign =  PayCommonUtil.createSign("UTF-8", params);
        params.put("packageValue", "prepay_id="+map.get("prepay_id"));    //这里用packageValue是预防package是关键字在js获取值出错
        params.put("paySign", paySign);                                                          //paySign的生成规则和Sign的生成规则一致
        params.put("sendUrl", ConfigUtil.SUCCESS_URL);                               //付款成功后跳转的页面
        String userAgent = request.getHeader("user-agent");
        char agent = userAgent.charAt(userAgent.indexOf("MicroMessenger")+15);
        params.put("agent", new String(new char[]{agent}));//微信版本号,用于前面提到的判断用户手机微信的版本是否是5.0以上版本。
        String json = JSONObject.fromObject(params).toString();

下面是一个简洁的支付页面。代码如下:

[html] view plain copy
  1. <body>  
  2.     <form action="" method="post" >  
  3.         <input type="button" value="确认支付" name="ajaxLoadId" id="test"/>  
  4.     </form>  
  5.     <script type="text/javascript">  
  6.     var basePath = "<%=basePath%>";  
  7.     $("#test").one("click",function(){  
  8.         $.ajax({  
  9.             url:basePath+"config/pay!execute.action"            //<span style="font-family:微软雅黑;">ajax调用微信统一接口获取prepayId</span>  
  10.         }).done(function(data){  
  11.             var obj = eval('(' + data + ')');  
  12.             if(parseInt(obj.agent)<5){  
  13.                 alert("您的微信版本低于5.0无法使用微信支付");  
  14.                 return;  
  15.             }  
  16.             WeixinJSBridge.invoke('getBrandWCPayRequest',{  
  17.                 "appId" : obj.appId,                  //公众号名称,由商户传入  
  18.                 "timeStamp":obj.timeStamp,          //时间戳,自 1970 年以来的秒数  
  19.                 "nonceStr" : obj.nonceStr,         //随机串  
  20.                 "package" : obj.packageValue,      //<span style="font-family:微软雅黑;">商品包信息</span>  
  21.                 "signType" : obj.signType,        //微信签名方式:  
  22.                 "paySign" : obj.paySign           //微信签名  
  23.                 },function(res){      
  24.                     alert(res.err_msg);  
  25.                 if(res.err_msg == "get_brand_wcpay_request:ok" ) {  
  26.                     window.location.href=obj.sendUrl;  
  27.                 }else{  
  28.                     alert("fail");  
  29.                     window.location.href="http://183.45.18.197:8016/wxweb/config/oauth!execute.action";     
  30.                                        //<span style="font-family:微软雅黑;">当失败后,继续跳转该支付页面让用户可以继续付款,贴别注意不能直接调转jsp,</span><span style="font-size:10.5pt">不然会报</span><span style="font-size:12.0pt"> system:access_denied。</span>  
  31.                 }  
  32.             });  
  33.               
  34.         });  
  35.     });  
  36.     </script>  
  37.   </body>  
支付成功后,微信就会调用你填写的notify_url的方法,本人微信支付的开发配置中说明了我的notify_url为http://183.33.212.175:8016/wxweb/config/pay!paySuccess.action

对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略(如 30 分钟共 8 次)定期重新发起通知,尽可能提高通知的成功率,
但微信不保证通知最终能成功。由于存在重新収送后台通知的情况,因此同样的通知可能会多次収送给商户系统。 商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据
进行状态检查和处理之前,要采用数据锁进行幵収控制,以避免凼数重入造成的数据混乱。
判断完成后,我们需要通知微信,我们收到信息了,不然微信就会通过一定的策略定期重新发起通知。
怎么通知微信呢。方法如下。

public void paySuccess() throws Exception{
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        InputStream inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
        outSteam.close();
        inStream.close();
        String result  = new String(outSteam.toByteArray(),"utf-8");//获取微信调用我们notify_url的返回信息
        Map<Object, Object> map = XMLUtil.doXMLParse(result);
        for(Object keyValue : map.keySet()){
            System.out.println(keyValue+"="+map.get(keyValue));
        }
        if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
            //TODO 对数据库的操作
            response.getWriter().write(PayCommonUtil.setXML("SUCCESS", ""));   //告诉微信服务器,我收到信息了,不要在调用回调action了
            System.out.println("-------------"+PayCommonUtil.setXML("SUCCESS", ""));
        }
    }

PayCommonUtil.setXML("SUCCESS", ""))方法如下:

public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code
                + "]]></return_code><return_msg><![CDATA[" + return_msg
                + "]]></return_msg></xml>";

}


上面代码中工具类打包下载地址:http://download.csdn.net/detail/u011160656/8354883

官方文档有少许常见错误解决方法,观众们可自行下载查阅,仍有不懂的可留言。

第一次写这么长的博客,不足之处请留言告之便于本人成长,再次感谢你的阅读。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值