1. 准备工作
----> 微信提供的appid、 appSecret、payKey、 MchId、token
这些比较容易获取,过程此处省略.....
----->配置微信的OAuth2.0网页授权回调页面的域名。
这个比较难找,我看文档看了1天没有找到,郁闷致死。 最后登录到微信公众号点左侧菜单,基本所有的菜单都点了一遍才被我发现,当时哭的心都有了。所以直接上图,明确位置。如下图:只需要点修改把自己的域名放进去就行。如m.baidu.com 或者 baidu.com
-----> 配置微信公众号支付的授权目录
这个比较好找,直接上图:直接点修改,页面给的有提示。
以上工作都准备完毕 就剩下些代码了。
2. 功能实现
实现流程
--> 统一下单API查看微信提供的文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
--> 页面授权获取openid
授权文档 http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
甭看他们啰嗦 直接拉到页面的中部看目录部分。走到目录中的第二步就能获取用户的openid
第一步:重定向用户授权的URL。
控制器重定向一下地址:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
参数:
这里要注意的是 redirect_uri。 这个地址中的域名一定要是上面准备工作【配置微信的OAuth2.0网页授权回调页面的域名】提到的域名。
注意:只有参数scope=snsapi_userinfo 的时候才会出现需要用户点击授权的页面,其它的不出现。
第二步:通过code换取网页授权access_token
在这个回调地址的控制器中获取返回的参数code。实现代码如下:
public static AccessTokenOAuth getWeiXinOAuthAccessToken(String code){
AccessTokenOAuth token = null;
StringBuffer sb = new StringBuffer(WeiXinConfig.getOAuthAccessTokenURL);
sb.append("?appid=").append(WeiXinConfig.AppId).append("&secret=").append(WeiXinConfig.AppSecret);
sb.append("&code=").append(code).append("&grant_type=authorization_code");
try {
URL url = new URL(sb.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream is =url.openStream();
//转换返回值
String returnStr = SendMsgUtil.convertStreamToString(is);
// 返回结果为{"access_token":"ACCESS_TOKEN","expires_in":7200}
Gson gson = new Gson();
token = gson.fromJson(returnStr, AccessTokenOAuth.class);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return token;
}
AccessTokenOAuth 的实体代码如下:
public class AccessTokenOAuth implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9011346947427899815L;
private String access_token; //网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
private Long expires_in;
private String refresh_token;
private String openid;
private String scope;
private String errcode;
private String errmsg;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public Long getExpires_in() {
return expires_in;
}
public void setExpires_in(Long expires_in) {
this.expires_in = expires_in;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
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;
}
public String getErrcode() {
return errcode;
}
public void setErrcode(String errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
}
此时就可以从token中获取到用户的openid
第三步:调用微信提供的统一下单API
请求的参数trade_type = JSAPI Openid = 获取用户的openid
微信的统一下单API返回结果中 如果return_code="SUCCESS" 获取下单成功,
然后根据返回的参数生成签名 此签名不同与统一下单 的签名,此签名主要用于页面通过JS调用微信的H5支付请求。
组装签名的代码:
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("appId", WeiXinConfig.AppId);
paramMap.put("timeStamp", String.valueOf(new Date().getTime()));
paramMap.put("nonceStr", WeiXinConfig.getRandomStr());
paramMap.put("package", "prepay_id="+returnXML.get("prepay_id"));
paramMap.put("signType", "MD5");
String _signData = SignUtil.genSignData(JSON.parseObject(JSON.toJSONString(paramMap)));
_signData +="&key="+WeiXinConfig.PayKey;
String _sign = SignUtil.addSignMD5(_signData);
签名需要进行MD5加密。
微信签名工具类
public class SignUtil {
private static String token = "XXXXXXX"; //在微信公众平台配置
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 获取jsapi的签名
* @param jsapiTicket
* @return
*/
public static String getSignature(String jsapiTicket){
String[] arr = new String[] {jsapiTicket};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return tmpStr;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
/**
* 签名字符串
* @param sign_src 需要签名的字符串
* @return
*/
public static String addSignMD5(String sign_src){
if (sign_src == null) return "";
try
{
String md5 = DigestUtils.md5Hex(getContentBytes(sign_src, "UTF-8")).toUpperCase();
return md5;
}catch (Exception e){
return "";
}
}
/**
* 校验MD5签名
* @param text 需要签名的字符串
* @param sign 签名结果
* @return
*/
public static boolean verifySignMD5(String text, String sign) {
String mysign = DigestUtils.md5Hex(getContentBytes(text, "UTF-8")).toUpperCase();
if(mysign.equals(sign)) {
return true;
}
else {
return false;
}
}
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
public static String genSignData(JSONObject jsonObject)
{
StringBuffer content = new StringBuffer();
// 按照key做首字母升序排列
List<String> keys = new ArrayList<String>(jsonObject.keySet());
Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < keys.size(); i++)
{
String key = (String) keys.get(i);
if ("sign".equals(key))
{
continue;
}
String value = jsonObject.getString(key);
// 空串不参与签名
if (isnull(value))
{
continue;
}
content.append((i == 0 ? "" : "&") + key + "=" + value);
}
String signSrc = content.toString();
if (signSrc.startsWith("&"))
{
signSrc = signSrc.replaceFirst("&", "");
}
return signSrc;
}
public static boolean isnull(String str)
{
if (null == str || str.equalsIgnoreCase("null") || str.equals(""))
{
return true;
} else
return false;
}
}
最后一步在页面通过js发起微信的H5支付请求
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr" :nonceStr, //随机串
"package":paypackage,
"signType":"MD5", //微信签名方式:
"paySign":sign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
Util.alert("支付成功!!",5000);
}else if(res.err_msg=="get_brand_wcpay_request:cancel"){//支付过程中用户取消
Util.alert("您取消了支付!!",5000);
}else if(res.err_msg=="get_brand_wcpay_request:fail"){//支付失败
Util.alert("支付失败!!",-1);
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
来源:oschina
链接:https://my.oschina.net/u/819687/blog/711905