一、微信授权
1、在对接微信之前,首先从微信官网(https://mp.weixin.qq.com)去注册微信公众公众账号,提交资料等待验证通过。
2、验证通过后就可以 微信公众平台 获取到 AppID 和 AppScret 两个参数值,如图:
3、网页授权域名
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
在 “开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名” 进行网页域名授权,需要下载校验文件并放在项目根目录下,注意能被访问到,避免拦截器,过滤器等拦截了,如图:
4、此时,按照微信官方文档,需要进行 填写服务器配置,参考微信接入指南 (https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319),如图:
注意:
URL 参数是一个接口地址,需要写一个接口(http://xx.xx.com/x/xSignature类似的)供微信端调用校验。
Token 自己填写。
如图:
只要能返回 随机字符串 就可以通过。
调用成功后如图:
注意:只要验证通过,微信自定义菜单就会失效,需要调用接口生成自定义菜单,生成方式查看微信开发文档,可以在微信公众平台线接口调试生成。
5、因为需要调用接口生成菜单,所以需要获取 调用接口的 access_token。
https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?
grant_type=client_credential&appid=APPID&secret=APPSECRET
创建菜单接口 POST
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
6、在项目中进行拦截,微信端嵌套H5页面。
代码1
// 判断是否微信端跳转
if(userAgent.contains("micromessenger")){
String version = userAgent.substring(userAgent.indexOf("micromessenger"));
System.out.println("微信请求...去授权");
weChatAuthorityUtil.weChatRedirect(request,response);
}
然后跳转到微信端进行授权,并且传入回调接口地址,用于获取code 和 state 两个参数值,code 和 state 两个参数用于获取微信用户信息,在微信端校验。
代码2
@RequestMapping(value = "/ucode", method = RequestMethod.GET)
public void weChatCode(HttpServletRequest request, HttpServletResponse response, String code, String state){
if(code != null && code.length() > 0 && propertiesUtil.getState().equals(state)){
//通过 code 获取 access_token,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期
Map<String,String> param = new HashMap<String,String>();
param.put("appid",propertiesUtil.getAppId());
param.put("secret",propertiesUtil.getAppSecret());
param.put("code",code);
param.put("grant_type","authorization_code");
//获取用户token openid
String resStr = HttpUtil.doPostMap(WXPayConstants.WECHAT_ACCESS_TOKEN,param);
if(resStr != null && resStr.length() > 0){
System.out.println("获取token resStr = " + resStr);
Map<String,Object> res = JSON.parseObject(resStr,Map.class);
String errcode = (String) res.get("errcode");
if(errcode == null){ //errcode 错误码
String accessToken = (String) res.get("access_token");
int expiresIn = (int) res.get("expires_in");
String openId = (String) res.get("openid");
String scope = (String) res.get("scope");
//首先从数据库中获取用户信息,没有从微信端获取
UserIdentity userIdentity = userService.getUserIdentityByOpenId(openId);
if(userIdentity != null){ //数据库中没有获取到,第一次登录
//数据库里面有,放入session
//业务逻辑
}else{
//获取用户信息 存库 加入session
userIdentity = weChatAuthorityUtil.getUserInfoBy(openId,accessToken);
//业务逻辑
}
}else{
String errmsg = (String) res.get("errmsg");
//此处,说明微信获取token失败
}
}
}
}
二:微信支付
1、在对接微信支付之前,同上,也是需要去 “微信支付商务” 平台去开通;
选择支付方式 JSAPI,NATIVE,APP,MWEB–H5支付
在 微信商户平台 ——> 账户中心 配置个人信息; 在 API安全 中配置 API密匙(后面调用下单接口需要用到,32为码)
在 微信商户平台 ——> 产品中心——>开发配置 添加域名配置,授权等
2、此项目首先选择 JSAPI支付,首先需要调用 统一下单(URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder)接口在微信端下单。
注意:在 统一下单 接口里面,需要对接口参数(sign)进行一个签名认证,签名算法看官方文档(接口规则 --> 安全规范)
为了校验签名是否正确,用 签名校验工具 验证
此处 商户Key 为 商户号
把生成的xml结构的参数粘贴进去,参数中需要有 sign 已生成好的签名进行校验,代码3 类似这样:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<appid>wx69767be593fa4866</appid>
<body>水费-缴费</body>
<mch_id>1543011521</mch_id>
<nonce_str>20ef119e812exxxxxxxxxxa448b57ebc</nonce_str>
<notify_url>http://xx.xxx.com/xx/callback</notify_url>
<out_trade_no>82bae3axxxxxxxxxxxa17aff7310097b</out_trade_no>
<spbill_create_ip>123.151.0.0</spbill_create_ip>
<total_fee>50</total_fee>
<trade_type>JSAPI</trade_type>
<sign>758394EB3F6A59E60BE3BCB21D1DD166</sign>
<openid>oxrDv57zsMrMtnPvLoDZZmnAkWDE</openid>
</xml>
3、此时需要进行调用微信 统一下单(https://api.mch.weixin.qq.com/pay/unifiedorder) 接口生成微信端订单。
此接口 为 post提交,xml的形式传参数。类似上面提到的xml格式的数据 代码3。
统一下单接口中 签名校验 和 页面JS 发起支付需要的签名算法一样。
代码4
@RequestMapping(value = "/payment",method = RequestMethod.GET)
public ModelAndView wxPayment(HttpServletRequest request, HttpServletResponse response, Long id){
ModelAndView modelAndView = new ModelAndView("view/");
PayBill payBill = new PayBill();
payBill.setId(BigInteger.valueOf(id));
RsFeesMessage rsFeesMessage = payBillService.getFeesMessage(payBill);
UserIdentity userIdentity = (UserIdentity) request.getSession().getAttribute(SessionConstant.ACCOUNT_SESSION_KEY);
WxPayEntity result = new WxPayEntity();
try {
//查询账单信息,使用账单中的金额
PayBill bill = payBillService.getBillMessage(id);
//商户订单号
String outTradeNo = WXPayUtil.getOut_trade_no();
//产品价格,单位:分
BigDecimal multiply = bill.getPaymentAmount().multiply(new BigDecimal(100));
Integer totalFee = multiply.intValue();
//客户端ip
String ip = request.getRemoteAddr();
//支付成功后回调的url地址
String notifyUrl ="http//xx.xx.com/pay/callback";
//String nonceStr = WXPayUtil.getNonceStr();//随机数据
WxPayEntity payEntity = new WxPayEntity();
payEntity.setBody("水费-缴费");
payEntity.setOutTradeNo(outTradeNo);
payEntity.setTotalFee(totalFee);
payEntity.setNotifyUrl(notifyUrl);
payEntity.setOpenId(userIdentity.getOpenId());
payEntity.setAppId(propertiesUtil.getAppId());
payEntity.setMchId(propertiesUtil.getMchId());
payEntity.setAppKey(propertiesUtil.getAppKey());
/*SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
long time = 30*60*1000;//30分钟
Date afterDate = new Date(new Date().getTime() + time);//30分钟后的时间
String timeExpire = sdf.format(afterDate);*/
//统一下单
String strResult = WXPayUtil.unifiedorder("测试", outTradeNo, totalFee,ip, notifyUrl,userIdentity.getOpenId(),propertiesUtil.getAppId(),propertiesUtil.getMchId(),propertiesUtil.getAppKey());
//将解析结果存储在HashMap中
Map map = WXPayUtil.doXMLParse(strResult);
//返回状态码
String returnCode = (String) map.get("return_code");
String resultCode = (String) map.get("result_code");
System.out.println("resultCode = " + resultCode);
System.out.println("----------------------------------------------");
//两者都为SUCCESS才能获取prepay_id
if( returnCode.equals(WXPayConstants.SUCCESS) && resultCode.equals(WXPayConstants.SUCCESS) ){
System.out.println("统一下单成功...");
//业务逻辑,写入订单日志(你自己的业务) .....
payBill.setUserId(userIdentity.getId());
//修改账单表 支付中
payBillService.updatePayBillById(payBill);
String timeStamp = WXPayUtil.getTimeStamp();//时间戳
String nonceStr = WXPayUtil.getNonceStr();//随机字符串
//注:上面这两个参数,一定要拿出来作为后续的value,不能每步都创建新的时间戳跟随机字符串,不然H5调支付API,会报签名参数错误
String prepayId = (String) map.get("prepay_id");
System.out.println("prepayId = " + prepayId);
result.setResultCode(resultCode);
result.setAppId(propertiesUtil.getAppId());
result.setTimeStamp(timeStamp);
result.setNonceStr(nonceStr);
result.setPackageStr("prepay_id="+prepayId);
result.setSignType(WXPayConstants.MD5);
/*redisUtil.set(userIdentity.getId()+"_prepay_id",prepayId);
redisUtil.setObject(RedisConstant.PREPAY_ID_KEY,userIdentity,RedisConstant.KEY_EXPIRE_30m);*/
//WXPayUtil.unifiedorder(.....) 下单操作中,也有签名操作,那个只针对统一下单,要区别于下面的paySign
//第二次签名,将微信返回的数据再进行签名
SortedMap<Object,Object> signMap = new TreeMap<Object,Object>();
signMap.put("appId",propertiesUtil.getAppId());
signMap.put("timeStamp", timeStamp);
signMap.put("nonceStr", nonceStr);
signMap.put("package", "prepay_id="+prepayId); //注:看清楚,值为:prepay_id=xxx,别直接放成了wxReturnData.getPrepay_id()
signMap.put("signType", WXPayConstants.MD5);
String paySign = WxSign.createSign(signMap, propertiesUtil.getAppKey());//支付签名
result.setSign(paySign);
}else{
result.setResultCode(WXPayConstants.FAIL);
if((map.get("return_code")).equals(WXPayConstants.FAIL)){
saveBillLog(payBill,(String) map.get("return_msg"));
}else if((map.get("result_code")).equals(WXPayConstants.FAIL)){
saveBillLog(payBill,(String) map.get("err_code_des"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
modelAndView.addObject("rsFeesMessage",rsFeesMessage);
modelAndView.addObject("result",result);
return modelAndView;
}
生成签名算法:
代码5
public static String createSign(SortedMap<Object,Object> parameters, String key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
4、下单成功后,将获取到的订单号 prepay_id 参数传递到页面进行调用JS支付此订单。
注意:传输到页面的package参数是订单号,参数格式为 prepay_id=11111111111
代码6
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":package,
"signType":signType, //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
//您已付款成功
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
//您已取消支付
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
//支付遇到问题
}
});
}
var fn = function() {
alert($("input[name='checkbox']").is('checked'));
if(resultCode == "SUCCESS"){
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、在前段用js发起支付,微信就可以支付了
6、支付成功,微信回调你的回调接口,放回支付成功信息,咋流中获取xml数据:
@RequestMapping("/callback")
@ResponseBody
public String callback(HttpServletRequest request, HttpServletResponse response){
System.out.println("支付回调...");
String resXml = "";
InputStream inStream;
Map resultMap = null;
PayBill payBill = new PayBill();
try {
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
// 获取微信调用我们notify_url的返回信息
String result = new String(outSteam.toByteArray(), "utf-8");
// 关闭流
outSteam.close();
inStream.close();
resultMap = WXPayUtil.doXMLParse(result); //解析为Map
//返回状态码
String returnCode = (String) resultMap.get("return_code");
String resultCode = (String) resultMap.get("result_code");
//String sign = (String) resultMap.get("sign");
if (resultCode.equals(WXPayConstants.SUCCESS)) {
logger.info("wxnotify:微信支付----返回成功");
String xmlStr = WXPayUtil.mapToXml(resultMap);
//验证签名 和之前一样方式验证
boolean flag = WXPayUtil.isSignatureValid(xmlStr,propertiesUtil.getAppKey());
// 保存数据或者 处理业务
}
} else {
logger.error("wxnotify:支付失败,错误信息:" + resultMap.get("return_msg"));
resXml = resFailXml;
}
}catch (Exception e) {
logger.error("wxnotify:支付回调发布异常:", e);
} finally {
//处理数据
saveBillLog(payBill,(String)resultMap.get("return_msg"));
}
// <xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>
//需要给微信端返回结果,要不一直调好几次
return resXml;
}