微支付jsapi巨坑 微支付 jsapi java

来源:互联网 发布:linux mv命令 编辑:程序博客网 时间:2024/05/16 16:04
1.在微信网站上的设置
(你必须是服务号并且申请各种认证通过后才能往下走,否则请回炉!)
首先请登录https://mp.weixin.qq.com
微信公众平台,进行如下配置,谢谢。


1.1微信支付-开发配置

1.2公众号设置-功能设置

1.3开发者中心-配置项-接口权限

1.4 微信支付-商户平台
(坑1:此处为最大的巨坑,要不是大神告诉我要配置这里,我死也想不出这里也需要配置,这里不配置的话,后面的预订单提交会一直报错,并且那个错也许你永远都不知道原因。。。阿西)
请登录:https://pay.weixin.qq.com 微信支付-商户平台
将这个秘钥设置成和AppSecret(应用密钥)设置成一样的,just相信我。

2.调用网页微支付
各位童鞋,经过上面的配置,大家可以进入第二阶段,its time to coding.
因为我们是要从网页上调用微支付么,对吧,那么我们就要通过一个http get请求来告诉微信服务器,我们要请求啦。肿么写?



2.1 第一个http请求,我们要支付!!!
https://open.weixin.qq.com/connect/oauth2/authorize?appid=11111111&redirect_uri=http%3A%2F%2Ftest.aaa.com/wxtest%2Fwxtest&response_type=code&scope=snsapi_base&state=12345#wechat_redirect

您第一眼看到这坨,您会想,嗯。。。这尼玛是什么?
简单的说,这坨就是你要告诉微信服务器你要进行支付的一个http get请求的url,当然了这只是第一步,你需要给他问号传参,下面请允许我来解释一下这几个参数:
appid:这就不用说了,你应该把它弄成全局变量,很多地方要用。
redirect_uri:跳转地址,写成你自己url,切记这里的写法要与上面的配置在一个域名里,否则这步就够你卡一阵子的。你可以配置成/xxx.jsp,xxx.do,xxx.action,啥都可以,我这里是用了spring 的restful的url格式。(坑2:你要把你的url进行encode转码,切记你的url是带http://的,例如我的是http://test.aaaa.com/wxtest,转码之后是http%3A%2F%2Ftest.aaa.com/wxtest%2Fwxtest。这不是演习!)
response_type:code,固定写法,不要问为什么。
scope:snsapi_base固定写法,不要问为什么。
state:state是你自己的业务id,随便写啥都行,在后面会接到你自己传的参数,名字就叫state,别改。后面的照抄(#wechat_redirect),别乱写。

这坨url只能在微信浏览器中使用,因此我们的测试方法是把这个url用微信随便发给一个人,然后你自己在聊天框里点击这个url。
如果上面的配置都木有错误的话,那么按照套路出牌,微信服务器会回调你的url,说白了就是请求你的服务器,然后给你传递2个参数:code和state。如果没有拿到这2个参数或者没有被请求到,反省吧,凡人,前面的配置你肯定是配错了。好的,休息一下,我们进入下一小节。

(什么?你说你不会写如何提供web服务让微信访问你的代码?
就是controller,action里的方法呗,或者一个jsp,servlet也行呀。
什么?你还是不懂?
凡人,你需要立即马上放下手头的工作,去买一本java web速成。。。)


2.2 第一次微信来请求你,还不错。
好的,欢迎回来,前文书说到我们会拿到2个参数,下面是2个参数的含义:
state:你自定义的业务id,前面提到过。
code:code是微信返回的用户auth校验后的code码,后面要用到。
你接到参数的时候需要做判断:如果code是空,则不能往下继续进行,因为可能用户鉴权失败了。


2.3 第一个签名(config配置签名),坑王之王。
注意!一大波大坑正在靠近。你的脑海里要有一个印象,在亚瑞特巨坑中,呃,不是,在微信jsapi巨坑中,所有的签名,都是手机中的战斗机。坑王之王。只要是带签名的地方,我们基本都被卡住很长时间,各种试啊,你懂我的意思。

那么好,你拿到的那个code和state,是要干什么用?答案是你要做第一个config签名(配置签名),什么是配置签名?
简单的说,微信服务器为了防止第三方程序or个人进行抓包篡改请求参数,就是做坏事,的一种防范措施。
签名里放的是你要给微信服务器请求的参数以及加密,再使用ticket,把他们揉在一起,捏出来一个签名,发给微信服务器。

不太懂对吗?别怕,跟着杨老师一步一步带你升仙。

这样,我先告诉你,这个config签名里都需要什么吧,然后我在下面一步一步带你升仙,肿么样?
这个签名里还需要nonce_str32位随机码,timestamp生成时间,url微支付页面url,jsapiTicket门票。就这四样任务物品,得到后就可以交任务,拿经验了。

其中jsapiTicket是比较坑的,获取jsapiTicket需要accessToken,而获取accessToken又需要appid和AppSecret(应用密钥)。

WTF?我们继续。。。

2.3.1 获取accessToken
简单的说你要给微信的一个url发http get请求,参数是appid和AppSecret(应用秘钥),
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+WxConfig.getAppID()+"&secret="+WxConfig.getSecret(); 
这是我的代码,当然了我把appid和AppSecret(应用密钥)弄成了全局变量。
(坑3:这个请求不要试图在页面上写,不要用jquery或者ajax,会有跨域的问题,即使你解决了跨域的问题,也会有拿不到返回值的问题。乖乖的拿到java代码里去写,在java代码里写一个get请求,并且用json来接返回值,不会写的话,QQ里管我要。)
请求后,微信服务器会返回给你一个json,没错,你没听错,的确是json。
json里取"access_token",并且要把这个值全局缓存起来,7200秒吧。频繁的请求微信服务器会把你的账号封掉。

什么?你说你不会取json里的值?
凡人。。。。。

2.3.2 获取jsapiTicket
好的,我们现在已经有了accessToken,下一步就是去取jsapiTicket了哟。
同上,你需要给微信的另一个url发http get请求,参数是我们刚才拿到的access_token,
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+accessToken+"&type=jsapi 
返回值依然是json,你需要在返回的json中取"ticket",并缓存7200秒。

2.3.3 获取url
这里的url是微支付页面url,也是你之前在2.1里配置的那个url,好了,坑来了。
(坑4:这个url必须是带"http://",并且不用encode转码,记住,是不能转码!并且必须带上code和state参数,否则出错!当时就因为没有传code和state试了多少遍,阿西!
例如:http://test.aaa.com/wxtest?code="+code+"&state="+state;
code和state是微信给你传的,还记得吗?

2.3.4 获取nonce_str+timestamp
String nonce_str = UUID.randomUUID().toString().replaceAll("-", "");//32位随机码
String timestamp = Long.toString(System.currentTimeMillis() / 1000);//生成时间
能看懂吧?

2.3.5 生成第一个config配置签名
有了上面的4样任务物品,咱们就可以交任务,拿经验了。
(坑5:下面的写法顺序也有严格的要求,照我的抄吧,至少我的写法是最后支付成功了。)
下面我们就来把这4个东西揉在一块,捏出一个签名出来,我直接放java代码了,有疑问请加我QQ。

/**
* @category 第一个签名,前台的config配置签名 
* @param code 
* @param state 
* @return 返回签名map,前台要用 
*/ 
public static Map<String, Object> configSign(String code,String state) { 
String nonce_str = UUID.randomUUID().toString().replaceAll("-", "");//32位随机码 
String timestamp = Long.toString(System.currentTimeMillis() / 1000);//生成时间 
String url = WxConfig.getUrl()+"/wxtest?code="+code+"&state="+state;//微支付页面url 
String jsapiTicket; 
try { 
jsapiTicket = WxpayUtil.getJsapiTicket(); 
//注意这里参数名必须全部小写,且必须有序 
String string1 = "jsapi_ticket=" + jsapiTicket + 
"&noncestr=" + nonce_str + 
"&timestamp=" + timestamp + 
"&url=" + url; 

//加密算法 
MessageDigest crypt = MessageDigest.getInstance("SHA-1"); 
crypt.reset(); 
crypt.update(string1.getBytes("UTF-8")); 
String signature = byteToHex(crypt.digest());//生成签名 

Map<String, Object> returnMap = new HashMap<String, Object>(); 
returnMap.put("url", url); 
returnMap.put("jsapi_ticket", jsapiTicket); 
returnMap.put("nonceStr", nonce_str); 
returnMap.put("timestamp", timestamp); 
returnMap.put("signature", signature); //这是万恶的config配置签名,就是他!!!
return returnMap; 
} catch (Exception e) { 
e.printStackTrace(); 

return null; 
}
我的方法返回了一个MAP,MAP里放的是config签名信息,我们姑且叫他config签名MAP,我在前台页面中需要拿这个MAP进行下一步操作。

我的页面中是这么写的,当然你可以有你更好的写法,我并没有对代码进行优化和封装:
<%
String code = request.getParameter("code");//这个code码在下面会用到,到时不要问我code码从哪里来,他的故乡在远方。。。
String state = request.getParameter("state");//报名ID 

//第一个签名,用于第一个JS配置 
Map<String,Object> signatureMap = WxpaySign.configSign(code,state); 
%>

我,尼玛,这都是神马跟神马?
这只是一个开始,休息一会,抽根烟,放松放松,我们接着来。



2.4 页面中配置config对象以及ready+error监听方法
前文书说到,我们拿到了万恶的config配置签名,以及从后台返回了一个config签名MAP,下面,我们要把这个MAP里的东西用起来,来配置天杀的config对象。


2.4.1 创建一个jsp页面,并引入微信的js包+jquery包
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script type="text/javascript" src="${ctx}/js/jquery/jquery-1.9.1.min.js"></script>

什么?你不用jquery?厉害!

2.4.2 配置config对象
<script>
 wx.config({
    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来
    appId: '<%=WxConfig.getAppID()%>', // 必填,公众号的唯一标识 
    timestamp: '<%=signatureMap.get("timestamp")%>', // 必填,生成签名的时间戳 
    nonceStr: '<%=signatureMap.get("nonceStr")%>', // 必填,生成签名的随机串 
    signature: '<%=signatureMap.get("signature")%>',// 必填,签名,见附录1 
    sApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 
  });
(坑6,记住全部要带单引号!!!)
signatureMap就是我上面从后台返回的config签名MAP,不用客气,从里面拿东西往config里放吧。


2.4.3 配置error监听
<script> 
    wx.error(function(res){ 
        for(i in res){ 
            alert(i+":"+res[i]); 
        
        alert("微支付调用失败,请截图给管理人员,谢谢"); 
        window.close(); 
    });
你在这里可以做你想要做的任何逻辑。

2.4.4 配置ready监听
<script> 
    wx.ready(function(){ 
        alert("hello world..."); 
    });
(坑7:如果上述的配置or写法有一点错误,那么页面上就会触发error监听,坑的是触发完error监听,依然会进入ready监听,大家要写好自己的逻辑。触发error监听后,你能看到微信服务器给你返回的错误提示信息,OK,但凡触发了错误监听,您就别往下走了,老老实实的回去反思吧。)
呃。。。如果上面的配置or写法完全100%没问你的话,会打出hello world,前提是没有进error监听方法的情况下,好!!!
第一场战役胜利了。HOLY SHIT!!!
庆祝一下!!!你可以试想当时没有人指点的情况下,要摸清这么多坑,是多么恶心的事情。
你下面几乎全部的写法都要在ready监听方法里实现,OK,我们进入下一章,万恶的预订单提交。



2.5 万恶的预订单提交
该配置的配置完啦,我们该准备提交订单啦,等等!先要有一个预订单,就是告诉微信服务器你准备要提交订单了,我,尼玛。。。

预订单说白了就是一个表单,微信官方叫统一下单接口,你要给微信服务器一个http post请求,参数是xml(尼玛刚才不还是json呢么,肿么现在是xml了?),这里我是在java里写的,在ready里用ajax调用的。您随意。

我先大概说一下这个预订单提交,你需要拿到通过code码(之前有)拿到openid。然后提交一坨参数给微信,是xml格式,还包括万恶的预订单签名,在一切木有问题的情况下,微信会给你返回一个xml,你通过解析后能拿到prepay_id,传说中的预订单ID,有了这个ID我们才好去打下一关的boss对不对?

2.5.1 取得openid
简单的说你要给微信的一个url再发http get请求(恩?我为什么要说一个再字),参数是appid,AppSecret(应用秘钥),code码。
https://api.weixin.qq.com/sns/oauth2/access_token?appid="+WxConfig.getAppID()+"&secret="+WxConfig.getSecret()+"&code="+code+"&grant_type=authorization_code 
这个url给你返回的是一个json对象,木有错,就是一会xml一会json玩死你。
在返回的json对象中取"openid"

什么?你问我openid是什么?
对不起,我也不知道,用,就对了。

2.5.2 构造预订单参数
像我刚才说的,预订单需要一个xml格式的参数,那么好,我们现在来搞这个xml。
我这里是用一个MAP里放一坨参数,然后再用代码转成xml的,当然,你也可以有你自己的实现方式。
直接粘代码了啊:

(坑7:Map的顺序一定不能错,不然会报签名错误,GOD!)
//此map用于生成签名和XML 
Map<String,Object> paramSignMap = new LinkedHashMap();//按顺序来的 
paramSignMap.put("appid", WxConfig.getAppID()); 
paramSignMap.put("attach", "aaa");//
附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
paramSignMap.put("body", "逐风者祝福之剑"); //支付的时候给用户看的商品描述,直接写中文,不用转义。
paramSignMap.put("mch_id", WxConfig.getMchID());//商户号 
paramSignMap.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));//随机码
paramSignMap.put("notify_url", WxConfig.getUrl()+"/wxnotify");//监听地址,支付成功或失败微信会回调这个url通知你。
paramSignMap.put("openid",openId); //之前拿到的oepnid
paramSignMap.put("out_trade_no", "1231231231231");//自己的订单号,这里跟你的业务逻辑要挂钩了。
paramSignMap.put("spbill_create_ip", request.getRemoteAddr());//请求者IP 
paramSignMap.put("total_fee", signMap.get("money")+"00");//钱数 
paramSignMap.put("trade_type", "JSAPI");//固定写法 
paramSignMap.put("sign", WxpaySign.preFormSign(paramSignMap));//生成预订单签名

(坑8:paramSignMap的notify_url,必须是带http://的,例如:http://test.aaa.com/wxnotify
(坑9:paramSignMap的total_fee,后面要加00,因为你传过去1,默认是1分钱
(坑10:paramSignMap的sign,详见2.5.3,一点都不能错,不然就会报天杀的"签名错误"

2.5.3 生成预订单签名
好的,大家看到了刚才的这段代码:
paramSignMap.put("sign", WxpaySign.preFormSign(paramSignMap));//生成预表单签名
preFormSign是我自己封的构造预订单签名的方法,大概的逻辑说一下。
paramSignMap里除了sign以外的所有参数都累加,并且使用MD5加密后再加上一个秘钥的大写,就形成了预订单签名。
(坑11:这个秘钥就是用户必须要在商户平台去设置的那个,同坑1,我们为了方便,把这个秘钥跟AppSecret(应用密钥)设置成一个了。这个地方是坑王之王,坑断肠。

话不多说,粘代码:
/** 
* @category 预提交表单签名 
* @param url 
* @return 
*/ 
public static String preFormSign(Map<String,Object> paramSignMap) { 
StringBuffer tempStr = new StringBuffer(1024); 

for (String key : paramSignMap.keySet()) { 
tempStr.append(key+"="+paramSignMap.get(key)+"&"); 

System.out.println(tempStr.toString()); 
String returnStr = MD5.GetMD5Code(tempStr.substring(0,tempStr.length()-1)+"&key="+WxConfig.getSecret()).toUpperCase();
logger.info("preFormSign==========================================>"+returnStr); 
return returnStr; 
}

(坑12,此处的MD5校验,完全复制我的代码吧,之前我们自己的MD5交易,微信服务器不认!
代码如下:
public class MD5 { 
// 全局数组 
private final static String[] strDigits = { "0", "1", "2", "3", "4", "5", 
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; 

public MD5() { 


// 返回形式为数字跟字符串 
private static String byteToArrayString(byte bByte) { 
int iRet = bByte; 
// System.out.println("iRet="+iRet); 
if (iRet < 0) { 
iRet += 256; 

int iD1 = iRet / 16; 
int iD2 = iRet % 16; 
return strDigits[iD1] + strDigits[iD2]; 


// 返回形式只为数字 
private static String byteToNum(byte bByte) { 
int iRet = bByte; 
System.out.println("iRet1=" + iRet); 
if (iRet < 0) { 
iRet += 256; 

return String.valueOf(iRet); 


// 转换字节数组为16进制字串 
private static String byteToString(byte[] bByte) { 
StringBuffer sBuffer = new StringBuffer(); 
for (int i = 0; i < bByte.length; i++) { 
sBuffer.append(byteToArrayString(bByte[i])); 

return sBuffer.toString(); 


public static String GetMD5Code(String strObj) { 
String resultString = null; 
try { 
resultString = new String(strObj); 
MessageDigest md = MessageDigest.getInstance("MD5"); 
// md.digest() 该函数返回值为存放哈希值结果的byte数组 
resultString = byteToString(md.digest(strObj.getBytes())); 
} catch (NoSuchAlgorithmException ex) { 
ex.printStackTrace(); 

return resultString; 


public static void main(String[] args) { 
System.out.println(MD5.GetMD5Code("qqqqqq")); 

}


2.5.4 用一坨参数构造xml
书接上文2.5.2,我们已经有了paramSignMap,下面该干什么啦?对!我们应该把paramSignMap转成xml,然后一脚踢给微信服务器,抽出她裤衩里的猴皮筋做个弹弓打他们家玻璃。

粘代码:
String xmlInfo = XMLUtil.getRequestXml(paramSignMap);//生成xml,这个xml要post请求到微信的服务端

/** 
* @category 生成请求xml,用于微支付 
* @param parameters 
* @return 
*/ 
public static String getRequestXml(Map<String,Object> parameters){
    StringBuffer sb = new StringBuffer(1024);
    sb.append("<xml>"); 

    for (String key : parameters.keySet()) { 
        System.out.println("key= "+ key + " and value= " + parameters.get(key));
        String k = key; 
        String v = parameters.get(key)+""; 
        sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">"); 
    } 
    sb.append("</xml>"); 
    return sb.toString(); 
}
(坑13:我们当时测试的时候,有的加了CDATA,有的没加,各种问题,后来都加上CDATA就好了。不管了,这么用吧。什么?你不知道什么是CDATA?恩,做得好!)

2.5.5 提交xml给微信服务器,拿prepay_id
准备工作都做好了,下面可以砸他们家玻璃了,下面的写法也是我从网上找的,至少他是好用的对吧?
(坑14:这里的提交方式你也可以自己写,不过切记编码集是UTF-8,且是POST提交)
提交成功后,需要拿到返回的prepay_id,他给你返回的也是xml,需要解析。
另外此处你可以跟你的业务挂钩了,进行你的表操作。

粘代码:
//提交XML给微信服务器,微信服务器返回PREID 
URL url = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder");
URLConnection con = url.openConnection(); 
con.setDoOutput(true); // POST方式 
out = new OutputStreamWriter(con.getOutputStream(), "UTF-8"); 
out.write(xmlInfo); 
if(out!=null){ 
out.flush(); 
out.close(); 


StringBuffer returnStr = new StringBuffer(1024); 
String sCurrentLine = ""; 
InputStream l_urlStream = con.getInputStream(); 
BufferedReader l_reader = new BufferedReader(new InputStreamReader(l_urlStream));
while ((sCurrentLine = l_reader.readLine()) != null) { 
returnStr.append(sCurrentLine); 


// System.out.println("xmlInfo=" + xmlInfo); 
// System.out.println("-------------预订单返回--------------"+returnStr.toString()); 

Map<String, String> returnMap = XMLUtil.doXMLParse(returnStr.toString());//解析微信返回的信息,以Map形式存储便于取值
// System.out.println("最后的结果111----->"+returnMap.get("return_code")); 
// System.out.println("最后的结果222----->"+returnMap.get("return_msg")); 
if("SUCCESS".equals(returnMap.get("return_code"))){

其中doXMLParse的代码:
/** 
* 解析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; 
}
(坑15:关于xml解析等等部分最好是按照我的代码写,因为不保证别的代码是否有问题。)

需要判断返回值:
if("SUCCESS".equals(returnMap.get("return_code"))){
    //自己的业务 
    return 返回给前台prepay_id以及你自己的业务id;
}else{ 
    return 错误信息;
}
艾玛,这里你只要能拿到prepay_id,恭喜,一个里程碑似的胜利,然后我把这个prepay_id返回到前台(我们依然是在ready函数里,还记得吗?)。


2.6 快要胜利了,支付订单提交
我知道你现在的感受,我很同情你,但是,坚持,坚持。。。
知道你现在也很痛苦,但是你想想我们淌水的时候那种艰辛,你换位思考一下,就释然了。
书接上文2.5.5,我们已经拿到了prepay_id了对吧,好,我们要用这个prepay_id来干嘛?
对!我们要来拿prepay_id 来做订单提交的签名呀。

我从前台给传过来了timestamp,prepay_id,nonceStr,其中timestamp和nonceStr是从之前的signatureMap里get出来的,我真的懒得再生成了,代码如下:


/** 
* @category 真实表单签名生成 
* @param request 
* @param response 
* @return String 
*/ 
@ResponseBody 
@RequestMapping("getRealFormSignature") 
public JsonMessage getRealFormSignature(HttpServletRequest request, String prepay_id,String timestamp,String nonceStr) throws Exception{
Map<String,Object> readFormParamMap = new HashMap(); 
readFormParamMap.put("appId",WxConfig.getAppID()); 
readFormParamMap.put("timeStamp",timestamp); 
readFormParamMap.put("nonceStr",nonceStr); 
readFormParamMap.put("package","prepay_id="+prepay_id); 
readFormParamMap.put("signType","MD5"); 

String realFormSign = WxpaySign.getSign(readFormParamMap); 
return 把签名返回给前台
}

WxpaySign.getSign代码:
//官方的代码 
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=" + WxConfig.getKey(); 
//Util.log("Sign Before MD5:" + result); 
result = MD5Util.MD5Encode(result).toUpperCase(); 
//Util.log("Sign Result:" + result); 
return result; 
}
(坑15:这个getSign是官方的通用签名写法,必须这么写,你自己写的一定会出错。)

拿到支付订单签名了吧,下面开始真正的提交支付订单:
需要几个参数:
写法如下:timestamp,nonceStr,package,signType。就照着我的写法就可以。

//提交支付订单
wx.chooseWXPay({ 
timestamp: '<%=signatureMap.get("timestamp")%>', 
nonceStr: '<%=signatureMap.get("nonceStr")%>', 
package: 'prepay_id='+prepay_id, 
signType: 'MD5',//SHA1 
paySign: realFormSignature, 
success:function(res1) { 
//alert(res1); 

//修改交易状态,这是我自己的逻辑,你换成你的。就是需要更新你的逻辑,告诉自己的数据库,交易成功了。
$.ajax({ 
type:"POST", //post提交方式默认是get 
dataType:'json', 
url:'${ctx}/wxPaySaveSuccess', 
data : { 
key_id : payLogUUID, //这是我自己的业务ID
trade_status :'TRADE_SUCCESS'//我自己的业务参数 
}, 
error:function(data) {// 设置表单提交出错 
alert('系统出现异常,请联系管理员'); 
}, 
success:function(result) {//提交成功 
if(result.success){ 
alert("哇!成功啦,快去领取您的号码牌吧~"); 
 window.location.href="${ctx}/sign/phoneSignSuccess/<%=state%>"; //跳转到成功页面
}else{ 
alert(result.msg); 
alert("更新交易记录失败!"); 


}); 
},fail:function(res) { 
//alert(res); 
alert("本次支付失败,请联系客服人员,谢谢您的支持与理解。"); 

});

2.7 支付成功后的闭环回调
微支付会有一个异步的通知给你,告诉你支付成功了,你还需要给他返一个成功标识,不然他还会不听的报警。
这个异步通知的url是在2.5.2配置的notify_url。

(坑16:返回给微支付的必须是一个xml)
写法如下,我的是springmvc的controller,不过都大同小异了:
/**
* @category 监听通知,微信服务器调用闭环 
* @param request 
* @param response 
* @return String 
*/ 
@RequestMapping("wxnotify") 
public void wxnotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
System.out.println("微支付回调.1111111111111111111111111111111..."); 
InputStream inputStream = null; 
try { 
// 解析结果存储在HashMap 
Map<String, String> map = new HashMap<String, String>(); 
inputStream = request.getInputStream(); 
// 读取输入流 
SAXReader reader = new SAXReader(); 
Document document = reader.read(inputStream); 
// 得到xml根元素 
Element root = document.getRootElement(); 
// 得到根元素的所有子节点 
List<Element> elementList = root.elements(); 

// 遍历所有子节点 
for (Element e : elementList){ 
map.put(e.getName(), e.getText()); 


//遍历MAP 
for (String key : map.keySet()) { 
System.out.println("key= "+ key + " and value= " + map.get(key)); 


if(map!=null&&"SUCCESS".equals(map.get("return_code"))){ 
Map logMap = tPayLogService.findOneByUUID(map.get("out_trade_no")); 
if(logMap!=null&&"".equals(logMap.get("trade_status"))){//如果已经状态是成功则无视,否则UPDATE一下状态
Map paramMap = new HashMap(); 
logMap.put("trade_status", "TRADE_SUCCESS"); 
Map signMap = signService.findOneByKeyId(logMap.get("sign_id")); 
Map userMap = tClientService.findOneByKeyId(signMap.get("client_id")); 
Map activityMap = tActivityService.findOneByKeyId(signMap.get("activity_id")); 

paramMap.put("social_type", userMap.get("social_type")); 
paramMap.put("price_model", signMap.get("price_model")); 

//支付成功 
signMap.put("s_status", 2);//状态(0:免费;1:未付费;2:已付费) 
signMap.put("sign_num", signService.findMaxSignNum(paramMap, activityMap)); 
signService.saveLogAndSignTransaction(logMap,signMap); 

Map<String,Object> paramSignMap = new LinkedHashMap();//按顺序来的 
paramSignMap.put("return_code","SUCCESS"); 
// paramSignMap.put("return_msg",); 

String xmlInfo = XMLUtil.getRequestXml(paramSignMap);//生成xml,这个xml要post请求到微信的服务端 
PrintWriter out = null; 
try { 
// 转码 
// response.setContentType("text/html;charset=UTF-8"); 
out = response.getWriter(); 
out.write(xmlInfo); 
out.flush(); 
} catch (final IOException e) { 
e.printStackTrace(); 
} finally { 
if (out != null) { 
out.close(); 




} catch (Exception e) { 
e.printStackTrace(); 
}finally{ 
if(inputStream!=null){ 
// 释放资源 
inputStream.close(); 
inputStream = null; 



}

最后,我只能以一句“GOOD LUCK”来做结束。
有技术交流请加我的QQ。

下面附上我全部的页面代码:
wxtest.jsp:


<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/jsp/common/taglib.jsp"%> 
<%@ page import="com.mingyisoft.bean.wxpay.WxpayUtil"%> 
<%@ page import="com.mingyisoft.bean.wxpay.WxConfig"%> 
<%@ page import="com.mingyisoft.bean.wxpay.WxpaySign"%> 

<%@ page import="java.util.Map"%> 
<html> 
<head> 
<meta charset="utf-8"> 

<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script type="text/javascript" src="${ctx}/js/jquery/jquery-1.9.1.min.js"></script>

<script> 
<% 
String code = request.getParameter("code");//页面获取的code码 
String state = request.getParameter("state");//报名ID 

//第一个签名,用于第一个JS配置 
Map<String,Object> signatureMap = WxpaySign.configSign(code,state); 
%> 

wx.config({ 
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '<%=WxConfig.getAppID()%>', // 必填,公众号的唯一标识 
timestamp: '<%=signatureMap.get("timestamp")%>', // 必填,生成签名的时间戳 
nonceStr: '<%=signatureMap.get("nonceStr")%>', // 必填,生成签名的随机串 
signature: '<%=signatureMap.get("signature")%>',// 必填,签名,见附录1 
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 
}); 

wx.error(function(res){ 
for(i in res){ 
alert(i+":"+res[i]); 

// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
alert("微支付调用失败,请截图给管理人员,谢谢"); 
window.close(); 
}); 

// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.ready(function(){ 
//调用统一下单接口 
$.ajax({ 
type:"POST", //post提交方式默认是get 
dataType:'json', 
url:basePath+'/wxpostpreform', 
data : { 
openId :'<%=WxpayUtil.getOpenid(code)%>', 
state : '<%=state%>' 
},error:function(data) {// 设置表单提交出错 
alert('系统出现异常,请联系管理员'); 
},success:function(result) {//提交成功 
if(result.success){ 
var prepay_id = result.msg;//这个就是预订单ID 
var payLogUUID = result.data;//自己的业务逻辑ID 

//alert("预订单编号===>"+prepay_id); 

$.ajax({ 
type:"POST", //post提交方式默认是get 
dataType:'json', 
url:basePath+'/getRealFormSignature', 
data : { 
prepay_id :prepay_id, 
timestamp :'<%=signatureMap.get("timestamp")%>', 
nonceStr :'<%=signatureMap.get("nonceStr")%>' 
},error:function(data) {// 设置表单提交出错 
alert('系统出现异常,请联系管理员'); 
},success:function(result3) {//提交成功 
if(result3.success){ 
var realFormSignature = result3.msg; 
//alert("最后订单的签名===>"+result3.msg); 

//提交支付订单 
wx.chooseWXPay({ 
timestamp: '<%=signatureMap.get("timestamp")%>', 
nonceStr: '<%=signatureMap.get("nonceStr")%>', 
package: 'prepay_id='+prepay_id, 
signType: 'MD5',//SHA1 
paySign: realFormSignature, 
success:function(res1) { 
//alert(res1); 

//修改交易状态 
$.ajax({ 
type:"POST", //post提交方式默认是get 
dataType:'json', 
url:'${ctx}/wxPaySaveSuccess', 
data : { 
key_id : payLogUUID, 
trade_status :'TRADE_SUCCESS'//参数 
}, 
error:function(data) {// 设置表单提交出错 
alert('系统出现异常,请联系管理员'); 
}, 
success:function(result) {//提交成功 
if(result.success){ 
alert("哇!成功啦,快去领取您的号码牌吧~"); 
window.location.href="${ctx}/sign/phoneSignSuccess/<%=state%>"; 
}else{ 
alert(result.msg); 
alert("更新交易记录失败!"); 


}); 
},fail:function(res) { 
//alert(res); 
alert("本次支付失败,请联系客服人员,谢谢您的支持与理解。"); 

}); 

}else{ 
alert(result3.msg); 


}); 
}else{ 
alert(result.msg); 


}); 
}); 
</script> 
</head> 
<body> 
<h1>系统正在提交订单,请勿跳转页面。</h1> 
</body> 
</html>
0 0
原创粉丝点击