前言
微信登录网页授权与APP授权
微信JSAPI支付
微信APP支付
微信APP和JSAPI退款
支付宝手机网站支付
支付宝APP支付
支付宝退款
以上我都放到个人公众号,搜一搜:JAVA大贼船,文末有公众号二维码!觉得个人以后开发会用到的可以关注一下哦!少走点弯路…
官方文档
H5微信登录授权文档地址
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
APP微信登录授权
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
iOS 接入指南
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html
Android 接入指南
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/Android.html
流程步骤
H5微信登录授权
- 引导用户进入授权页面同意授权,获取code
- 通过code换取网页授权access_token(与基础支持中的access_token不同)
- 如果需要,开发者可以刷新网页授权access_token,避免过期
- 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
疑问1:scope为snsapi_base和scope为snsapi_userinfo的区别?
snsapi_base是静默授权并自动跳转到回调页的,snsapi_userinfo是需要用户手动同意。
疑问2:网页授权access_token和普通access_token的区别?
普通access_token获取用户信息时,如果用户未关注,信息获取就为空。而网页授权access_token的获取,只要用户许可,就可以获得,不论用户是否关注。
疑问3:UnionID和openid的区别?
unionid对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号)都是相同的。
而openid对同一个微信开放平台下的不同应用都是不相同的,如用户授权应用A和应用B,那么用户的两个openid是不相同的,并且一个应用对应一个openid,如用户在次授权给应用A,openid不变。
疑问4:关于UnionID机制
即如果开发者有多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。
踩过的坑
- redirect_uri域名与后台配置不一致
解决:在公众号设置-功能设置-网页授权,配置前端存放txt文件的路径,如www.xxx.com/static,然后点击提交,可以事先测一下能不能访问到txt的内容。
- 获取微信用户信息,返回的unionID为空
解决:前往微信开放平台,绑定该公众号,大功告成
APP微信登录授权
- 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
- 通过code参数加上AppID和AppSecret等,通过API换取access_token;
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
准备工作
natapp 内网穿透工具
开发阶段可用此工具获取域名
获取AppID和AppSecret
前往微信开放平台(open.weixin.qq.com)查看对应的应用详情
修改授权回调域名
前往公众平台官网中的“公众号功能-功能设置”的配置选项中,根据说明规范配置网页授权回调域名
代码实现(含APP和H5)
配置参数
application.yml
# 微信相关配置
wx:
#商户 ID
MCH_ID:
# 项目基础域名
BASEURL:
#微信登录-用户同意后回调域名(前端域名)
URL:
# 公众号APP_ID
H_APP_ID:
# 公众号秘钥
H_APP_SECRET:
# app的APP_ID
A_APP_ID:
# APP的 秘钥
A_APP_SECRET:
#微信登录-微信授权基本地址
LOGIN_AUTH_BASE_URL: https://open.weixin.qq.com/connect/oauth2/authorize?
#微信登录-获取ACCESS_TOKEN的URL
LOGIN_ACCESS_TOKEN_URL: https://api.weixin.qq.com/sns/oauth2/access_token?
#微信登录-获取登录人信息的url
LOGIN_USER_INFO_URL: https://api.weixin.qq.com/sns/userinfo?
#微信登录-用户同意后回调地址(前端地址)
LOGIN_RETURN_URL: ${wx.URL}/static/weixinShouQuan.html
#微信登录-应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid), snsapi_userinfo
#(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 )
SCOPE: snsapi_userinfo
读取参数
YmlParament
/**
* 获取yml参数实体
*/
@Component
@Data
public class YmlParament {
/*微信相关字段*/
@Value("${wx.BASEURL}")
private String baseurl;
@Value("${wx.H_APP_ID}")
private String h_app_id;
@Value("${wx.A_APP_ID}")
private String a_app_id;
@Value("${wx.H_APP_SECRET}")
private String h_app_secret;
@Value("${wx.A_APP_SECRET}")
private String a_app_secret;
@Value("${wx.LOGIN_ACCESS_TOKEN_URL}")
private String login_access_token_url;
@Value("${wx.LOGIN_USER_INFO_URL}")
private String login_user_info_url;
@Value("${wx.LOGIN_AUTH_BASE_URL}")
private String login_auth_base_url;
@Value("${wx.LOGIN_RETURN_URL}")
private String login_return_url;
@Value("${wx.SCOPE}")
private String scope;
}
获取code
- H5获取code
前端通过后台拿到授权url,然后前端请求该url得到code再请求后台
WxController
@ApiOperation("获取授权url")
@PostMapping("/getWeiXinLoginUrl")
public R getWeiXinLoginUrl() throws Exception {
String url = ymlParament.getLogin_auth_base_url() + "appid=" + ymlParament.getH_app_id()+ "&redirect_uri=" + ymlParament.getLogin_return_url()
+ "&response_type=code"+ "&scope=snsapi_userinfo" + "&state=STATE#wechat_redirect";
//这里的R是自己自定义的,用于返回结果给前端
return R.ok().data("redirectUrl", url);
}
- APP获取code
APP则是前端使用 SDK 请求授权登录,用户同意授权后得到code再去请求后台
iOS 平台应用授权登录接入代码示例(请参考 iOS 接入指南):
-(void)sendAuthRequest
{
//构造SendAuthReq结构体
SendAuthReq* req =[[[SendAuthReq alloc]init]autorelease];
req.scope = @"snsapi_userinfo";
req.state = @"123";
//第三方向微信终端发送一个SendAuthReq消息结构
[WXApi sendReq:req];
}
Android 平台应用授权登录接入代码示例(请参考 Android 接入指南):
{
// send oauth request
Final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "wechat_sdk_demo_test";
api.sendReq(req);
}
通过code换取网页授权access_token,然后通过access_token和openid拉取用户信息
WxController
/*H5和app都可以调用*/
@ApiOperation("获取微信用户信息")
@PostMapping(value = "/getWxUserInFo")
public R getWxUserInFo(@RequestBody String body) throws Exception {
String state = JacksonUtil.parseString(body, "state");
String code = JacksonUtil.parseString(body, "code");
//标志哪一个应用,用来获取对应的appid和appsecret
Integer openIdType = JacksonUtil.parseInteger(body, "openIdType");
//1、获取code
if(IsNull.isNull(code) || IsNull.isNull(state)) {
return R.error("参数不能为空");
}
//2、通过code获取accesstoken,UserWxOpenidEums是用来记录应用的,如type1是xxAPP,type2是xx服务号
JSONObject accessToken=WxUtils.getAccessTokenByCode(code,
openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_id():ymlParament.getH_app_id(),
openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_secret():ymlParament.getH_app_secret(),
ymlParament.getLogin_access_token_url());
//3、获取用户信息
JSONObject userinfo=WxUtils.getUserinfo(openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_id():ymlParament.getH_app_id(),
accessToken.getString("openid"), accessToken.getString("access_token"), ymlParament.getLogin_user_info_url());
if(!IsNull.isNull(userinfo.getString("errcode"))){
return R.error(userinfo.getString("errmsg"));
}
return R.ok().data("token", token).data("userinfo",userinfo);
}
WxUtils
/**
* =============>>登录<<=============
* 第二步
* 通过code获取access_token
* 正确时返回的JSON数据包如下:
* {
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
*/
public static JSONObject getAccessTokenByCode(String code,String appId,String appSecret,String login_access_token_url) throws Exception {
Map map = new HashMap();
map.put("appid",appId);
map.put("secret",appSecret);
map.put("code",code);
map.put("grant_type","authorization_code");
return (JSONObject)JSON.parse(HttpUtil.get(login_access_token_url, map));
}
/**
* =============>>登录<<=============
* 第三步:刷新access_token(如果需要)
* 正确时返回的JSON数据包如下:
* {
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
*/
public static JSONObject refreshAccessToken(String appid,String refresh_token) throws Exception {
Map map = new HashMap<>();
map.put("appid",appid);
map.put("grant_type","refresh_token");
map.put("refresh_token",refresh_token);
return (JSONObject)JSON.parse(HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/refresh_token?", map));
}
/**
* =============>>登录<<=============
* 第四部,获取用户信息
* 通过code获取access_token
* 正确时返回的JSON数据包如下:
* {
"openid":" OPENID",
"nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
* @throws Exception
*/
public static JSONObject getUserinfo(String appid,String openid,String accessToken,String login_user_info_url) throws Exception {
//先判断下accessToken是否有效
HashMap param =new HashMap();
param.put("access_token", accessToken);
param.put("openid", openid);
JSONObject check=(JSONObject)JSON.parse(HttpUtil.get("https://api.weixin.qq.com/sns/auth?access_token="+accessToken+"&openid="+openid, param));
//检验授权凭证(access_token)是否有效,如果accessToken失效了,则刷新accessToken
if(!check.getString("errcode").equals("0")) {
accessToken=refreshAccessToken(appid, accessToken).getString("refresh_token");
}
param =new HashMap();
param.put("openid",openid);
param.put("access_token",accessToken);
param.put("lang","zh_CN");
return (JSONObject) JSON.parse(HttpUtil.get(login_user_info_url, param));
}
工具类
HttpUtil
public static String get(String urlStr, Map parameters) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setDoInput(true);
httpURLConnection.setDoOutput(true); // 设置该连接是可以输出的
httpURLConnection.setRequestMethod("GET"); // 设置请求方式
httpURLConnection.setRequestProperty("charset", "utf-8");
PrintWriter pw = new PrintWriter(new BufferedOutputStream(httpURLConnection.getOutputStream()));
StringBuffer parameter = new StringBuffer();
parameter.append("1=1");
for (Entry entry : parameters.entrySet()) {
parameter.append("&" + entry.getKey() + "=" + entry.getValue());
}
pw.write(parameter.toString());// 向连接中写数据(相当于发送数据给服务器)
pw.flush();
pw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) { // 读取数据
sb.append(line + "\n");
}
br.close();
return sb.toString();
}
JacksonUtil
public class JacksonUtil {
public static String parseString(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null) {
return leaf.asText();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Integer parseInteger(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null) {
return leaf.asInt();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}