之前做了微信支付,一直也没静下心来整理一下微信支付这块的内容,今天晚上有空,整理了一下,
微信支付流程我就不详细的介绍了,主要是代码部分,从请求开始到微信支付成功
1.首先,页面js部分,需要成功拉取支付请求那么就需要一些微信的内容
getWxConfig();
function getWxConfig(){
var url =window.location.href; //返回当前的页面的url
var data = {"url" : url};
$.ajax({
url:'getwxjsconfig.do',
type:'get',
data:data,
dataType:'json',
async:false,
success:function(result){
var dataJson = eval(result);
appId = dataJson.appid;
timestamp = dataJson.timestamp;
nonceStr = dataJson.nonceStr;
signature = dataJson.signature;
},
error:function(XMLResponse){
// alert("error:"+XMLResponse.responseText);
}
});
}
相对应的后台逻辑代码
Controller
@RequestMapping("getwxjsconfig")
public void getConfig(String url,HttpServletResponse response,HttpServletRequest request) throws IOException{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
Map<String, String> ret = wxmanager.getJsConfig(url);
System.out.println("dataUrl:" + url);
JSONObject config = JSONObject.fromObject(ret);
// System.out.println(config);
writer.print(config);
writer.flush();
writer.close();
}
采用最原始的流来会写数据
service层
/**
* 获取微信jsapi配置
* @param url 调用接口的地址
*/
public Map<String, String> getJsConfig(String url) {
String appId = config.getAppID();
String accessToken = "";
String jspaiTicket = "";
accessToken = getAccessToken(appId,config.getAppSecret());
jspaiTicket = getJsApiTicket(accessToken);
// System.out.println(accessToken);
//获取签名
Map<String, String> ret = Sign.sign(jspaiTicket,url);
ret.put("appid", appId);
return ret;
}
/**
* 获取access_token
* @param appID 公众号appid
* @param appSecret 开发者密码
*/
public String getAccessToken(String appID, String appSecret) {
String accessToken = WxConfig.getAccessToken();
String accessTime = WxConfig.getAccessTime();
int tokenExpiresIn = WxConfig.getTokenExpiresIn();
long accessTimeL = 0;
long currentTimeL = 0;
currentTimeL = new Date().getTime();
accessTimeL = Util.getTimeFromString("yyyyMMddHHmmss", accessTime);
String currentTime = "";
int expiresIn = 0;
if(accessToken.equals("") || accessTime.equals("") || (currentTimeL - accessTimeL) / 1000 >= tokenExpiresIn){
String url = WxConfig.getAccessUrl();
url = url.replace("APPID", appID);
url = url.replace("APPSECRET", appSecret);
currentTime= Util.getTime("yyyyMMddHHmmss");
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response = null;
String content = "";
try {
//执行get方法
response = httpclient.execute(httpget);
if(response.getStatusLine().getStatusCode()==200){
content = EntityUtils.toString(response.getEntity(),"utf-8");
JSONObject baseinfo=JSONObject.fromObject(content);
accessToken = baseinfo.getString("access_token");
expiresIn = baseinfo.getInt("expires_in") - 1800;
WxConfig.setAccessToken(accessToken);
WxConfig.setTokenExpiresIn(expiresIn);
WxConfig.setAccessTime(currentTime);
}
} catch (ClientProtocolException e) {
Util.logException("获取access_token失败" , e);
} catch (IOException e) {
Util.logException("获取access_token失败" , e);
} catch (JSONException e){
Util.logException("获取access_token失败 " + " content:" + content , e);
} finally{
try {
if (response != null){
response.close();
}
} catch (IOException e) {
Util.logException("response关闭异常", e);
}
}
}
// System.out.println("token:" + accessToken + " time:" + accessTime + " expires_in:" +tokenExpiresIn);
return accessToken;
}
/**
* 获取jsapi_ticket
*/
public String getJsApiTicket(String token) {
String jspaiTicket = WxConfig.getJsapiTicket();
String tickeTime = WxConfig.getTicketTime();
int ticketExpiresIn = WxConfig.getTicketExpiresIn();
long tickeTimeL = 0;
long currentTimeL = 0;
currentTimeL = new Date().getTime();
String currentTime = "";
int expiresIn = 0;
currentTime= Util.getTime("yyyyMMddHHmmss");
tickeTimeL = Util.getTimeFromString("yyyyMMddHHmmss", tickeTime);
if(jspaiTicket.equals("") || tickeTime.equals("") || (currentTimeL - tickeTimeL) / 1000 >= ticketExpiresIn){
String url = WxConfig.getTicketUrl();
// System.out.println(url);
url = url.replace("ACCESS_TOKEN", token);
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response = null;
String content = "";
try {
//执行get方法
response = httpclient.execute(httpget);
if(response.getStatusLine().getStatusCode()==200){
content = EntityUtils.toString(response.getEntity(),"utf-8");
JSONObject baseinfo=JSONObject.fromObject(content);
// System.out.println(baseinfo.getInt("errcode"));
if(baseinfo.getInt("errcode") == 40001){
WxConfig.setTokenExpiresIn(0);
}else{
jspaiTicket = baseinfo.getString("ticket");
expiresIn = baseinfo.getInt("expires_in") - 1800;
WxConfig.setJsapiTicket(jspaiTicket);
WxConfig.setTicketExpiresIn(expiresIn);
WxConfig.setTicketTime(currentTime);
}
}
} catch (ClientProtocolException e) {
Util.logException("获取jsapiticket失败" , e);
} catch (IOException e) {
Util.logException("获取jsapiticket失败" , e);
} finally{
try {
if (response != null){
response.close();
}
} catch (IOException e) {
Util.logException("response关闭异常", e);
}
}
}
// System.out.println(jspaiTicket);
return jspaiTicket;
}
到这里 js方法必须验证通过,getWxConfig();
也就是说这个方法中返回值
success:function(result){
var dataJson = eval(result);
appId = dataJson.appid;
timestamp = dataJson.timestamp;
nonceStr = dataJson.nonceStr;
signature = dataJson.signature;
},
这些是必须有返回值的用来校验的,否则微信支付无法拉取,
然后页面上就应该添加校验的js了
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature ,// 必填,签名
jsApiList: [
'chooseWXPay',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'hideMenuItems'
] // 必填,需要使用的JS接口列表
});
}
如果说这个时候都通过了话,那么恭喜你,可以正式的去执行下单请求了
//下单请求
wx.ready(function(){
$("#submit").click(function(){
tradeno = "";
newpay();
});
});
function newpay(){
payfee = 0.01;
var data = "user=" + userid + "&body=" + "测试" + "&fee=" + payfee;
$.ajax({
url:'newpay.do',
type:'post',
data:data,
dataType:'json',
success:function(result){
// alert(result.tradeno);
tradeno = result.tradeno;
alert(result.timeStamp+"--"+result.nonceStr+"--"+result.package+"--"+result.signType+"--"+result.paySign);
// alert(tradeno);
wx.chooseWXPay({
timestamp: result.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: result.nonceStr, // 支付签名随机串,不长于 32 位
package: result.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: result.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: result.paySign, // 支付签名
success: function (res) {
}
});
},
error:function(XMLResponse){
alert("error:"+XMLResponse.responseText);
}
});
}
我这里为了省钱,把金额写死了,这个是页面动态获取的
下单Controller
//下单
@RequestMapping("newpay")
public void getConfig(HttpServletResponse response,HttpServletRequest request) throws Exception{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
String openid = request.getParameter("user");;
String body = request.getParameter("body");
int fee = (int) (Float.parseFloat(request.getParameter("fee")) * 100);
String requestUrl = request.getRequestURL().toString();
String requestUri = request.getRequestURI();
String domain = requestUrl.replace(requestUri,"");
String ip = Util.getIpAddr(request);
// System.out.println(openid + "\n" + body + "\n" + fee + "\n" + domain + "\n" + ip);
HashMap<String, String> paysign = paymanager.unifiedOrder(openid, body, fee, domain, ip);
JSONObject res = JSONObject.fromObject(paysign);
//TODO 在页面上判断一下res信息 然后插入数据
System.out.println(res);
writer.print(res);
writer.flush();
writer.close();
}
Service层
@Autowired
WxConfig config;
//统一下单
public HashMap<String, String> unifiedOrder(String openid,String body,int fee,String domain,String ip) {
try {
WXPayConfigImpl payconfig = WXPayConfigImpl.getInstance(config.getKey(), config.getAppID(),
config.getMchID(), config.getCertLocalPath());
WXPay wxpay = new WXPay(payconfig);
String tradeno = NoTools.getInstance().getWXTradeNo();
HashMap<String, String> data = new HashMap<String, String>();
data.put("body", body);
data.put("openid", openid);
data.put("out_trade_no", tradeno);
data.put("device_info", "");
data.put("fee_type", "CNY");
data.put("total_fee", fee+"");
data.put("spbill_create_ip", ip);
data.put("notify_url", domain + "/paynotify.do");
data.put("trade_type", "JSAPI");
Map<String, String> r = wxpay.unifiedOrder(data);
// System.out.println(r);
if(r.get("return_code").equals("SUCCESS") && r.get("result_code").equals("SUCCESS")){
HashMap<String, String> paysign = new HashMap<String, String>();
paysign.put("appId", config.getAppID());
paysign.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
paysign.put("nonceStr", WXPayUtil.generateUUID());
paysign.put("package", "prepay_id=" + r.get("prepay_id"));
paysign.put("signType", "MD5");
paysign.put("paySign", WXPayUtil.generateSignature(paysign,config.getKey(),SignType.MD5));
WXPayOrders order = new WXPayOrders(tradeno, "", body, fee, Util.getTime("yyyy-MM-dd HH:mm:ss"),
null, openid, r.get("prepay_id"), r.get("trade_type"), 0, null, 0);
wxpayDao.newOrder(order);
paysign.put("tradeno", tradeno);
return paysign;
}
} catch (Exception e) {
Util.logException("微信支付下单失败", e);
}
return null;
}
这是微信提供的SDK,代码里面设计到的类可以自行查找
注意我代码中的的paynotify这是支付结果通知,也就是支付成功之后需要的回调函数
//支付结果通知
@RequestMapping("paynotify")
public void payNotify(HttpServletResponse response,HttpServletRequest request) throws Exception{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
InputStream is = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String buffer = null;
StringBuffer sb = new StringBuffer();
while ((buffer = br.readLine()) != null) {
sb.append(buffer);
}
Map<String, String> notify = WXPayUtil.xmlToMap(sb.toString());
HashMap<String, String> res = paymanager.checkOrder(notify);
String resStr = "";
if(res.get("code").equals("1")){
resStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
resStr = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[" + res.get("msg") + "]]></return_msg></xml>";
}
// System.out.println(resStr);
writer.print(resStr);
writer.flush();
writer.close();
}
此时,支付已经能够成功拉取并且进行支付,在支付成功之后,生成的订单信息保存到本地的数据库中即可
明天我再更新微信退款这快的内容
之前我写的这一段忘记提醒读者进行微信支付的校验了 ,尤其是回调函数这一段,不论是微信还是支付宝都是需要对回调函数进行校验的,
看看微信支付的官方文档怎么说的。
支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知
最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
技术人员可登进微信商户后台扫描加入接口报警群。
也就是说在paynotify这个回调请求里面,我用的是最简单的方式进行的回调函数的异步校验,这也是比较狗的,属于治标不治本的方法。
paynotify的Service层
//支付完成通知校验
public HashMap<String, String> checkOrder(Map<String, String> notify) {
HashMap<String, String> res = new HashMap<String, String>();
try {
if(WXPayUtil.isSignatureValid(notify, config.getKey())){
WXPayOrders order = wxpayDao.getOrder(notify.get("out_trade_no"));
if(order != null){
if(order.getStatus() == 0){
if(order.getTotalfee() == Integer.parseInt(notify.get("total_fee"))){
SimpleDateFormat format1 = new SimpleDateFormat("yyyyMMddhhmmss");
SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datetime = notify.get("time_end");
try {
Date date = format1.parse(datetime);
datetime = format2.format(date);
// System.out.println(datetime);
} catch (ParseException e) {
Util.log(e.getMessage());
}
order.setTransactionid(notify.get("transaction_id"));
order.setEndtiem(datetime);
order.setStatus(1);
wxpayDao.confirmPay(order);
}
}
res.put("code", "1");
res.put("msg", "OK");
}else{
res.put("code", "0");
res.put("msg", "未找到订单");
}
}else{
res.put("code", "0");
res.put("msg", "签名错误");
}
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
这个里面的getOrder就是使用订单号查询出入账的该订单数据,
WXPayUtil.isSignatureValid(notify, config.getKey()
这个方法则是微信提供的让我们来进行验签的
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
然后我就在Controller里面做出了一个判断,也就是上边的那个
if(res.get("code").equals("1")){
resStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
resStr = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[" + res.get("msg") + "]]></return_msg></xml>";
}
而xml是微信支付所需要的格式,只有这样才能成功调用微信支付,以上就是微信支付全部的内容了,希望对大家有所帮助,如果还有不明白的,可以在下方添加评论,随时进行更新解答问题!