首先,我先简要说明一下微信支付开发的流程
众所周知,工欲善其事,必先利其器,微信官方推出了web微信开发工具,有windows、linux、版本的,根据自己的开发环境选择合适自己的,登陆公众平台-->开发-->开发工具。
根据官网的文档说明,先在微信公众平台里点击微信支付,填写测试授权或支付授权目录,支付测试状态下,设置测试目录,测试人的微信号添加到白名单,发起支付的页面目录必须与设置的精确匹配,而且该域名必须是通过备案的,自己可以写个简单的servlet验证token,如果验证通过,说明该域名是有效的,如果没有验证通过,则说明该域名肯定有问题(排除servlet写的有问题)这一步是可选的,不是必须的,反正,我是当时测过的。
接着,添加测试白名单,填写支付申请。
最后,记得在开发-->接口权限-->网页服务-->网页账号,修改网页授权回调页面域名,授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权
好了,到这里,微信支付的配置信息暂且告一段落,下面开始微信支付开发流程
1)先进行微信网页授权,获取code,引导关注者打开如下页面:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect若提示“该链接无法访问”,请检查参数是否填写错误。
参数说明:
部分代码:
<pre name="code" class="java"><pre name="code" class="java">@SuppressWarnings("deprecation")
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//共账号及商户相关参数
String appid = "";
//String backUri = "";
String backUri = "";
//授权后要跳转的链接所需的参数一般有会员号,金额,订单号之类,
//最好自己带上一个加密字符串将金额加上一个自定义的key用MD5签名或者自己写的签名,
//比如 Sign = %3D%2F%CS%
String totals = request.getParameter("totals");
String orderNo=request.getParameter("orderNO");
//String orderNo=appid+Sha1Util.getTimeStamp();
backUri = backUri+"?orderNo="+orderNo+"&describe=test&money="+totals;
//URLEncoder.encode 后可以在backUri 的url里面获取传递的所有参数
backUri = URLEncoder.encode(backUri);
//scope 参数视各自需求而定,这里用scope=snsapi_base 不弹出授权页面直接授权目的只获取统一支付接口的openid
String url = "http://open.weixin.qq.com/connect/oauth2/authorize?" +
"appid=" + appid+
"&redirect_uri=" +
backUri+
"&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect";
response.setCharacterEncoding("UTF-8");
response.sendRedirect(url);
}
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE 。若用户禁止授权,则重定向后不会带上 code 参数,仅会带上 state 参数 redirect_uri?state=STATE
2)通过code换取openid
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
正确时返回的JSON数据包如下:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误时微信会返回JSON数据包如下(示例为Code无效错误):
{"errcode":40029,"errmsg":"invalid code"}在这里我遇到了第一坑,openid拿不到,究其原因是网页授权的域名写错了。
3)调用支付接口https://api.mch.weixin.qq.com/pay/unifiedorder
在这里我遇到了第二坑,prepay_id为空,最后查出来是因为商户密钥配错了
请求参数如下:
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名生成算法 |
商品描述 | body | 是 | String(128) | Ipad mini 16G 白色 | 商品或支付单简要描述 |
商品详情 | detail | 否 | String(8192) | Ipad mini 16G 白色 | 商品名称明细列表 |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 |
货币类型 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 |
总金额 | total_fee | 是 | Int | 888 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 | 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟 |
商品标记 | goods_tag | 否 | String(32) | WXG | 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 取值如下:JSAPI,NATIVE,APP,详细说明见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | no_credit--指定不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 |
部分代码:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//网页授权后获取传递的参数
//String userId = request.getParameter("userId");
String orderNo = request.getParameter("orderNo");
String money = request.getParameter("money");
String code = request.getParameter("code");
//金额转化为分为单位
float sessionmoney = Float.parseFloat(money);
int paymoney=(int) (sessionmoney*100);
String finalmoney=String.valueOf((paymoney));
//商户相关资料
String appid = "";
String appsecret = "";
String partner = "";// 商户号
String partnerkey = "";//商户号密钥
String openId ="";
String URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid+"&secret="+appsecret+"&code="+code+"&grant_type=authorization_code";
JSONObject jsonObject = CommonUtil.httpsRequest(URL, "GET", null);
if (null != jsonObject) {
openId = jsonObject.getString("openid");
System.out.println(openId);
PrintWriter pw = response.getWriter();
//pw.print("openId----------->"+openId);
}
//获取openId后调用统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder
String currTime = TenpayUtil.getCurrTime();
//8位日期
String strTime = currTime.substring(8, currTime.length());
//四位随机数
String strRandom = TenpayUtil.buildRandom(4) + "";
//10位序列号,可以自行调整。
String strReq = strTime + strRandom;
//商户号
String mch_id = partner;
//子商户号 非必输
//String sub_mch_id="";
//设备号 非必输
//String device_info="";
//随机数
String nonce_str = strReq;
//商品描述
//String body = describe;
//商品描述根据情况修改
String body = orderNo;
//附加数据
//String attach = userId;
//商户订单号
String out_trade_no = orderNo;
int intMoney = Integer.parseInt(finalmoney);
//总金额以分为单位,不带小数点
int total_fee = intMoney;
//订单生成的机器 IP
String spbill_create_ip = request.getRemoteAddr();
//订 单 生 成 时 间 非必输
// String time_start ="";
//订单失效时间 非必输
// String time_expire = "";
//商品标记 非必输
// String goods_tag = "";
//这里notify_url是 支付完成后微信发给该链接信息,可以判断会员是否支付成功,改变订单状态等。
String notify_url ="";
String trade_type = "JSAPI";
String openid = openId;
//非必输
// String product_id = "";
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", body);
//packageParams.put("attach", attach);
packageParams.put("out_trade_no", out_trade_no);
//这里写的金额为1 分到时修改
//packageParams.put("total_fee", "1");
packageParams.put("total_fee", finalmoney);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("openid", openid);
RequestHandler reqHandler = new RequestHandler(request, response);
reqHandler.init(appid, appsecret, partnerkey);
String sign = reqHandler.createSign(packageParams);
String xml="<xml>"+
"<appid>"+appid+"</appid>"+
"<mch_id>"+mch_id+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body><![CDATA["+body+"]]></body>"+
//"<attach>"+attach+"</attach>"+
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
//金额,这里写的1 分到时修改
//"<total_fee>"+1+"</total_fee>"+
"<total_fee>"+finalmoney+"</total_fee>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"<notify_url>"+notify_url+"</notify_url>"+
"<trade_type>"+trade_type+"</trade_type>"+
"<openid>"+openid+"</openid>"+
"</xml>";
System.out.println(xml);
String allParameters = "";
try {
allParameters = reqHandler.genPackage(packageParams);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String prepay_id="";
try {
prepay_id = new GetWxOrderno().getPayNo(createOrderURL, xml);
PrintWriter pw = response.getWriter();
if(prepay_id.equals("")){
request.setAttribute("ErrorMsg", "统一支付接口获取预支付订单出错");
String json="{\"status\":\"error\",\"message\":\"prepay_id is null\"}";
pw.print(json);
//response.sendRedirect("error.jsp");
return;
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
SortedMap<String, String> finalpackage = new TreeMap<String, String>();
String appid2 = appid;
String timestamp = Sha1Util.getTimeStamp();
String nonceStr2 = nonce_str;
String prepay_id2 = "prepay_id="+prepay_id;
String packages = prepay_id2;
finalpackage.put("appId", appid2);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonceStr2);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
String finalsign = reqHandler.createSign(finalpackage);
//System.out.println("pay.jsp?appid="+appid2+"&timeStamp="+timestamp+"&nonceStr="+nonceStr2+"&package="+packages+"&sign="+finalsign);
response.sendRedirect("pay.jsp?appid="+appid2+"&timeStamp="+timestamp+"&nonceStr="+nonceStr2+"&package="+packages+"&sign="+finalsign+"&orderNO="+orderNo);
return;
}
4)前台页面掉微信支付 js 接口
示例代码如下:
unction onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId" : "", //公众号名称,由商户传入
"timeStamp":" ", //时间戳,自1970年以来的秒数
"nonceStr" : "", //随机串
"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();
}
5)处理回调逻辑
3)步棸自己配置notify_url即为支付过后的回调地址,可以为action,静态页面等,自己可以在改回调地址里处理支付过后的业务逻辑
到这里,微信jsAPI开发流程已基本全部讲完,最后,在测试的过程中,微信支付是不允许一个订单经常改价钱的,微信支付默认以第一次提交订单的价钱为基准,以后再提交该订单如果价钱不是第一次的价钱,微信支付默认通不过(切记!我这个坑查了我一天),最后,祝大家开发顺利