您的位置:首页 > 移动开发 > 微信开发

2017-9月微信公众号支付-Java详解

2020-04-05 18:20 1151 查看

 微信支付源代码

在此之前,先C麻瓜藤N遍,MD官方文档一半正确一半错误。言归正传,

微信支付整体流程:微信授权登录商户的公众号——微信支付的公众号配置——统一下单——微信js调起支付页面——输入密码支付——支付成功,异步回调URL处理商户的相应业务

一、业务场景:

  先看一下支付的业务场景:用户使用微信登录商户页面,点击支付按钮,调起微信支付,选择付款卡号,输入密码,完成支付,如图:

 

 

场景十分简单,不过步骤比较多,稍不注意就掉坑里了。

二、微信公众号支付的配置准备:

1)调用公众号支付,首先你得有个公众号,并且是服务号,如果是订阅号,请联系上司改为服务号。有了服务号,你需要去申请微信支付接口,具体可参考怎么申请微信支付接口(这个过程中会要求填写一个网页授权域名,下面的配置中会用到)。然后登录微信公众平台https://mp.weixin.qq.com,配置JS接口安全域名和网页授权域名:公众号设置——功能设置——JS接口安全域名,网页授权域名(注:会下载MP_verify_8QXHzPy7cuvgQiqU.txt文件,如图,将其放到项目根目录下)

 

两个域名配置成项目的根目录即可,

如图(注:mozhu.iok.la替换成你的网站域名,wap37lion替换成你的项目名称;开头没有http/https,结尾没有斜杠“/”),

 

 

2)登录后,修改支付授权目录:微信支付——开发配置——支付授权目录更改。

注:支付授权目录指的是你的支付页面所在目录。如图,

假设你的项目名为wTest2,你是在pay目录下,NewFile.html中调起的微信支付,外网访问NewFile.html的全路径为"http://你的网站域名/wTest2/pay/",那么你的目录是:"http://你的网站域名/wTest2/pay/";或者,如果你的网站域名就是你的项目根目录,外网访问NewFile.html的全路径为"http://你的网站域名/pay/",那目录就是"http://你的网站域名/pay/"。如图

 

三、微信网页授权登录公众号

授权可参考官方文档https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

1)用户同意授权,获取code

在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(服务号获得高级接口后,默认拥有scope参数中的snsapi_base和snsapi_userinfo),引导关注者打开如下页面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。 

参数说明

参数是否必须说明
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当作参数传到redirect_uri路径下。我们只需要定义好redirect_uri,在redirect_uri相应的控制器中获取code参数即可

示例代码(在控制器中):

package com.common;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("test/TestResource")
public class TestResource {
@GET
@Produces({ MediaType.APPLICATION_JSON })
@Path("wxLogin")//redirect_uri
public Response wxLogin(@QueryParam("code") String code){
System.out.println(code);
return null;
}
}

 

2)通过code获取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   

示例代码:

public static Map<String, Object> getAccessToken(String code) {
Map<String, Object> data = new HashMap();
try {
String appid="";//商户的APPID
String appsecret="";//商户的APPSECRET

String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
appid, appsecret, code);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String tokens = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(tokens,Map.class);
       

        String openId = data.get("openid")+"";
        String accessToken=data.get("access_token")+"";


  

} catch (Exception e) {
e.printStackTrace();
}
return data;
}

 

返回说明

正确时返回的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 用户授权的作用域,使用逗号(,)分隔

 

3)通过access_token获取用户信息

请求方法

http:GET(请使用https协议) https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 

参数说明

参数描述
access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
openid 用户的唯一标识
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

示例代码:

/**
* 获取用户信息
*
* @param accessToken
* @param openId
* @return
*/
public static Map<String, Object> getUserInfo(String accessToken, String openId) {
Map<String, Object> data = new HashMap();
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(responseStr,Map.class);
} catch (Exception ex) {
}
return data;
}

 

返回说明

正确时返回的JSON数据包如下:

{    "openid":" OPENID",  

 " nickname": NICKNAME,   

 "sex":"1",   

 "province":"PROVINCE"   

 "city":"CITY",   

 "country":"COUNTRY",    

 "headimgurl":    "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ

4eMsv84eavHiaiceqxibJxCfHe/46",  

"privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],    

 "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" 

参数描述
openid 用户的唯一标识
nickname 用户昵称
sex 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
province 用户个人资料填写的省份
city 普通用户个人资料填写的城市
country 国家,如中国为CN
headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
privilege 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
unionid 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。

 四、统一下单(请求参数较多,仔细看)

调用微信支付必须在后台生成统一下单接口,获取prepay_id。需要的参数,可参考官方文档统一下单。看不懂,不要急,往下看。

接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder

请求参数说明:

appid :应用ID,登陆微信公众号后台——开发——基本配置获取

 

mch_id : 微信支付商户号,登陆微信支付后台,即可看到

 

device_info:设备号,终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"

 

body:商品描述,商品或 支付单简要描述(先随便传个字符串)

 

trade_type:交易类型,取值如下:JSAPI,NATIVE,APP。我们这里使用的JSAPI。他们的区别,请参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2

 

nonce_str:随机字符串,不长于32位(参考算法https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3)

 

notify_url:通知地址,接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数,不能是相对地址。

out_trade_no:商户订单号,商户系统内部的订单号,32个字符内、可包含字母(参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2)

 

total_fee:总金额,订单总金额,单位为分

 

openid:用户标识,trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。(要是不知道这个从哪里来的话,没关系。微信不是给咱写文档了吗https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4)

还有最最重要的角色总要在最后登场。

sign:签名,官方给的签名算法https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3。

key:获取方法:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

 

 

示例代码:

/**
* 微信支付 获得PrePayId
*
* @param payURL
*            调用支付的完整URL request.getRequestURL().toString();
* @param nonce_str
*            随机字符串 用WXPayUtil生成
* @param body
*            商品或支付单简要描述
* @param out_trade_no
*            商户系统内部的订单号,32个字符内、可包含字母 。用WXPayUtil生成
* @param total_fee
*            订单总金额,单位为分
* @param IP
*            APP和网页支付提交用户端ip
* @param notify_url
*            接收微信支付异步通知回调地址
* @param openid
*            用户openId request.getSession().getAttribute("openid")
* @throws IOException
*/
public Map<String, Object> wxPay(String payURL,String nonce_str, String body, String out_trade_no, String total_fee, String ip,
String notify_url, String openId) throws Exception {
WxPaySendDataPO result = new WxPaySendDataPO();
Map<String,Object>map=new HashMap<String, Object>();

//统一下单
Map resultMap=WXPayUtil.unifiedorder(nonce_str, body, out_trade_no, total_fee, ip, notify_url, openId);
String timeStamp = WXPayUtil.getTimeStamp();//时间戳
String result_code=resultMap.get("result_code")+"";
String return_code=resultMap.get("return_code")+"";
if("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){
//result.setResult_code(result_code);
result.setAppid(WXUtil.APPID);
result.setTimeStamp(timeStamp);
result.setNonce_str(nonce_str);
result.setPackageStr("prepay_id="+resultMap.get("prepay_id"));
result.setSignType("MD5");
//
//            // WeChatUtil.unifiedorder(.....) 下单操作中,也有签名操作,那个只针对统一下单,要区别于下面的paySign
//            //第二次签名,将微信返回的数据再进行签名
SortedMap<Object,Object> signMap = new TreeMap<Object,Object>();
signMap.put("appId", WXUtil.APPID);
signMap.put("timeStamp", timeStamp);
signMap.put("nonceStr", nonce_str);
signMap.put("package", "prepay_id="+resultMap.get("prepay_id"));  //注:看清楚,值为:prepay_id=xxx,别直接放成了wxReturnData.getPrepay_id()
signMap.put("signType", "MD5");
String paySign = WXSignUtil.createSign(signMap,  WXUtil.KEY);//支付签名
result.setSign(paySign);

String signature=WXUtil.getSHA1Sign( payURL, timeStamp, nonce_str);
map.put("signature",signature);
map.put("code", 1);
map.put("msg", "success");
map.put("data", result);

}else{
map.put("code", 2);
map.put("msg", "获取prepay_id失败");
map.put("data", "");
}
return map;
}

WxPaySendDataPO:

package com.util;

public class WxPaySendDataPO {
//公众账号ID
private String appid;
//附加数据
private String attach;
//商品描述
private String body;
//商户号
private String mch_id;
//随机字符串
private String nonce_str;
//通知地址
private String notify_url;
//商户订单号
private String out_trade_no;
//标价金额
private String total_fee;
//交易类型
private String trade_type;
//终端IP
private String spbill_create_ip;
//用户标识
private String openid;
//签名
private String sign;
//预支付id
private String prepay_id;
//签名类型:MD5
private String signType;
//时间戳
private String timeStamp;
//微信支付时用到的prepay_id
private String packageStr;

private String return_code;
private String return_msg;
private String result_code;

private String bank_type;
private Integer cash_fee;
private String fee_type;
private String is_subscribe;
private String time_end;
//微信支付订单号
private String transaction_id;
private String ip;
private Integer coupon_count;
private Integer coupon_fee;
private Integer coupon_fee_0;
private String coupon_type_0;
private String coupon_id_0;

public String getCoupon_type_0() {
return coupon_type_0;
}
public void setCoupon_type_0(String coupon_type_0) {
this.coupon_type_0 = coupon_type_0;
}
public String getCoupon_id_0() {
return coupon_id_0;
}
public void setCoupon_id_0(String coupon_id_0) {
this.coupon_id_0 = coupon_id_0;
}
public Integer getCoupon_fee_0() {
return coupon_fee_0;
}
public void setCoupon_fee_0(Integer coupon_fee_0) {
this.coupon_fee_0 = coupon_fee_0;
}
public Integer getCoupon_fee() {
return coupon_fee;
}
public void setCoupon_fee(Integer coupon_fee) {
this.coupon_fee = coupon_fee;
}
public Integer getCoupon_count() {
return coupon_count;
}
public void setCoupon_count(Integer coupon_count) {
this.coupon_count = coupon_count;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getBank_type() {
return bank_type;
}
public void setBank_type(String bank_type) {
this.bank_type = bank_type;
}
public Integer getCash_fee() {
return cash_fee;
}
public void setCash_fee(Integer cash_fee) {
this.cash_fee = cash_fee;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getIs_subscribe() {
return is_subscribe;
}
public void setIs_subscribe(String is_subscribe) {
this.is_subscribe = is_subscribe;
}
public String getTime_end() {
return time_end;
}
public void setTime_end(String time_end) {
this.time_end = time_end;
}
public String getTransaction_id() {
return transaction_id;
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTotal_fee() {
return total_fee;
}
public void setTotal_fee(String total_fee) {
this.total_fee = total_fee;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}

public String getPackageStr() {
return packageStr;
}
public void setPackageStr(String packageStr) {
this.packageStr = packageStr;
}
@Override
public String toString() {
return "WxPaySendData [appid=" + appid + ", attach=" + attach
+ ", body=" + body + ", mch_id=" + mch_id + ", nonce_str="
+ nonce_str + ", notify_url=" + notify_url + ", out_trade_no="
+ out_trade_no + ", total_fee=" + total_fee + ", trade_type="
+ trade_type + ", spbill_create_ip=" + spbill_create_ip
+ ", openid=" + openid + ", sign=" + sign + ", prepay_id="
+ prepay_id + ", signType=" + signType + ", timeStamp="
+ timeStamp + ", packageStr=" + packageStr + ", return_code="
+ return_code + ", return_msg=" + return_msg + ", result_code="
+ result_code + ", bank_type=" + bank_type + ", cash_fee="
+ cash_fee + ", fee_type=" + fee_type + ", is_subscribe="
+ is_subscribe + ", time_end=" + time_end + ", transaction_id="
+ transaction_id + ", ip=" + ip + ", coupon_count="
+ coupon_count + ", coupon_fee=" + coupon_fee
+ ", coupon_fee_0=" + coupon_fee_0 + ", coupon_type_0="
+ coupon_type_0 + ", coupon_id_0=" + coupon_id_0 + "]";
}
}
WxPaySendDataPO

 

 

MD5Util:

package com.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* md5加密算法实现
*/
public class MD5Util {

private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

/**
* md5加密
*
* @param text 需要加密的文本
* @return
*/
public static String encode(String text) {
try {// aes rsa
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] result = md.digest(text.getBytes()); // 对文本进行加密
// b
// 000000..0000011111111
StringBuilder sb = new StringBuilder();
for (byte b : result) {
int i = b & 0xff ; // 取字节的后八位有效值
String s = Integer.toHexString(i);
if (s.length() < 2) {
s = "0" + s;
}
sb.append(s);
}

// 加密的结果
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// 找不到该算法,一般不会进入这里
e.printStackTrace();
}

return "";
}

public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) {

}
return resultString;
}

private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));

return resultSb.toString();
}

private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
}
MD5Util

 

 

WXPayUtil:

package com.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.dom4j.DocumentException;

public class WXPayUtil {

/**
* 统一下单
* 获得PrePayId
* @param body   商品或支付单简要描述
* @param out_trade_no 商户系统内部的订单号,32个字符内、可包含字母
* @param total_fee  订单总金额,单位为分
* @param IP    APP和网页支付提交用户端ip
* @param notify_url 接收微信支付异步通知回调地址
* @param openid 用户openId
* @throws IOException
* @throws DocumentException
*/
public static Map unifiedorder(String nonce_str,String body,String out_trade_no,String total_fee,String ip,String notify_url,String openId)throws IOException, DocumentException {
/**
* 设置访问路径
*/
HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
//String nonce_str = getNonceStr();//随机数据
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
/**
* 组装请求参数
* 按照ASCII排序
*/
parameters.put("appid",WXUtil.APPID);
parameters.put("body", body);
parameters.put("mch_id", WXUtil.MCH_ID );
parameters.put("nonce_str", nonce_str);
parameters.put("out_trade_no", out_trade_no);
parameters.put("notify_url", notify_url);
parameters.put("spbill_create_ip", ip);
parameters.put("total_fee",total_fee );
parameters.put("trade_type",WXUtil.TRADE_TYPE );
parameters.put("openid", openId);

String sign = WXSignUtil.createSign(parameters, WXUtil.KEY);

/**
* 组装XML
*/
StringBuilder sb = new StringBuilder("");
sb.append("<xml>");
setXmlKV(sb,"appid",WXUtil.APPID);
setXmlKV(sb,"body",body);
setXmlKV(sb,"mch_id",WXUtil.MCH_ID);
setXmlKV(sb,"nonce_str",nonce_str);
setXmlKV(sb,"notify_url",notify_url);
setXmlKV(sb,"out_trade_no",out_trade_no);
setXmlKV(sb,"spbill_create_ip",ip);
setXmlKV(sb,"total_fee",total_fee);
setXmlKV(sb,"trade_type",WXUtil.TRADE_TYPE);
setXmlKV(sb,"sign",sign);
setXmlKV(sb,"openid",openId);
sb.append("</xml>");

StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//这个处理是为了防止传中文的时候出现签名错误
httppost.setEntity(reqEntity);
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(httppost);
String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));

return XMLUtil.doXMLParse(strResult);

}

//获得随机字符串
public static String getNonceStr(){
Random random = new Random();
return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
}

//插入XML标签
public static StringBuilder setXmlKV(StringBuilder sb,String Key,String value){
sb.append("<");
sb.append(Key);
sb.append(">");

sb.append(value);

sb.append("</");
sb.append(Key);
sb.append(">");

return sb;
}

//解析XML  获得 PrePayId
public static String getPrePayId(String xml){
int start = xml.indexOf("<prepay_id>");
int end = xml.indexOf("</prepay_id>");
if(start < 0 && end < 0){
return null;
}
return xml.substring(start + "<prepay_id>".length(),end).replace("<![CDATA[","").replace("]]>","");
}

//商户订单号
public static String getOut_trade_no(String start,String end){
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String str=start+df.format(new Date())+end;
String regex="\\w{14,32}";
if(!str.matches(regex)){
throw new RuntimeException("订单号格式错误");
}
return  str;
}

//时间戳
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}

//随机4位数字
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) (random * num);
}

public static String inputStream2String(InputStream inStream, String encoding){
String result = null;
try {
if(inStream != null){
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[1024];
int count = -1;
while((count = inStream.read(tempBytes, 0, 1024)) != -1){
outStream.write(tempBytes, 0, count);
}
tempBytes = null;
outStream.flush();
result = new String(outStream.toByteArray(), encoding);
}
} catch (Exception e) {
result = null;
}
return result;
}
/**
* 生成length为随机数
* @param length
* @return
*/
public static String getRandomStr(int length) {
String val = "";
Random random = new Random();
for (int i = 0; i < length; i++) {
// 输出字母还是数字
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
// 字符串
if ("char".equalsIgnoreCase(charOrNum)) {
// 取得大写字母还是小写字母
int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
val += (char) (choice + random.nextInt(26));
} else if ("num".equalsIgnoreCase(charOrNum)) { // 数字
val += String.valueOf(random.nextInt(10));
}
}
return val;
}

public static void main(String[] args) throws IOException, DocumentException {
//        System.out.println(getOut_trade_no("",""));
//        //openID
String openId="";
//
//        //统一下单
String nonce_str=getNonceStr();
String str="|";
String out_trade_no = WXPayUtil.getOut_trade_no("testpay",WXPayUtil.getRandomStr(6));
Map strResult = WXPayUtil.unifiedorder(nonce_str,"test", out_trade_no, "1", "127.0.0.1", "http://www.baidu.com",openId);
System.out.println(strResult);
//        //Map map=XMLUtil.doXMLParse(strResult);
//        SortedMap<Object,Object> resultMap=(TreeMap)XMLUtil.doXMLParse(strResult);
//        System.out.println(resultMap);
//        String regex="[0-9a-zA-Z\\_\\-\\*\\|@]{1,32}";
//        System.out.println(nonce_str.length());
System.out.println(out_trade_no.length());
}

}
WXPayUtil

 

WXSignUtil:

package com.util;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

import com.job37.jser.core.util.MD5Util;

public class WXSignUtil {

/**
  * 创建签名
  * @param parameters
  * @param key
  * @return
  */
@SuppressWarnings("rawtypes")
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;
}
}
WXSignUtil

 

WXUtil:

package com.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.json.JSONObject;

import com.alibaba.dubbo.common.json.JSON;
import com.job37.jser.core.constants.GlobalConstants;
import com.job37.jser.core.exceptions.CustomGlobalException;
import com.job37.jser.core.util.RedisUtil;

import redis.clients.jedis.Jedis;

import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

public class WXUtil {
//服务号ID
public static final String APPID="";
//服务号密码
public static final String APPSECRET="";
//上齐猎微信支付商户号
public static final String MCH_ID="";
public static final String TRADE_TYPE="JSAPI";
//api密钥
public static final String KEY="";
//检查微信登录的URL
public static final String  WXLOGIN_URL="";
//微信支付,送贺礼成功,异步回调的URL
public static final String GIFTAGENT_NOTIFY_URL = "";

/*******************************************************************************/

/**
* 获取请求用户信息的access_token
*
* @param code
* @return
*/
public static Map<String, Object> getUserInfoAccessToken(String code) {
Map<String, Object> data = new HashMap();
try {
String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
APPID, APPSECRET, code);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String tokens = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(tokens,Map.class);} catch (Exception e) {
e.printStackTrace();
}
return data;
}

public static Map<String, Object> getAccessToken(String code) {
Map<String, Object> data = new HashMap();
try {
String appid="";//商户的APPID
String appsecret="";//商户的APPSECRET

String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
appid, appsecret, code);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String tokens = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(tokens,Map.class);} catch (Exception e) {
e.printStackTrace();
}
return data;
}

/**
* 获取用户信息
*
* @param accessToken
* @param openId
* @return
*/
public static Map<String, Object> getUserInfo(String accessToken, String openId) {
Map<String, Object> data = new HashMap();
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(responseStr,Map.class);
} catch (Exception ex) {
}
return data;
}

/**
* 生成用于获取access_token的Code的Url
*
* @param redirectUrl
* @return
*/
public static String getRequestCodeUrl(String redirectUrl) {
return String.format("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect",
APPID, redirectUrl, "snsapi_userinfo", "xxxx_state");
}

public static Map<String, Object> testGetUserInfo(String accessToken, String openId) {
Map<String, Object> data = new HashMap();
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
String url2="https://api.weixin.qq.com/cgi-bin/user/info?access_token="+ accessToken + "&openid=" + openId + "&lang=zh_CN";;
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(httpEntity, "utf-8");
data=JSON.parse(responseStr,Map.class);} catch (Exception e) {
e.printStackTrace();
}
return data;
}

public static String getTicket(String accessToken){
String ticket = null;
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ accessToken +"&type=jsapi";//这个url链接和参数不能变
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(httpEntity, "utf-8");
//            System.out.println(responseStr);
Map  data=JSON.parse(responseStr,Map.class);
ticket=data.get("ticket")+"";
} catch (Exception e) {
e.printStackTrace();
}
return ticket;
}

public static String getAccessToken(){
String token="";
RedisUtil ut=new RedisUtil();
Jedis jedis=ut.getJedis();
try {
jedis.select(6);
token=jedis.get("ACCESS_TOKEN");
if(token==null||token.trim().equals("")||"null".equals(token)){
token=refreshAccessToken();
jedis.set("ACCESS_TOKEN", token);
jedis.expire("ACCESS_TOKEN", 7000);
}
} catch (Exception e) {
throw e;
}finally{
ut.returnResource(jedis);
}
return token;
}

public static String refreshAccessToken() {
String access_token = "";
String grant_type = "client_credential";//获取access_token填写client_credential
//这个url链接地址和参数皆不能变
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type="+grant_type+"&appid="+APPID+"&secret="+APPSECRET;

try {

DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String responseStr = EntityUtils.toString(httpEntity, "utf-8");
//System.out.println(responseStr);
Map  data=JSON.parse(responseStr,Map.class);
access_token=data.get("access_token")+"";
} catch (Exception e) {
e.printStackTrace();
}
return access_token;
}

/**
* 获取微信配置的签名
* @param jsapi_ticket
* @param url
* @param timeStamp
* @param nonce_str
* @return
*/
public static String getSHA1Sign(String jsapi_ticket, String url,String timeStamp,String nonce_str){
String decript = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"&timestamp=" + timeStamp +
"&url=" + url;
return SHA1(decript);

}

/**
* 获取微信配置的签名
* @param url
* @param timeStamp
* @param nonce_str
* @return
*/
public static String getSHA1Sign( String url,String timeStamp,String nonce_str){
String token=getAccessToken();
String ticket=getTicket(token);
String decript = "jsapi_ticket=" + ticket +
"&noncestr=" + nonce_str +
"&timestamp=" + timeStamp +
"&url=" + url;
return SHA1(decript);

}

public static String SHA1(String decript) {
try {
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
//获取微信授权登录的URL
public static String getLoginRedirectUrl(String redirectUrl){
//        String  redirect_uri=TEST_WXLOGIN_URL+redirectUrl;
String  redirect_uri=WXLOGIN_URL+redirectUrl;
try {
redirect_uri=java.net.URLEncoder.encode(redirect_uri,"utf-8");
} catch (UnsupportedEncodingException e) {
throw new CustomGlobalException("参数错误");
}
return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+APPID+"&redirect_uri="+redirect_uri+"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
}

/**
* 获取完整的URL
* @param request
* @param midfix 链接的中间字符串
* @return
*/
public static String getWholeURL(HttpServletRequest request,String midfix){
return GlobalConstants.SHARE_URL+midfix+"?" + request.getQueryString();
}

}
WXUtil

 

XMLUtil:

package com.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class XMLUtil {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
* @throws DocumentException
*/
public static Map doXMLParse(String strxml) throws  IOException, DocumentException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

if(null == strxml || "".equals(strxml)) {
return null;
}

Map m = new TreeMap();

InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXReader builder = new SAXReader();
Document doc =  builder.read(in);
Element root = doc.getRootElement();
Iterator it = root.elementIterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = e.getText();
//        List children = e.getChildren();
//        if(children.isEmpty()) {
//            v = e.getTextNormalize();
//        } else {
//            v = XMLUtil.getChildrenText(children);
//        }

m.put(k, v);
}

//关闭流
in.close();

return m;
}

/**
* 获取子结点的xml
* @param children
* @return String
* @throws DocumentException
* @throws IOException
*/
//public static String getChildrenText(List children) {
//    StringBuffer sb = new StringBuffer();
//    if(!children.isEmpty()) {
//        Iterator it = children.iterator();
//        while(it.hasNext()) {
//            Element e = (Element) it.next();
//            String name = e.getName();
//            String value = e.getTextNormalize();
//            List list = e.getChildren();
//            sb.append("<" + name + ">");
//            if(!list.isEmpty()) {
//                sb.append(XMLUtil.getChildrenText(list));
//            }
//            sb.append(value);
//            sb.append("</" + name + ">");
//        }
//    }
//
//    return sb.toString();
//}
public static void main(String[] args) throws IOException, DocumentException {
String xml="<xml><id>555</id>"+
"<name>hello</name></xml>";
Map<String,Object>m=XMLUtil.doXMLParse(xml);
System.out.println(m);
}
}
XMLUtil

 

 

五、H5页面调起微信支付的js

1)要调用微信的支付js,必须在页面引入<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>,并且配置微信支付

微信js配置(wx.js)如下:

var paySign ;
var appId;
var timeStamp ;
var nonceStr ;
var packageStr ;
var signType ;
var signature;

function wxPay(){
//console.log(signature);
//通过config接口注入权限验证配置
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,公众号的唯一标识
timestamp:timeStamp , // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,这里只写支付的
});
//通过ready接口处理成功验证
wx.ready(function(){
//wx.hideOptionMenu();//隐藏右边的一些菜单
wx.chooseWXPay({
timestamp: timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: nonceStr, // 支付签名随机串,不长于 32 位
package: packageStr, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: paySign, // 支付签名
success: function (res) {
// 支付成功后的回调函数
layer.closeAll();
alert("支付成功");
},
//如果你按照正常的jQuery逻辑,下面如果发送错误,一定是error,那你就太天真了,当然,jssdk文档中也有提到
fail: function(res) {
//接口调用失败时执行的回调函数。
layer.closeAll();
alert("支付失败,请稍后再试");
},
complete: function(res) {
//接口调用完成时执行的回调函数,无论成功或失败都会执行。
//alert("complete");
},
cancel: function(res) {
//用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
//alert("cancel");
},
trigger: function(res) {
//监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。
//alert("trigger");
}
});

});
}

function testPay(){
//调用微信支付
//callpay();
var url='../../../guest/WXResource/getPrepayid';
var giftMoney=$("#giftMoney").val();
var param={giftMoney:giftMoney,identityId:identityId};
$.post(url,param,function(res){
var code=res.code;
var data=res.data;
if(code==1){
paySign=data.sign ;
appId=data.appid;
timeStamp=data.timeStamp ;
nonceStr=data.nonce_str ;
packageStr=data.packageStr ;
signType=data.signType ;
signature=res.signature;
wxPay();
//callpay();
}else{
layer.open({
content:res.msg
});
}
});
}

配置好后,在页面写个点击事件,调用testPay函数,就会初始化微信支付的配置,随后调起js支付。

六、异步回调

支付成功后,要在异步回调的URL(这个在统一下单中就定义了notify_url)中处理相应的业务

示例回调代码:

/**
* 支付回调接口
* @param request
* @return
*/
@Path("payCallback")
@POST
public void callBack(){
//        System.out.println("gift_payCallBack...");
response.setContentType("text/xml;charset=UTF-8");
try {
InputStream is = request.getInputStream();
String resultStr = IOUtils.toString(is, "UTF-8");
if("".equals(resultStr)){
System.out.println("fail:result is null");
response.getWriter().write("<xm><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数错误!]]></return_msg></xml>");
return ;
}
//解析xml
SortedMap<Object,Object> resultMap=(TreeMap)XMLUtil.doXMLParse(resultStr);
//                System.out.println(resultMap);
String sign = resultMap.get("sign")+"";
String sign2 = WXSignUtil.createSign(resultMap, WXUtil.KEY);
String result_code=resultMap.get("result_code")+"";
String return_code=resultMap.get("return_code")+"";
if(sign.equals(sign2)){//校验签名,两者需要一致,防止别人绕过支付操作,不付钱直接调用你的业务,不然,哈哈,你老板会很开心的 233333.。。。
if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
//业务逻辑(先判断数据库中订单号是否存在,并且订单状态为未支付状态)
try {
//resultMap.put("openid", request.getSession().getAttribute("openid"));

} catch (Exception e) {
e.printStackTrace();
}
//request.setAttribute("out_trade_no", out_trade_no);
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}else{
response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失败]]></return_msg></xml>");
}
}else{
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名校验失败]]></return_msg></xml>");
}
response.getWriter().flush();
response.getWriter().close();
return;
} catch (Exception e) {
e.printStackTrace();
}
}

 

 

 

转载于:https://www.cnblogs.com/hyj-blog/p/7500901.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
dawenluan7688 发布了0 篇原创文章 · 获赞 0 · 访问量 11 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: