最近开发app支付,支付宝按照开发文档很快搞定,本想微信支付开发也一样的容易,结果我错了,一路踩坑不断,到最后终于完成,耗了不少时间和精力,所以想写一篇关于微信统一支付的开发过程,希望大家能少走弯路
本文章适用于微信公众号支付开发,用的方式是统一支付接口,请对号入座,因为有好几种支付接口
只能是在手机App内的微信公众号内微信浏览器使用;PC电脑、手机浏览器(但不在微信公众号内的浏览器)是无法适用
另外说下
H5的支付方式(适用于任意浏览器),无法申请,不够资格;
Wap的支付方式(适用于任意浏览器),无法申请,不够资格;
:(
准备工作
一、微信公众号这部分的配置
申请公众号 https://mp.weixin.qq.com
- 申请好后,做下图配置,看1,2点
申请支付商户
- 申请好后,得到下图,看1点,记下商户号
点击上图中的“开发配置”,做以下配置,如图
授权目录url的注意地方
比如:您准备要写的支付调用页面是 http://xxx.xxx.xxx/abc/pay/weixin_pay.jsp 这个
则 授权目录就得写成
http://xxx.xxx.xxx/abc/pay/
至此,微信公众号就配置好了
二、微信支付的配置
登陆微信支付平台
https://pay.weixin.qq.com
在您申请成功微信支付审核通过后,会有一封邮件发给你,里边有帐号和密码什么的。
登陆进去后,点击顶部的“账户中心”,再点左边菜单的“api安全”,按下图配置
这样,微信支付这部分也大概设置好了。
三、到这里,我们总共记下了这些信息,如下图
四、以上准备好后,就可以准备写代码了
顺序一般是
pay_type.jsp (A支付类型选择) => pay_order_info.jsp(B商品订单信息) => wexinpay_oauth2.jsp(C统一支付)
a)支付类型选择,就是指选择支付宝、微信、银联等支付方式;
b)A页面提交后(选择微信支付),到B页面显示商品订单的详情,包括订单编号(order_no)、数量及金额等,这里开始就得写微信支付相关代码了,就从这里说起;
B页面,提交后,就到C统一支付页面。
以下是详细说明
B页面的订单信息,有表单提交按钮
如
<button οnclick="onpay();">确认提交订单</button>
这里要先取得用户信息,用 oauth2授权
注意:url的位置在前面的授权目录内
appid=你的微信公众号appid
订单提交代码如下
<script>
function onpay(){
var return_url = encodeURIComponent('http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp');
var oauth2_url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=微信公众号appid&redirect_uri='+return_url+'&response_type=code&scope=snsapi_base&state='+state+'#wechat_redirect';
document.getElementById("jtform").target = '_blank';
document.getElementById("jtform").action = oauth2_url;
}
</script>
这样,在点击“确认提交订单”按钮后,表单的订单内容信息,就会post到(统一支付页面)
http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp
重要
下面,我们再看这个C统一支付页面的写法
在徽信公众号浏览器下,可以直接带回code参数,而不需要微信用户点击授权确认;
wexinpay_oauth2.jsp 内关键代码如下
<html>
<head>
<meta charset="utf-8" />
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script src="jquery/jquery.min.js"></script> <!--引入你的jquery-->
<script>
//执行支付
function executePay(){
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();
}
}
//执行支付
function onBridgeReady(){
var result = document.getElementById("payString").value;
var payJson = eval('('+result+')'); //就是统一支付最终返回的json字符串
if(result!=''&&payJson['paySign']!=undefined&&payJson['paySign']!=null&&payJson['paySign']!=''){
WeixinJSBridge.invoke(
'getBrandWCPayRequest',{
"appId":payJson['appId'],
"timeStamp":payJson['timeStamp'],//时间戳,自1970年以来的秒数
"nonceStr":payJson['nonceStr'], //随机串
"package":payJson['package'],
"signType":"MD5", //微信签名方式:
"paySign":payJson['paySign'] //微信签名
},function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert('支付成功!');
/*在这里其实是不保险的,有可能在支付时,用户提前关闭或断线什么的,不能保证真正的支付结果,
应该在统一支付的回调url中处理,支付后的状态与订单状态同步,
请看第五点 ,统一支付的回调处理//回调的url本例设置为
String notify_url = "http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp"; //通知回调地址,不能有参数*/
var curr_url = '商户展示界面';
window.location.href = curr_url;
}else{
alert('支付失败!');
var curr_url = '商户失败界面';
window.location.href = curr_url;
} /* 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 */
});
}else{
alert('支付校验未能通过!');
var curr_url = '商户失败界面';
window.location.href = curr_url;
}
}
</script>
</head>
<body οnlοad="executePay();">
//页面加载完成后,调用executePay()执行支付...略
<%
String code = String.valueOf(request.getParameter("code")).replaceAll("null", ""); //取得传过来的code
String order_no = String.valueOf(request.getParameter("order_no")).replaceAll("null", ""); //取得传过来的订单编号
String userIp = '取得用户端的访问ip'; //代码略....
WeiXinPayDao wxPayDao = new WeiXinPayDao(); //做了一个微信支付业务类
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid+"&secret="+secret+"&code="+code+"&grant_type=authorization_code";
//用公众号的appid,key,刚接收的code组成新的url,来获取微信用户的openid
String info = wxPayDao.httpRequest(url);
if(info!=null&&!info.equals("")){
if(info.indexOf("errcode")==-1){
JSONObject joInfo = new JSONObject(info);
access_token = joInfo.getString("access_token");
refresh_token = joInfo.getString("refresh_token");
openid = joInfo.getString("openid");
unionid = joInfo.getString("unionid");}}
/*
如果以上没有什么问题的话,就可以取得当前微信用户的openid;
接下来,支付准备工作就差不多完成,终于到了调用统一支付接口这里了,统一支付接口调用就是为了取得 prepay_id 的值,有了这个值,就能发起支付
*/
/**
根据订单编号order_no,查询订单信息放到 Map orderMap = new HashMap();
orderMap 就是存放订单信息的,代码略....
*/
/**
接下来, 调用统一支付接口....
*/
String payJson = wxPayDao.pay(openid,orderMap,userIp); //参数openid,orderMap(订单信息),userIp(微信用户端的真实ip),得到一个json字符串
//把最终带 prepay_id 参数的字符串的值,放到界面的一个隐藏域内,这个值给executePay(),页面初始化时,发起支付
if(!payJson .equals("")){
payJson = payJson.replaceAll("\"", "'");
%>
<input id="payString" name="payString" value="<%=payJson%>" type="hidden">
<%
}
%>
</body>
</html>
以下是代码补充
补方法:
WeiXinPayDao
pay(openid,orderMap,userIp),是一个方法,代码如下
/**
* 统一下单接口
* openid 微信用户id
* orderMap 订单信息map
* user_ip 微信用户ip
* */
public String pay(String openid,Map orderMap,String user_ip){
String result = getResult("0","pass",null); //本方法最终返回的字符串
String order_no = String.valueOf(orderMap.get("order_no")); //订单号
String device_info = "WEB";
String nonce_str = UUID.randomUUID().toString().replaceAll("-", ""); //随机字串,32位
String body = String.valueOf(orderMap.get("order_name")).replaceAll("null", ""); //商品描述,我这里用订单名称
String total_fee = "2" ;//总金额 ,单位是分
String fee_type = "CNY"; //币种
String notify_url = "http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp"; //通知回调地址,不能有参数
String trade_type = "JSAPI"; //交易类型 公众号=JSAPI
String limit_pay = "no_credit"; //上传此参数no_credit--可限制用户不能使用信用卡支付
//然后把以上参数放到一个map中
Map paramsMap = new HashMap();
paramsMap.put("appid", appid); //微信公众号
paramsMap.put("mch_id", mch_id); //商户号,前面就是你记下来的
paramsMap.put("device_info", device_info);
paramsMap.put("nonce_str", nonce_str);
paramsMap.put("body", body);
paramsMap.put("total_fee", total_fee);
paramsMap.put("spbill_create_ip", user_ip);
paramsMap.put("notify_url", notify_url);
paramsMap.put("trade_type", trade_type);
paramsMap.put("limit_pay", limit_pay);
paramsMap.put("attach", order_no);
paramsMap.put("fee_type", fee_type);
paramsMap.put("openid", openid);
paramsMap.put("out_trade_no",order_no);//然后用以上参数排个序,连成一个参数字符串
String[] arr = new String[]{"appid","mch_id","device_info","nonce_str","total_fee",
"attach","fee_type","spbill_create_ip","notify_url","trade_type",
"limit_pay","body","openid","out_trade_no"};
Arrays.sort(arr);
String stringA = "";
int arr_len = arr.length;
for(int i=0;i<arr_len;i++){
stringA = stringA + "&"+arr[i]+"=" + paramsMap.get(arr[i]);
}
if(!stringA.equals("")){
stringA = stringA.substring(1);
}
String stringSignTemp = stringA+"&key=支付的key,注意不是公众号的key了";
String sign = Md5Encode.md5Encoding(stringSignTemp).toUpperCase();
//做一次md5加密,结果转成大写//然后,用sign的值,再拼一次xml格式的字符串
StringBuffer payXmlBuff = new StringBuffer();
payXmlBuff.append("<xml>")
.append("<appid>").append(appid).append("</appid>")
.append("<attach>").append(order_no).append("</attach>")
.append("<body><![CDATA[").append(body).append("]]></body>")
.append("<device_info>").append(device_info).append("</device_info>")
.append("<fee_type>").append(fee_type).append("</fee_type>")
.append("<limit_pay>").append(limit_pay).append("</limit_pay>")
.append("<mch_id>").append(mch_id).append("</mch_id>")
.append("<nonce_str>").append(nonce_str).append("</nonce_str>")
.append("<notify_url>").append(notify_url).append("</notify_url>")
.append("<openid>").append(openid).append("</openid>")
.append("<out_trade_no>").append(order_no).append("</out_trade_no>")
.append("<spbill_create_ip>").append(user_ip).append("</spbill_create_ip>")
.append("<total_fee>").append(total_fee).append("</total_fee>")
.append("<trade_type>").append(trade_type).append("</trade_type>")
.append("<sign>").append(sign).append("</sign>")
.append("</xml>");
/**然后,把带sign的xml拼成的字符串,发送到统一支付接口*/
String payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一支付接口的url
//然后,把上面拼接好的xml字符串,发到统一支付接口
HttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost(payUrl);
post.setEntity(new StringEntity(payXmlBuff.toString(),"UTF-8")); //注意utf-8
try {
HttpResponse execute = httpClient.execute(post);
HttpEntity entity = execute.getEntity();
String responseContent = EntityUtils.toString(entity,"utf-8"); //注意utf-8 ,responseContent 的结果也是一个xml字符串
//System.out.println("PayController index获取到的总数据:"+responseContent);
/*判断是否成功,返回的字符串结果中,标志有SUCCESS及prepay_id参数,为成功*/
if(responseContent.indexOf("SUCCESS")!=-1&&responseContent.indexOf("prepay_id")!=-1){
try {
//对返回的xml字符串做解析,这里用的是dom4j包
Document document = DocumentHelper.parseText(responseContent);
Element root = document.getRootElement();
result = String.valueOf(root.elementText("prepay_id")).replaceAll("null", "");
//System.out.println("prepay_id:="+result);
String payTimeStamp = Long.toString(System.currentTimeMillis()/1000); //去掉三位,精确到秒
String payNonceStr = UUID.randomUUID().toString().replaceAll("-", ""); //随机串
//跟前面一样,把参数放到map
String payarr[] = new String[]{"appId","timeStamp","nonceStr","package","signType"};
Map payParamMap = new HashMap();
payParamMap.put("appId", appid);
payParamMap.put("timeStamp",payTimeStamp);
payParamMap.put("nonceStr", payNonceStr);
payParamMap.put("package", "prepay_id="+result);
payParamMap.put("signType", "MD5");
Arrays.sort(payarr); //排序
String payA = "";
int pay_len = payarr.length;
for(int i=0;i<pay_len;i++){
payA = payA + "&"+payarr[i]+"=" + payParamMap.get(payarr[i]);
}
if(!payA.equals("")){
payA = payA.substring(1);
}
String paySignTemp = payA+"&key=支付的key,注意不是公众号的key";
//System.out.println("paySignTemp:"+paySignTemp);
String paySign = Md5Encode.md5Encoding(paySignTemp).toUpperCase();
//System.out.println("paysign:"+paySign);
payParamMap.put("paySign", paySign);
JSONObject payJson = new JSONObject(payParamMap);
result = payJson.toString();
//做成一个json字符串,返回给 http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp 界面的隐藏域内
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = getResult("999","支付出错!",null);
}
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = getResult("999","支付出错!",null);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = getResult("999","支付出错!",null);
}
}
补方法:
WeiXinPayDao
getResult(code,msg,infoMap)是一个方法,代码如下
/**
* 生成返回信息
* */
private String getResult(String code,String msg,Map infoMap){
JSONObject result = new JSONObject();
result.put("code",code);
result.put("msg",msg);
if(infoMap!=null&&!infoMap.isEmpty()){
result.put("info", infoMap);
}
return result.toString();
}
补方法:
WeiXinPayDao
httpRequest(url),是一个方法,代码如下
/**
* 发起http请求获取返回结果
* @param req_url 请求地址
* @return
*/
private String httpRequest(String req_url) {
StringBuffer buffer = new StringBuffer();
try {
URL url = new URL(req_url);
HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
httpUrlConn.setDoOutput(false);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
httpUrlConn.setRequestMethod("GET");
httpUrlConn.connect();
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
} catch (Exception e) {
System.out.println(e.getStackTrace());
}
return buffer.toString();
}
五、统一支付回调处理
http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp
回调处理 ,主要就是同步徽信支付状态和商户订单的支付状态,成功或失败
关键代码如下
<html>
<head>
<body>略...
<%
String return_xml = "";
//获取徽信那边post过来的数据流
String acceptjson=null;
try {
BufferedReader br = new BufferedReader(new InputStreamReader( (ServletInputStream) request.getInputStream(), "utf-8"));
StringBuffer sb = new StringBuffer("");
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
br.close();
acceptjson = sb.toString();
// System.out.print("acceptjson="+acceptjson);
} catch (Exception e) {
e.printStackTrace();
}
//判断处理 ,根据你自己实际的业务来写 ,acceptjson 实际也是一个xml字符串,用dom4j解析
if(acceptjson!=null&&!acceptjson.equals("")){
String source = "eva";
String appid = "";
String attach = "";
String mch_id = "";
String out_trade_no = "";
String result_code = "";
String return_code = "";
String time_end = "";
String transaction_id = "";
try {
Document document = DocumentHelper.parseText(acceptjson);
Element elRoot = document.getRootElement();
appid = String.valueOf(elRoot.elementText("appid")).replaceAll("null", "");
attach = String.valueOf(elRoot.elementText("attach")).replaceAll("null", "");
mch_id = String.valueOf(elRoot.elementText("mch_id")).replaceAll("null", "");
out_trade_no = String.valueOf(elRoot.elementText("out_trade_no")).replaceAll("null", "");
result_code = String.valueOf(elRoot.elementText("result_code")).replaceAll("null", "");
return_code = String.valueOf(elRoot.elementText("return_code")).replaceAll("null", "");
time_end = String.valueOf(elRoot.elementText("time_end")).replaceAll("null", "");
transaction_id = String.valueOf(elRoot.elementText("transaction_id")).replaceAll("null", "");
//合法性校验
if(appid.equals("您的公众号")&&mch_id.equals("您的商户号")){
//以下订单的处理只做参考
if(!out_trade_no.equals("")){
//订单同步处理
JdbcHelp jdbc = JdbcHelpUtil.getInstance();
String querySQL = "select 1 from hq_order_head where payment_status=8 and order_no="+out_trade_no;
//是否已经支付过了
Form orForm = new Form();
orForm.setValue(JdbcConstant.SQL, querySQL);
orForm.setValue(JdbcConstant.SOURCE, source);
Map qMap = jdbc.queryMap(orForm);
if(qMap.isEmpty()){
if(result_code.equals("SUCCESS")){
time_end = UtilTools.getInstance().formatDate("yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss", time_end);
String upSQL = "update hq_order_head set payment_status=8,status=8,payment_date=?,payment_no=? where order_no="+out_trade_no;
orForm.setValue(JdbcConstant.PARAMES_FIELDS, "payment_date,payment_no");
orForm.setValue("payment_date", time_end);
orForm.setValue("payment_no", transaction_id);
orForm.setValue(JdbcConstant.SQL, upSQL);
jdbc.update(orForm);
jdbc.commit(source);
}
}
return_xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[]]></return_msg></xml>";
}
}else{
return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
}
} catch (DocumentException e1) {
return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
}
}else{
return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
}
//返回处理结果给徽信那边,值是xml字符串, SUCCESS或FAIL
response.getWriter().print(return_xml);
%>
</body>
</html>
到此,整个微信公众号统一支付介绍完了
以上代码是关键代码,非完整,但不影响阅读和参考。
希望对需要的人有用:)