相信各位程序员在开发过程中或多或少会有想关于支付对接的一些需求
本文主要总结在微信公众号对接过程中出现的一些注意事项:
代码块
例如:
/**
* 获得引导关注者打开的页面地址
*
* @param appid
* 公众号的唯一标识
*
* @param redirect_uri
* 授权后重定向的回调链接地址
*
* @param scope
* 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
*
* snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、
*
* 所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
*
* @param state
* 重定向后会带上state参数,开发者可以填写a-z A-Z 0-9的参数值
*
*/
public static String getLeadOAuthUrl(String appid, String redirect_uri, String scope, String state) {
String do_redirect_uri = URLEncoder.encode(redirect_uri);
String oauth_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri="
+ do_redirect_uri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
return oauth_url;
}
授权作用于scope:
snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)。
第一步:获得引导关注者打开的页面地址
请求方法
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。
尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问
跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。
参数说明:
参数 | 是否必填 | 说明 |
---|---|---|
appid | 是 | 公众号的唯一标识 |
redirect_uri | 是 | 授权后重定向的回调链接地址,请使用urlEncode对链接进行处理 |
response_type | 是 | 返回类型,请填写code |
scope | 是 | 应用授权作用域, snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid), snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。 并且,即使在未关注的情况下,只要用户授权,也能获取其信息) |
state | 否 | 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 |
#wechat_redirect |
是 | 无论直接打开还是做页面302重定向时候,必须带此参数 |
用户同意授权后
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
第二步:通过code换取网页授权access_token
首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。
请求方法
获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数说明
参数 | 是否必填 | 说明 |
---|---|---|
appid | 是 | 公众号的唯一标识 |
secret | 是 | 公众号的appsecret |
code | 是 | 填写第一步获取的code参数 |
grant_type | 是 | 填写为authorization_code |
返回说明
正确时返回的JSON数据包如下:
{
“access_token”:”ACCESS_TOKEN”,
“expires_in”:7200,
“refresh_token”:”REFRESH_TOKEN”,
“openid”:”OPENID”,
“scope”:”SCOPE”
}
参数说明
参数 | 描述 |
---|---|
access_token | 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 |
expires_in | access_token接口调用凭证超时时间,单位(秒) |
refresh_token | 用户刷新access_token |
openid | 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页, 也会产生一个用户和公众号唯一的OpenID |
scope | 用户授权的作用域,使用逗号(,)分隔 |
参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
一、设置支付目录
请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。
在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台–>产品中心–>开发配置。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。
规则:
1、所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
2、最多设置5个支付授权目录,且域名必须通过ICP备案;
3、头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
配置的具体规则是这样的:
1、
比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin/c.html,
那么:授权目录配置为 http://a.b.com/pay/weixin/
2、
比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin,
那么:授权目录配置为 http://a.b.com/pay/
3、
比如:调用以上JSSDK的页面地址为 http://a.b.com/pay,
那么:授权目录配置为 http://a.b.com/
4、如果有QueryString,自动忽略
比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin/c.html?name=mango,
那么:授权目录配置为 http://a.b.com/pay/weixin/
比如:调用以上JSSDK的页面地址为 http://a.b.com/#/pay/weixin/c.html?name=mango,
那么:授权目录配置为 http://a.b.com/
二、设置授权域名
开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。
UML图
商户系统和微信支付系统主要交互:
1、商户server调用统一下单接口请求订单,api参见公共api【统一下单API】
2、商户server接收支付通知,api参见公共api【支付结果通知API】
3、商户server查询支付结果,api参见公共api【查询订单API】
参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4
参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
注意事项:trade_type=JSAPI时(即公众号支付),openid必填,openid获取即关注着关注公众号可以获取openid
参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
注意事项:签名生成方式
参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
注意事项:签名生成方式
package cn.com.anne.bqj.trade.web.action.wechat.oauth;
import java.net.URLEncoder;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import cn.com.anne.bqj.trade.web.action.wechat.common.HttpClientConnectionManager;
import cn.com.anne.bqj.trade.web.action.wechat.oauth.vo.OauthAccessToken;
import cn.com.anne.bqj.trade.web.action.wechat.oauth.vo.OauthUser;
/**
* 微信网页授权管理类
*
* @className: OAuthManage
* @date: 2017-11-10 10:07
*/
@SuppressWarnings("deprecation")
public class OAuthManage {
// http客户端
public static DefaultHttpClient httpclient;
static {
httpclient = new DefaultHttpClient();
httpclient = (DefaultHttpClient) HttpClientConnectionManager.getSSLInstance(httpclient); // 接受任何证书的浏览器客户端
}
/**
* 获得引导关注者打开的页面地址
*
* @param appid
* 公众号的唯一标识
*
* @param redirect_uri
* 授权后重定向的回调链接地址
*
* @param scope
* 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
*
* snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、
*
* 所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
*
* @param state
* 重定向后会带上state参数,开发者可以填写a-z A-Z 0-9的参数值
*
*/
public static String getLeadOAuthUrl(String appid, String redirect_uri, String scope, String state) {
String do_redirect_uri = URLEncoder.encode(redirect_uri);
String oauth_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri="
+ do_redirect_uri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
return oauth_url;
}
/**
*
* 通过code换取网页授权access_token ,code是用户同意授权后获取的code,
*
* 这里通过code换取的网页授权access_token,与基础支持中的access_token不同。
*
* @param appid
* 公众号的唯一标识
*
* @param secret
* 公众号的appsecret
*
* @param code
* 填写第一步获取的code参数
*
*/
public static OauthAccessToken get_oauth_access_token_by_code(String appid, String secret, String code)
throws Exception {
HttpGet get = HttpClientConnectionManager
.getGetMethod("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret
+ "&code=" + code + "&grant_type=authorization_code");
HttpResponse response = httpclient.execute(get);
String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
JSONObject object = JSON.parseObject(jsonStr);
String access_token = object.getString("access_token");
int expires_in = object.getIntValue("expires_in");
String refresh_token = object.getString("refresh_token");
String openid = object.getString("openid");
String scope = object.getString("scope");
OauthAccessToken oauthAccessToken = new OauthAccessToken(access_token, expires_in, refresh_token, openid,
scope);
return oauthAccessToken;
}
/**
*
* 刷新access_token(如果需要)
*
* @param appid
* 公众号的唯一标识
*
* @param refresh_token
* 填写通过access_token获取到的refresh_token参数,
*
* refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
*
*/
public static OauthAccessToken get_oauth_access_token_by_refresh_token(String appid, String refresh_token)
throws Exception {
HttpGet get = HttpClientConnectionManager
.getGetMethod("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appid
+ "&grant_type=refresh_token&refresh_token=" + refresh_token);
HttpResponse response = httpclient.execute(get);
String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
JSONObject object = JSON.parseObject(jsonStr);
String access_token = object.getString("access_token");
int expires_in = object.getIntValue("expires_in");
String new_refresh_token = object.getString("refresh_token");
String openid = object.getString("openid");
String scope = object.getString("scope");
OauthAccessToken oauthAccessToken = new OauthAccessToken(access_token, expires_in, new_refresh_token, openid,
scope);
return oauthAccessToken;
}
/**
*
* 拉取用户信息(需scope为 snsapi_userinfo)
*
* @param access_token
* 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
*
* @param openid
* 用户的唯一标识
*
*/
public static OauthUser get_oauth_user_info(String access_token, String openid) throws Exception {
HttpGet get = HttpClientConnectionManager.getGetMethod("https://api.weixin.qq.com/sns/userinfo?access_token="
+ access_token + "&openid=" + openid + "&lang=zh_CN");
HttpResponse response = httpclient.execute(get);
String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
JSONObject object = JSON.parseObject(jsonStr);
;
String new_openid = object.getString("openid");
String nickname = object.getString("nickname");
String sex = object.getString("sex");
if ("1".equals(sex)) {
sex = "男";
} else if ("2".equals(sex)) {
sex = "女";
} else if ("0".equals(sex)) {
sex = "未知";
}
String province = object.getString("province");
String city = object.getString("city");
String country = object.getString("country");
String headimgurl = object.getString("headimgurl");
JSONArray privilege = object.getJSONArray("privilege");
OauthUser oauthUser = new OauthUser(new_openid, nickname, sex, city, country, province, headimgurl, privilege);
return oauthUser;
}
}
package cn.com.anne.bqj.trade.web.action.wechat.oauth.vo;
import com.alibaba.fastjson.JSONArray;
/**
* 授权者基本信息
* @className: OauthUser
* @date: 2017-11-10 10:06
*/
public class OauthUser {
private String openid;//用户的标识,对当前公众号唯一
private String nickname;//用户的昵称
private String sex;//用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
private String city;//用户所在城市
private String country;//用户所在国家
private String province;//用户所在省份
private String headimgurl;//用户头像,最后一个数值代表正方形头像大小
//(有0、46、64、96、132数值可选,0代表640*640正方形头像),
//用户没有头像时该项为空
private JSONArray privilege;//用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
public OauthUser(String openid, String nickname,
String sex, String city, String country,
String province, String headimgurl,
JSONArray privilege) {
this.openid = openid;
this.nickname = nickname;
this.sex = sex;
this.city = city;
this.country = country;
this.province = province;
this.headimgurl = headimgurl;
this.privilege = privilege;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getHeadimgurl() {
return headimgurl;
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public JSONArray getPrivilege() {
return privilege;
}
public void setPrivilege(JSONArray privilege) {
this.privilege = privilege;
}
}
package cn.com.anne.bqj.trade.web.action.wechat.oauth.vo;
/**
* 通过code换取网页授权access_token信息
* @className: OauthAccessToken
* @date: 2017-11-10 10:06
*/
public class OauthAccessToken {
private String access_token;//网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
private int expires_in;//access_token接口调用凭证超时时间,单位(秒)
private String refresh_token;//用户刷新access_token
private String openid;//用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
private String scope;//用户授权的作用域,使用逗号(,)分隔
public OauthAccessToken(String accessToken, int expiresIn,
String refreshToken, String openid, String scope) {
this.access_token = accessToken;
this.expires_in = expiresIn;
this.refresh_token = refreshToken;
this.openid = openid;
this.scope = scope;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String accessToken) {
access_token = accessToken;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expiresIn) {
expires_in = expiresIn;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refreshToken) {
refresh_token = refreshToken;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}