首先,我先简要说明一下微信支付开发的流程
众所周知,工欲善其事,必先利其器,微信官方推出了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若提示“该链接无法访问”,请检查参数是否填写错误。
参数说明:
部分代码:
@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);
}
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 packageParams = new TreeMap();
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=""+
""+appid+" "+
""+mch_id+" "+
""+nonce_str+" "+
""+sign+" "+
""+
//""+attach+" "+
""+out_trade_no+" "+
//金额,这里写的1 分到时修改
//""+1+" "+
""+finalmoney+" "+
""+spbill_create_ip+" "+
""+notify_url+" "+
""+trade_type+" "+
""+openid+" "+
" ";
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 finalpackage = new TreeMap();
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;
}
示例代码如下:
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();
}
3)步棸自己配置notify_url即为支付过后的回调地址,可以为action,静态页面等,自己可以在改回调地址里处理支付过后的业务逻辑
到这里,微信jsAPI开发流程已基本全部讲完,最后,在测试的过程中,微信支付是不允许一个订单经常改价钱的,微信支付默认以第一次提交订单的价钱为基准,以后再提交该订单如果价钱不是第一次的价钱,微信支付默认通不过(切记!我这个坑查了我一天),最后,祝大家开发顺利