**写在前面:

本人一直“奉行授人以鱼不如授人以渔”,本文主要是起一个引导的作用,注意一些很坑的地方。

微信支付,本人菜鸟花了2天时间弄出来,也算是有点成就感,所以特此做个记录

**

分割线—————————————————————————

正文开始:

先来看看微信官方流程图:

java微信网页支付源码 java微信支付接口开发流程_签名


总结起来就是四个步骤:

  1. 点击支付按钮
  2. 微信拉起支付,输入密码
  3. 完成支付
  4. 通知微信服务器
    下面就是我做测试支付的图:
    点击支付按钮(后台一大堆业务逻辑处理,后面会讲):

    微信拉起支付(这个调用微信自动完成的):

    完成支付,通知微信(这个需要我们自己完成业务逻辑,后面会讲到)

流程就是这样,其实总结出来,我们需要做的就是2步:
流程图中
4:统一下单
5:调用统一下单api
6:生成下单签名
(4 5 6)可以并为一步
11:告知微信通知处理结果

开始编码实现:
查阅文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
获取到统一下单接口:
统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
文档中说明,统一下单必须获取openid,各位客官别急,待我带你们一起查阅官方文档:
https://mp.weixin.qq.com/wiki?action=doc&id=mp1421140842&t=0.003036260317902828#1
这里一大堆说的就是获取openid,文档讲的十分清楚。

先放下prepay_id,我们来说说如何获取open_id(因为没有open_id,我们也没法去获取prepay_id)
获取这个分为4步:

  1. 用户同意授权,获取code
    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE
    我来解释下后面附带参数的意思:
    appid: 用户唯一凭证,就是微信公众号的应用id
    redirect_uri: 这个就是回调url,注意这个url 会附带着 code过去,就是第一步中的code。
    scope: 有两种 snsapi_base 和 snsapi_userinfo 具体请参照文档
    state: 随意填写
    其他参数 照写
  2. 通过code换取网页授权access_token
    请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
    参数说明:
    appid:这个参数使用都是一样的
    secret: 这个参数在位置支付基本配置中可以查看
    code: 第一步获取的code
    grant_type:不变
    请求这个就能返回如下json
{
    "access_token":"ACCESS_TOKEN",    
     "expires_in":7200,    
     "refresh_token":"REFRESH_TOKEN",    
     "openid":"OPENID",    
     "scope":"SCOPE" 
 }

解析上述json字符就可以获取到openid了。各位客官,可以喝杯茶休息休息啦!

接着来:有了openid,开始获取prepay_id
继续文档:
查阅文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
获取到统一下单接口:
统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
我们把那些必须的参数一一列出来,并给大家做出解释:
appid:公众号id
mch_id: 商户号
nonce_str: 随机字符串,长度32以内
sign :签名,最坑的地方,玩微信支付不遇到这个签名错误,都不好意思说自己开发过支付
body:商品描述
out_trade_no: 商户订单号 和随机字符串一样,长度32以内。
total_fee:总价,单位 分
spbill_create_ip : 终端IP
notify_url:回调url 这个就是我们总结出来2步中11的那个步骤,通知微信的处理结果的作用。
trade_type:交易类型,我们是公众号,传 JSAPI 就好了
openid 这里注意,网页支付一定要传这个,我当时就忘记传了,一直导致签名错误。
最后把这些东西组装成xml就好了:
给大家贴代码吧:

String openid = open_id;
        String appid = APP_ID;
        String mch_id = MCH_ID;
        String nonce_str = RandomStringGenerator.getRandomStringByLength(32);
        String bodyDesc = body;
        String out_trade_no = RandomStringGenerator.getRandomNumber(18);
        String total_fee = totalFee;
        String spbill_create_ip = spbillCreateIp;
        String notify_url = notifyUrl;
        String trade_type = "JSAPI";

        Map<String, Object> map = new HashMap<String, Object>();

        map.put("openid", openid);
        map.put("appid", appid);
        map.put("mch_id", mch_id);
        map.put("nonce_str", nonce_str);
        map.put("body", bodyDesc);
        map.put("out_trade_no", out_trade_no);
        map.put("total_fee", total_fee);
        map.put("spbill_create_ip", spbill_create_ip);
        map.put("notify_url", notify_url);
        map.put("trade_type", trade_type);

把需要的参数放到map中去,因为sign这个需要用到签名算法:微信文档中有,给出链接https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
还是贴代码:

public static String getSign(Map<String,Object> map){
        ArrayList<String> list = new ArrayList<String>();
        for(Map.Entry<String,Object> entry:map.entrySet()){
            if(entry.getValue()!=""){
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String [] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < size; i ++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + key;
        Log.error(SignUtil.class, "getSign", result);
        result = MD5.MD5Encode(result).toUpperCase();
        return result;
    }

注意这里用了一个key,这个key非常非常重要,key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
这个key必须参与签名。
把上面参数封装成PrePay对象

public class XmlUtil {
    public static String toXml(PrePay pay){
        XStream stream = new XStream(new Xpp3Driver(new NoNameCoder()));
        stream.alias("xml", pay.getClass());
        return stream.toXML(pay);
    }

最终得到的上传的参数是这样的:以下包含信息参数全用xxxxxx代替

<xml>
  <openid>xxxxxxxx</openid>
  <appid>xxxxxxxx</appid>
  <mch_id>xxxxxxxx</mch_id>
  <nonce_str>xxxxxxxx</nonce_str>
  <body>testpay</body>
  <out_trade_no>xxxxxxxx</out_trade_no>
  <total_fee>1</total_fee>
  <spbill_create_ip>xxxxxxx</spbill_create_ip>
  <notify_url>xxxxxxxxxxx</notify_url>
  <trade_type>JSAPI</trade_type>
  <sign>xxxxxxxxxxx</sign>
</xml>

好了post提交这个参数到 统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
如果签名没有错误,服务器就会返回:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[xxxxxxxx]]></appid>
<mch_id><![CDATA[xxxxxxxxxx]]></mch_id>
<nonce_str><![CDATA[ClB4m3CRcfg9cS7v]]></nonce_str>
<sign><![CDATA[xxxxxxxxxxx]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[xxxxxxxx]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>

大家擦亮亮眼睛,仔细看 prepay_id 好啦!!!解析xml获取prepay_id就算完事了,各位看官又到了喝茶的时间了,请各位前去喝茶。

好啦!差不多回来,开始最重要的一步,去前台调用支付:
以下就是微信需要我们传入的参数了:
appId :这个不解释了,全局都是同一个
timeStamp: 这个时间戳自己传过来就好了
nonceStr : 这个随机字符 自己传过来好了
package : 注意注意注意 这里 prepay_id= 一定要加这个
signType : MD5
paySign :这里把上面的参数按照获取prepay_id 一样的流程去签名就好了!

function onBridgeReady(){
   WeixinJSBridge.invoke(
       'getBrandWCPayRequest', {
           "appId":"wx2421b1c4370ec43b",     //公众号名称,由商户传入     
           "timeStamp":"1395712654",         //时间戳,自1970年以来的秒数     
           "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串     
           "package":"prepay_id=u802345jgfjsdfgsdg888",     
           "signType":"MD5",         //微信签名方式:     
           "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
       },
       function(res){     
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
       }
   ); 
}
if (typeof WeixinJSBridge == "undefined"){
   if( document.addEventListener ){
       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
   }else if (document.attachEvent){
       document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
       document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
   }
}else{
   onBridgeReady();
}

至此微信支付结束。。。
认为结束了???太年轻,微信还要我们对处理结果进行确认呢!!不然之前那个notify_url 不是很鸡肋,白传了么!!看文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
微信返回的是流对象,我们转成map就可以了:

public static Map<String,Object> requestToXml(HttpServletRequest request) throws DocumentException, IOException{
        SAXReader reader = new SAXReader();
        Document doc = reader.read(request.getInputStream());
        Element root = doc.getRootElement();
        Map<String,Object> map = new HashMap<String,Object>();
        List<Element> elements = root.elements();
        for(Element e:elements){
            map.put(e.getName(), e.getData());
        }
        return map;
    }

这里就获取到了微信传给我们的支付包含的所有数据:我这里把他放到map里面了。
(本人做法不推荐,只是用于测试:)

public static String replyWx(HttpServletRequest request) throws DocumentException,
            IOException {
        Map<String, Object> map = XmlUtil.requestToXml(request);
        String xml = "";
        if (map != null
                && "SUCCESS"
                        .equalsIgnoreCase(map.get("return_code").toString())) {
            Map<String, Object> replyMap = new HashMap<String, Object>();
            replyMap.put("return_code", "SUCCESS");
            replyMap.put("return_msg", "OK");
            xml = XmlUtil.mapToXml(replyMap);
        } else {
            Map<String, Object> replyMap = new HashMap<String, Object>();
            replyMap.put("return_code", "FAIL");
            replyMap.put("return_msg", "签名失败");
            xml = XmlUtil.mapToXml(replyMap);
        }
        return xml;
    }

这里我只是简单的返回成功和失败,大家做的时候一定一定要根据微信推荐的做法来,我推荐一个思路吧,先把sign取出来,然后置sign为空,让剩下的参数去签名,得到的签名和sign对比,如果一致,通过,如果不一致,那么sign被篡改,返回失败。
最后把这个xml返回给wx就好了!!
至此,支付结束,谢谢大家拜读!!!!