业务逻辑:
后台生成二维码——>获取openid ——>页面输入金额与数量——>后台进行第一次签名——>根据预订单获取最终签名——>唤醒微信支付窗口 ——>支付成功后进行异步回调
具体实现:
后台生成二维码,二维码存放topay路径。topay接口主要是区分是支付宝内置浏览器打开的还是微信内置浏览器打开,如果是微信则去微信获取openid。具体代码如下:
@RequestMapping(value = "topay")
public void topay(String id, HttpServletRequest request,
HttpServletResponse response, Model model) throws Exception {
String agent = request.getHeader("User-Agent").toLowerCase();// 获取响应头
model.addAttribute("carid", id);
model.addAttribute("ip", Global.getConfig("IP"));
if (agent.indexOf("micromessenger") > 0) {// 微信扫描 发送https请求 获取code 重定向到
System.out.println("-----前去微信获取openid-----");
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=appid&redirect_uri=获取openid的接口地址&response_type=code&scope=snsapi_base&state=参数#wechat_redirect";
response.sendRedirect(url);
} else if (agent.indexOf("alipayclient") > 0) {// 支付宝扫描 去支付宝支付页面
System.out.println("-----前去支付宝-----");
}
}
/**
* 获取openid后,跳转微信支付页 (根据code获取openid,前去微信支付页面)
*
* @param code
* 获取到的 code
* @param state
* 参数
*/
@RequestMapping(value = "wxopenid")
public void wxopenid(String code, String state,
HttpServletRequest request, HttpServletResponse response,
Model model) throws Exception {
System.out.println("-----获取openid-----");
if (org.apache.commons.lang3.StringUtils.isEmpty(code) || org.apache.commons.lang3.StringUtils.isEmpty(state)) {// 参数为空支付失败
System.out.println("-----code为空-----");
}
// 发送请求 返回结果集(获取openid,返回就json字符串)
String result = WXPayUtil.httpsRequest(
" https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ WxConfing.appid + "&secret="
+ WxConfing.AppSecret + "&code=" + code
+ "&grant_type=authorization_code", "POST", null);
if (org.apache.commons.lang3.StringUtils.isEmpty(result)) {// 返回结果为空,支付失败
System.out.println("-----获取openid结果为空-----");
}
JSONObject jsonObject2 = new JSONObject(result);
String openid = (String) jsonObject2.getObj("openid");
if (org.apache.commons.lang3.StringUtils.isEmpty(openid)) {// openid 为空支付失败
System.out.println("-----openid为空-----");
}
String url = Global.getConfig("IP") + "写好的H5页面路径?id=" + state+"&openid="+openid;// 前去支付页面
response.sendRedirect(url);
}
页面进行ajax请求,代码如下:
function submit() {
var id = window.location.search.split("=")[1];
var number = $("#num").html();
var price = $("#price").val();
var openid = window.location.search.split("=")[2];
$.ajax({
url : "后台微信支付接口/weixinpay",
data : {
price : price,
number : number,
id : id,
openid : openid
},
success : function(data) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId" : data.body.appId, //公众号名称,由商户传入
"timeStamp" : data.body.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr" : data.body.nonceStr, //随机串
"package" : data.body.packages,
"signType" : "MD5", //微信签名方式:
"paySign" : data.body.sign//微信签名
}, function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
//进行异步回调
$.post("后台异步回调接口/weixinsuccess", {
id:id
});
WeixinJSBridge.call('closeWindow');//关闭页面
} else {
WeixinJSBridge.call('closeWindow');//关闭页面
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
})
}
});
}
后台接口进行第一次签名,参数有:appid、body、mch_id、nonce_str、notify_url、openid、ut_trade_no、sign_type、spbill_create_ip、total_fee、trade_type、key 进行签名。将这些参数(包括刚创建的签名)转成xml,携带参数 post 访问 https://api.mch.weixin.qq.com/pay/unifiedorder 接口 ,成功的话会得到xml格式的prepay_id(预支付交易会话id),再将携带参数appId、timeStamp、nonceStr、package("prepay_id="+map.get("prepay_id")、signType进行第二次生成签名,最后将appId、timeStamp、nonceStr、package、paySign传个前端。具体实现如下:
/**
* 微信支付接口
*
* @param id id
* @param price 单价
* @param number 人数
* @return
*/
@RequestMapping("weixinpay")
public AjaxJson weixinpay(String id, Integer number, String openid,Double price, HttpServletRequest request, HttpServletResponse response) {
AjaxJson aj = new AjaxJson();
System.out.println("--------微信支付入口---------");
String APPID = WxConfing.appid;
String MERID = WxConfing.mchid;
String SIGNKEY =WxConfing.Key;
String spbill_create_ip = getIpAddr(request);
String tradeType = "JSAPI";//H5支付标记
String MD5 = "MD5";//虽然官方文档不是必须参数,但是不送有时候会验签失败
JSONObject result = new JSONObject();
String subject = "XXX支付";
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//金额转化为分为单位 微信支付以分为单位
Double sum = number * price;//获取总价
Double moneytotal = NumberUtil.mul(sum.doubleValue(), 100D);
String finalmoney = moneytotal.toString();// 将金额变为以分为单位
finalmoney = finalmoney.substring(0, finalmoney.indexOf("."));
System.out.println(finalmoney);
String out_trade_no = coachOrderinfo.getId();
//随机数
String nonce_str= WXPayUtil.generateNonceStr();
//签名数据
SortedMap<String, String> parameterMap = new TreeMap<>();
parameterMap.put("appid",APPID);
parameterMap.put("body",subject);
parameterMap.put("mch_id",MERID);
parameterMap.put("nonce_str",nonce_str);
parameterMap.put("notify_url","回调地址");
parameterMap.put("openid",openid);
parameterMap.put("out_trade_no",out_trade_no);
parameterMap.put("sign_type","MD5");
parameterMap.put("spbill_create_ip",spbill_create_ip);
parameterMap.put("total_fee",finalmoney);
parameterMap.put("trade_type",tradeType);
parameterMap.put("key",SIGNKEY);
//签名MD5加密
String sign = null;
try{
sign = WXPayUtil.createSign(parameterMap,SIGNKEY);
System.out.println("sign"+sign);
} catch (Exception e) {
e.printStackTrace();
}
//封装xml报文
String xml="<xml>"+
"<appid>"+ APPID+"</appid>"+
"<mch_id>"+ MERID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body>"+subject+"</body>"+//
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+finalmoney+"</total_fee>"+//
"<trade_type>"+tradeType+"</trade_type>"+
"<notify_url>"+"回调地址"+"</notify_url>"+
"<openid>" + openid + "</openid>" +
"<sign_type>MD5</sign_type>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"</xml>";
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单接口
try {
//预下单 获取接口地址
String resultwx = WXPayUtil.httpsRequest(createOrderURL,"POST", xml);
Map map = WXPayUtil.xmlToMap(resultwx);
String return_code = (String) map.get("return_code");
String return_msg = (String) map.get("return_msg");
if("SUCCESS".equals(return_code) && "OK".equals(return_msg)){
//签名MD5加密
String sign2 = null;
try{
SortedMap<String, String> parameterMap2 = new TreeMap<>();
parameterMap2.put("appId",parameterMap.get("appid"));
parameterMap2.put("timeStamp",timestamp);
parameterMap2.put("nonceStr",nonce_str);
parameterMap2.put("package", "prepay_id="+map.get("prepay_id"));
parameterMap2.put("signType","MD5");
sign2 = WXPayUtil.createSign(parameterMap2,SIGNKEY);
System.out.println("sign"+sign2);
aj.put("appId", parameterMap.get("appid"));
aj.put("timeStamp", timestamp);
aj.put("nonceStr", nonce_str);
aj.put("packages", "prepay_id="+map.get("prepay_id"));
aj.put("sign", sign2);
} catch (Exception e) {
e.printStackTrace();
}
}else{
System.out.println("统一支付接口获取预支付订单出错");
aj.put("msg", "支付错误");
return aj;
}
} catch (Exception e) {
System.out.println("统一支付接口获取预支付订单出错");
aj.put("msg", "支付错误");
return aj;
}
aj.put("msg", "success");
return aj;
}
/**
* 获取用户实际ip
* @param request
* @return
*/
public String getIpAddr(HttpServletRequest request){
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
/**
* 生成签名
* @param packageParams
* @param key
* @return
*/
public static String createSign(SortedMap<String, String> packageParams,String key) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext())
{
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if ((v != null) && (!"".equals(v)) && (!"sign".equals(k)) &&
(!"key".equals(k))) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
System.out.println("md5 sb:" + sb + "key=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8")
.toUpperCase();
System.out.println("packge:" + sign);
return sign;
}
/**
* 微信支付回调
*
* @return
* @throws Exception
*/
@RequestMapping(value = "/notifyWx")
public void wxUrl(HttpServletRequest request, HttpServletResponse response)
throws Exception {
/**
* 支付回调会多次调用,一般根据返回状态来修改业务逻辑,本支付没设么逻辑,所以不使用
*/
System.out.println("-----微信支付回调-----");
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
// 返回参数转码
String resultxml = new String(outSteam.toByteArray(), "utf-8");
// xml转换map
Map<String, String> params = WXPayUtil.xmlToMap(resultxml);
outSteam.close();
inStream.close();
// 获取返回状态
String success = params.get("return_code");
// 给回调的返回值
// Map<String, String> return_data = new HashMap<String, String>();
String resXml = "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
if (org.apache.commons.lang3.StringUtils.isNotBlank(success) && success.equals("SUCCESS")) {
// ------------------------------处理业务
// ------------------------------
// 此处处理订单状态,结合自己的订单数据完成订单状态的更新
// -----------------------------
// return_data.put("return_code", "SUCCESS");
// return_data.put("return_msg", "OK");
// return WXPayUtil.mapToXml(return_data);
// return
// "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
} else {
// 支付失败
System.out.println("-----微信回调支付失败-----");
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[交易失败]]></return_msg>" + "</xml>";
// return_data.put("return_code", "FAIL");
// return_data.put("return_msg", "return_code不正确");
// return WXPayUtil.mapToXml(return_data);
}
System.out.println("-----返回流------");
System.out.println("-----resXml------" + resXml.getBytes());
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
后文惊喜彩蛋 :APP支付文档
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3