微信支付之网页授权 - Java 开发
本文章是首次接触微信支付所写下,如果对您有帮助希望点个赞。若有疑问或不对的地方欢迎各位留言或私信指正交流
用户在微信中访问第三方网页,公众号可以通过微信网页授权机制获取用户基本信息,实现业务逻辑:网页授权 | 微信开放文档 (qq.com)
前期踩坑:
- 最开始的时候是一个订阅号,普通订阅号无法拉起微信的授权页面
- 然后改方案通过用户向订阅号号发送消息,后端去监听这个消息就能拿到用户的openId
- 再然后就是订阅号不能发送现金红包,就换了一个服务号
最终方案:
- 用户向服务号发消息
- 获取到openId -> 返回一个超链接跳转html资源文件
- h5中再请求自己的后端接口并带上 openId
必备要素
- 有自己的域名,比如
www.bigbanan.com
- 在公众平台官网中
开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息
的配置选项中,修改授权回调域名 - 用户关注了公众号后,才能发消息才能调用成功的
用户的 openid 在各个公众号下不同、用户的 UnionID 在同一个开发者下的不同公众号相同
方案一:拉起微信的授权页面
官网文档授权方式
- snsapi_base - 只能获取 openid
- snsapi_userinfo - 可获取 openid、unionid、头像、昵称等信息
拉起授权核心代码
/**
* 1.拉起微信授权页面获取 code、用户同意授权后跳转到后端 myApi 接口
*/
@RequestMapping(value = "/wxOauth2H5", method = {RequestMethod.GET, RequestMethod.POST})
public String wxOauth2H5(){
private String OAUTH2_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
private static final String APPID = "wxappid88888888888";
private static final String REDIRECT_URL = "www.bigbanana.com/wxOauth2AccessToken";
private static final String OAUTH_TYPE = "snsapi_base";
return OAUTH2_URL.replace("APPID",APPID).replace("REDIRECT_URI",encodeurl).replace("SCOPE",OAUTH_TYPE)
}
/**
* 2.在 wxOauth2AccessToken 接口中,使用 code 换取 access_token
*/
@RequestMapping(value = "/wxOauth2AccessToken", method = {RequestMethod.GET, RequestMethod.POST})
public String myApi(@RequestParam("code") String code){
private String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
private static final String APPID = "wxappid88888888888";
private static final String SECRET = "secret88888888888";
OAUTH2_ACCESS_TOKEN_URL.replace("APPID",APPID).replace("SECRET","SECRET").replace("CODE",CODE);
// 通过http工具发起请求(略)
String response = HttpUtil.get(url);
// 返回内容大概长这样{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
return response;
}
方案二:通过客服消息
通过指定消息服务器方法,用户向公众号发送的消息可以传到后代 api 进行处理
/**
* Get 请求用于保存服务器配置时候的验证
*/
@RequestMapping(value = "/receiveEvent", method = RequestMethod.GET)
public String receiveEventGet(@RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) {
try {
// 请求参数非空判断
if (StrUtil.hasBlank(signature, timestamp, nonce, echostr)) {
log.error("存在空参数");
return "error";
}
// 校验签名
if (checkSign(signature, timestamp, nonce)) {
return echostr;
}
return "signature check failed.";
} catch (Exception e) {
log.error("yun callback error.", e);
return "yun callback error.";
}
}
@RequestMapping(value = "/receiveEvent", method = RequestMethod.POST)
public String receiveEventPost(HttpServletRequest request) {
log.info("==================== Listener receive event post ====================");
String xmlData = WXPayUtil.getDataBodyByRequest(request);
if (StrUtil.isBlank(xmlData)) {
return StringUtils.EMPTY;
}
Map<String, String> xmlMapReq;
try {
xmlMapReq = WXPayUtil.xmlToMap(xmlData);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("xmlData to xmlMap -> {}", xmlMapReq);
// 消息类型
String msgType = xmlMapReq.get("MsgType");
if (null == msgType) {
return StringUtils.EMPTY;
}
// 根据不同消息类型做不同处理
switch (msgType) {
case WXPayConstants.MSG_TYPE_TEXT:
String content = xmlMapReq.get("Content");
if (StrUtil.isNotBlank(content) && content.equals("领红包")) {
String str = "<a href='https://www.xxx.cn/redPack/index.html?openId=" + xmlMapReq.get("FromUserName") + "'>点我领红包</a>";
Map<String, String> resMap = new HashMap<>();
resMap.put("ToUserName", xmlMapReq.get("FromUserName"));
resMap.put("FromUserName", xmlMapReq.get("ToUserName"));
resMap.put("CreateTime", "123456");
resMap.put("MsgType", "text");
resMap.put("Content", str);
String resXml;
try {
resXml = WXPayUtil.mapToXml(resMap);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("用户发送了文本内容,返回:" + resXml);
return resXml;
}
break;
default:
break;
}
return StringUtils.EMPTY;
}
/*
* 签名验证
*/
public boolean checkSign(String signature, String timestamp, String nonce) {
// 生成签名
List<String> signList = new ArrayList<>();
// 配置文件中读取token(微信后台配置的Token)
signList.add("mykey");
signList.add(timestamp);
signList.add(nonce);
// 1. 将token、timestamp、nonce三个参数进行字典序排序
signList.sort(Comparator.naturalOrder());
// 2. 将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder signSb = new StringBuilder();
signList.forEach(signSb::append);
String signStr = DigestUtils.sha1Hex(signSb.toString());
// 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
log.info("signature=[{}], signStr=[{}]", signature, signStr);
return signature.equals(signStr);
}