lekuhu 2015-10-16
功能简介
微信支付,是基于微信客户端提供的支付服务功能。同时向商户提供销售经营分析、账户和资金管理的技术支持。用户通过扫描二维码、点击图文消息进入商品页面购买等多种方式调起微信支付模块完成支付。
目前微信支持公众号内支付。其中支付方式,可以分为JSAPI支付、Native(原生)支付。商户可以结合业务场景,自主选择支付形式。
公众号支付有2种方式:
1.JSAPI支付:是指用户打开图文消息或者扫描二维码,在微信内置浏览器打开网页进行的支付。商户网页前端通过使用微信提供的JSAPI,调用微信支付模块。这种方式,适合需要在商户网页进行选购下单的购买流程。
2.Native(原生)支付:是指商户组成符合Native(原生)支付规则的URL链接,用户可通过点击该链接或者扫描对应的二维码直接进入微信支付模块(微信客户端界面),即可进行支付。这种方式,适合无需选购直接支付的购买流程。
以上两种支付方式,最大的差别在于是否需要经过网页调起支付。下面我们要讲的是基于微信内置浏览器的JSAPI支付:
JSAPI支付接口(getBrandWCPayRequest)
微信JSAPI只能在微信内置浏览器中使用,其他浏览器调用无效。微信提供getBrandWCPayRequest接口供商户前端网页调用,调用之前微信会鉴定商户支付权限,若商户具有调起支付的权限,则将开始支付流程。(接口需要注意:所有传入参数都是字符串类型!)
首先,要明确微信支付开发的一些准备工作,参考腾讯帮助文档:http://kf.qq.com/faq/120911VrYVrA150905zeYjMZ.html
1.注册微信公众号(注意必须是服务号),并通过微信认证(认证费:300元/次)。
微信认证审核时间约7个工作日左右
2.登录公众平台,提交资料申请微信支付(资料审核时间为3~7个工作日)。申请成功后,就可以进行支付接口的设计和开发了。参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
3.开户成功,登录商户平台进行验证,在线签署协议(平台帐户和密码请查看收到的开户邮件,验证款项(随机金额)请查收你的结算帐户)。平台账户默认是商户号@商户号,比如你的商户号是1226265502,那么你的商户平台帐号默认是:1226265502@1226265502。
成功接入微信支付后,我们着手开发支付接口。需要用到的相关参数如下:
/**
*服务号相关信息
*/
publicfinalstaticStringAPPid="";//服务号的应用号,已wx开头
publicfinalstaticStringAPP_SECRECT="";//服务号的应用密码
publicfinalstaticStringMCH_id="";//商户号
publicfinalstaticStringAPI_KEY="";//API密钥
publicfinalstaticStringSIGN_TYPE="MD5";//签名加密方式
//微信支付统一接口的回调action
publicfinalstaticStringNOTIFY_URL="";
//微信支付成功支付后跳转的地址
publicfinalstaticStringSUCCESS_URL="";
/**
*微信支付接口地址
*/
//微信支付统一接口(POST)
publicfinalstaticStringUNIFIED_ORDER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
其中,APPID、APP_SECRECT,在登录微信公众平台(https://mp.weixin.qq.com)-开发者中心页面
MCH_ID在微信公众平台-微信支付页面
API_KEY需要登录微信商户平台(https://pay.weixin.qq.com)-账户设置-api安全-设置密钥。(注意密钥设置成功后,设置密钥按钮不变,而且没有查询密钥的地方,这一点很不人性化。一般情况下密钥设置成功10分钟后自动生效)
正式进入开发阶段:
1.OAuth2.0授权,获取code参数(需要在微信公众平台-开发者中心-网页授权获取用户基本信息处添加“授权回调页面域名”)
JSAPI支付前需要调用登录授权接口获取到用户的Openid。所以需要做一次授权,这次授权是不弹出确认框的。其实质就是在用户访问你的网站微信支付页面(例如:http://www.abc.cn/wxpay/jspay)时,跳转到
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1111111111&redirect_uri=http://www.abc.cn/wxpay/jspay&response_type=code&scope=snsapi_base&state=123#wechat_redirect
以上链接重定向到http://www.abc.cn/wxpay/jspay,返回code和state参数,以此来获得code参数,并根据code来获得授权access_token及openid
2.OAuth2.0授权,获取openid参数
StringURL="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ConfigUtil.APPID+"&secret="+ConfigUtil.APP_SECRECT+"&code="+code+"&grant_type=authorization_code";
Map<String,Object>dataMap=newHashMap<String,Object>();
Stringtemp=HttpConnect.getInstance().doGetStr(URL);//返回json格式字符串
JSONObjectjsonObj=JSONObject.fromObject(temp);
if(jsonObj.containsKey("errcode")){
System.out.println(temp);
}
openId=jsonObj.getString("openid");
这一步的最终结果就是获得了当前用户的openid
3.调用统一支付接口
统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返回预支付订单号的接口,目前微信支付所有场景均使用这一接口。
SortedMap<Object,Object>parameters=newTreeMap<Object,Object>();
parameters.put("appid",ConfigUtil.APPID);
parameters.put("mch_id",ConfigUtil.MCH_ID);
parameters.put("nonce_str",PayCommonUtil.CreateNoncestr());
parameters.put("body","订单:"+goodsOrder.orderNo);
parameters.put("out_trade_no",goodsOrder.orderNo);
parameters.put("total_fee",total_fee);
parameters.put("spbill_create_ip",IPUtils.getIpAddr(request));
parameters.put("notify_url",ConfigUtil.NOTIFY_URL);
parameters.put("trade_type","JSAPI");
parameters.put("openid",openId);//oauth2认证后得到的openid
Stringsign=PayCommonUtil.createSign("UTF-8",parameters);//必须在商户平台设置api密钥,否则签名无效
parameters.put("sign",sign);
StringrequestXML=PayCommonUtil.getRequestXml(parameters);//post请求,必须将参数组织生成xml格式文档发送
Stringresult=CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL,"POST",requestXML);//调用微信统一接口获取prepayId
Map<String,String>map=XMLUtil.doXMLParse(result);//解析微信返回的信息(仍然返回xml格式),以Map形式存储便于取值
System.out.println("prepay_id="+map.get("prepay_id"));
4.调用微信支付JSAPI接口
如果prepay_id!=null,则说明订单已生成,可以发起支付请求。
//设置微信支付接口需要的参数
SortedMap<Object,Object>obj=newTreeMap<Object,Object>();
obj.put("appId",ConfigUtil.APPID);
obj.put("timeStamp",Long.toString(newDate().getTime()));
obj.put("nonceStr",PayCommonUtil.CreateNoncestr());
obj.put("package","prepay_id="+map.get("prepay_id"));
obj.put("signType",ConfigUtil.SIGN_TYPE);
StringpaySign=PayCommonUtil.createSign("UTF-8",obj);
obj.put("paySign",paySign);//paySign的生成规则和Sign的生成规则一致
obj.put("sendUrl",ConfigUtil.SUCCESS_URL);//付款成功后跳转的页面
if(request.headers.get("user-agent")!=null){
StringuserAgent=request.getHeader("user-agent");
charagent=userAgent.charAt(userAgent.indexOf("MicroMessenger")+15);
obj.put("agent",newString(newchar[]{agent}));//微信版本号,用于判断用户手机微信的版本是否是5.0以上版本。
}
将这些参数传到前端页面,赋值给JSAPI函数,发起支付请求。
前端页面示例如下:
<divclass="pay_but"><ahref="javascript:void(0);"onclick="onBridgeReady();">确认支付</a></div>
<scripttype="text/javascript">
functiononBridgeReady(){
if(parseInt(${obj.agent})<5){
alert("您的微信版本低于5.0无法使用微信支付");
return;
}
WeixinJSBridge.invoke('getBrandWCPayRequest',{
"appId":"${obj.appId}",
"timeStamp":"${obj.timeStamp}",
"nonceStr":"${obj.nonceStr}",
"package":"${obj.packageValue}",
"signType":"${obj.signType}",
"paySign":"${obj.paySign}"
},function(res){
//alert(res.err_msg);
if(res.err_msg=="get_brand_wcpay_request:ok"){
window.location.href="${obj.sendUrl}";
}else{
window.location.href="/mobile/front/payment?id=${goodsOrder.payId}";
}
});
}
</script>
点击“确认支付”按钮,发起支付请求,至此开发结束!
相关工具方法:
//sign签名
publicstaticStringcreateSign(StringcharacterEncoding,SortedMap<Object,Object>parameters){
StringBuffersb=newStringBuffer();
Setes=parameters.entrySet();
Iteratorit=es.iterator();
while(it.hasNext()){
Map.Entryentry=(Map.Entry)it.next();
Stringk=(String)entry.getKey();
Objectv=entry.getValue();
if(null!=v&&!"".equals(v)
&&!"sign".equals(k)&&!"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key="+ConfigUtil.API_KEY);
//System.out.println("signxml======"+sb.toString());
Stringsign=MD5Util.MD5Encode(sb.toString(),characterEncoding).toUpperCase();
returnsign;
}
//随机字符串
publicstaticStringCreateNoncestr(intlength){
Stringchars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Stringres="";
for(inti=0;i<length;i++){
Randomrd=newRandom();
res+=chars.indexOf(rd.nextInt(chars.length()-1));
}
returnres;
}
publicstaticStringCreateNoncestr(){
Stringchars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Stringres="";
for(inti=0;i<16;i++){
Randomrd=newRandom();
res+=chars.charAt(rd.nextInt(chars.length()-1));
}
returnres;
}
//将请求参数转换为xml格式的string
publicstaticStringgetRequestXml(SortedMap<Object,Object>parameters){
StringBuffersb=newStringBuffer();
sb.append("<xml>");
Setes=parameters.entrySet();
Iteratorit=es.iterator();
while(it.hasNext()){
Map.Entryentry=(Map.Entry)it.next();
Stringk=(String)entry.getKey();
Stringv=(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>");
returnsb.toString();
}
//发送https请求,返回微信服务器响应的信息
publicstaticStringhttpsRequest(StringrequestUrl,StringrequestMethod,StringoutputStr){
try{
//创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[]tm={newMyX509TrustManager()};
SSLContextsslContext=SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null,tm,newjava.security.SecureRandom());
//从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactoryssf=sslContext.getSocketFactory();
URLurl=newURL(requestUrl);
HttpsURLConnectionconn=(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){
OutputStreamoutputStream=conn.getOutputStream();
//注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
//从输入流读取返回内容
InputStreaminputStream=conn.getInputStream();
InputStreamReaderinputStreamReader=newInputStreamReader(inputStream,"utf-8");
BufferedReaderbufferedReader=newBufferedReader(inputStreamReader);
Stringstr=null;
StringBufferbuffer=newStringBuffer();
while((str=bufferedReader.readLine())!=null){
buffer.append(str);
}
//释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream=null;
conn.disconnect();
returnbuffer.toString();
}catch(ConnectExceptionce){
log.error("连接超时:{}",ce);
}catch(Exceptione){
log.error("https请求异常:{}",e);
}
returnnull;
}
//解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据
publicstaticMapdoXMLParse(Stringstrxml)throwsJDOMException,IOException{
strxml=strxml.replaceFirst("encoding=\".*\"","encoding=\"UTF-8\"");
if(null==strxml||"".equals(strxml)){
returnnull;
}
Mapm=newHashMap();
InputStreamin=newByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilderbuilder=newSAXBuilder();
Documentdoc=builder.build(in);
Elementroot=doc.getRootElement();
Listlist=root.getChildren();
Iteratorit=list.iterator();
while(it.hasNext()){
Elemente=(Element)it.next();
Stringk=e.getName();
Stringv="";
Listchildren=e.getChildren();
if(children.isEmpty()){
v=e.getTextNormalize();
}else{
v=XMLUtil.getChildrenText(children);
}
m.put(k,v);
}
//关闭流
in.close();
returnm;
}
/**
*获取子结点的xml
*@paramchildren
*@returnString
*/
publicstaticStringgetChildrenText(Listchildren){
StringBuffersb=newStringBuffer();
if(!children.isEmpty()){
Iteratorit=children.iterator();
while(it.hasNext()){
Elemente=(Element)it.next();
Stringname=e.getName();
Stringvalue=e.getTextNormalize();
Listlist=e.getChildren();
sb.append("<"+name+">");
if(!list.isEmpty()){
sb.append(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</"+name+">");
}
}
returnsb.toString();
}
一些常见报错解决方法:
1.redirect_uri参数错误
可能1:授权目录
支付授权目录是网站发起请求的页面所在目录,并且必须是能通过url地址访问的(与真实物理目录路径无关)。注意这个目录在注册填写时,需要精确到最细一级的且使用名称后直接加文件名,不可再增加or删减目录。
可能2:网页授权
当开发者使用微支付的“JSAPI”支付时,这种支付需要网页授权,先获取code,再拿code去获取openid和prepay_id。这个网页授权需要登录微信公众平台,点击左侧菜单“开发者中心”,在右侧“接口权限列表”中找到“网页账号”,点击最右侧的修改,把测试的域名写进去,注意不要加http。
可能3:网页获取用户基本信息(位置:微信公众号-开发者中心)
这个可能性最大,网页获取用户基本信息中的域名没有填写或填写错误,我遇到的了是这个问题,要确保相应的参数设置没有错误情况下,就检查这一项,很多时候就可以解决了。
可能4:链接地址不存在
在微信端点击相应的按钮,如果是出现链接地址不存在,或者配置错误也会出现这个问题,因此在配置内部链接网址的、目录的时候,一定要准确,不然就会出现以上图问题,这个比较好解决,检查,重新设置链接,这个有时要求有一定的代码基础。
2.access_denied
支付授权目录,注意看文档,大小写关系很大,url地址上最好都是小写字母,否则容易报此错误。
3.access_not_allow
将测试人的微信帐号加入白名单。
4.签名无效
登录微信商户平台(https://pay.weixin.qq.com)-账户设置-api安全-设置密钥。注意密钥设置成功后,设置密钥按钮不变,而且没有查询密钥的地方,这一点很不人性化。一般情况下密钥设置成功10分钟后自动生效。