大概思路:将參數組裝為一個map集合,簽名後發送http請求給微信,微信會傳回一個xml字元串,将這個xml字元串解析并驗簽,得到code_url參數,傳給前台,前台将這個code_url生成一個二維碼,就可以了。
1、建立支付
/**
* 微信支付
* @param model
* @param order_id
* @return
* @throws Exception
*/
@RequestMapping(value = "/doWechatPay", method = RequestMethod.POST)
@ResponseBody
public Map createWechatPay(Model model, Integer order_id,HttpServletRequest request) throws Exception {
Map json = new HashMap();
User user = (User) model.asMap().get("user");
ShareOrderInfo order = orderInfoMapper.selectByPrimaryKey(order_id);
//生成一筆預付訂單流水
String trad_no = "PC_WECHAT" + OrderNoUtil.leadsNo();//訂單流水号
ShareUserTrad trad = new ShareUserTrad();
trad.setResourceTradId(-1);
trad.setUserId(user.getId());
trad.setCreatedBy(user.getId());
trad.setLastUpdBy(user.getId());
trad.setOnlineOfflineFlag("0");//線上
trad.setOrderNo(order.getOrderNo());
trad.setUserTradAmount(order.getToBePaid());
trad.setTradMethod("4");//支付寶
trad.setPayReceiveFlag("2");//支出
trad.setSuccessFlag("0");//交易進行中
trad.setTradType("1");//訂單支付
trad.setTradNo(trad_no);
trad.setModifyNum(0);
shareUserTradMapper.insertSelective(trad);
//随機字元串
String nonce_str = PayUtil.getRandomString(32);
String UTF8 = "UTF-8";
Map<String,String> map = new HashMap<String,String>();
map.put("body","訂單["+order.getOrderNo()+"]支付");
map.put("trade_type","NATIVE");
map.put("mch_id",Config.wechat_mch_id);
map.put("sign_type","MD5");
map.put("nonce_str",nonce_str);
map.put("fee_type","CNY");
map.put("device_info","WEB");
map.put("out_trade_no",trad_no);
map.put("total_fee",order.getToBePaid().multiply(new BigDecimal(100)).toBigInteger().toString());
// map.put("total_fee","1");//測試
map.put("appid",Config.wechat_app_id);
map.put("notify_url",Config.wechat_notify_url);
map.put("spbill_create_ip",PayUtil.getIp(request));
String sign = PayUtil.getWechatSign(map);
String reqBody = "<xml>" +
"<body>"+map.get("body")+"</body>" +
"<trade_type>"+map.get("trade_type")+"</trade_type>" +
"<mch_id>"+map.get("mch_id")+"</mch_id>" +
"<sign_type>"+map.get("sign_type")+"</sign_type>" +
"<nonce_str>"+map.get("nonce_str")+"</nonce_str>" +
"<detail />"+
"<fee_type>"+map.get("fee_type")+"</fee_type>" +
"<device_info>"+map.get("device_info")+"</device_info>" +
"<out_trade_no>"+map.get("out_trade_no")+"</out_trade_no>" +
"<total_fee>"+map.get("total_fee")+"</total_fee>" +
"<appid>"+map.get("appid")+"</appid>" +
"<notify_url>"+map.get("notify_url")+"</notify_url>" +
"<sign>"+sign+"</sign>" +
"<spbill_create_ip>"+map.get("spbill_create_ip")+"</spbill_create_ip>" +
"</xml>";
URL httpUrl = new URL(Config.wechat_url);
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
httpURLConnection.setRequestProperty("Host", "api.mch.weixin.qq.com");
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setConnectTimeout(10*1000);
httpURLConnection.setReadTimeout(10*1000);
httpURLConnection.connect();
OutputStream outputStream = httpURLConnection.getOutputStream();
outputStream.write(reqBody.getBytes(UTF8));
//擷取内容
InputStream inputStream = httpURLConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
final StringBuffer stringBuffer = new StringBuffer();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
String resp = stringBuffer.toString();
if (stringBuffer!=null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream!=null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream!=null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Map<String, String> respData = PayUtil.xmlToMap(resp);
String return_code = "";
if (respData.containsKey("return_code")) {
return_code = respData.get("return_code");
}
if("SUCCESS".equals(return_code)){
if (respData.containsKey("sign") ) {
String respSign = respData.get("sign");
if(respData.get("sign").equals(PayUtil.getWechatSign(respData))){
json.put("code_url",respData.get("code_url"));
}
}
}
System.out.println(resp);
return json;
}
respData.get("code_url")
這個就是我們要的二維碼内容。
PayUtil:
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.*;
public class PayUtil {
/**
* MD5加密
* @param data
* @return
* @throws Exception
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* XML轉MAP
* @param strXML
* @return
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return data;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* 擷取随機字元串
* @param length
* @return
*/
public static String getRandomString(int length) { //length表示生成字元串的長度
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 擷取IP位址
* @param request
* @return
*/
public static String getIp(HttpServletRequest request){
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 擷取微信支付簽名
* @param map
* @return
* @throws Exception
*/
public static String getWechatSign(Map<String,String> map) throws Exception{
Set<String> keySet = map.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals("sign")) {
continue;
}
if (map.get(k).trim().length() > 0) {// 參數值為空,則不參與簽名
sb.append(k).append("=").append(map.get(k).trim()).append("&");
}
}
sb.append("key=").append(Config.wechat_key);
return PayUtil.MD5(sb.toString()).toUpperCase();
}
/**
*
* Map轉xml資料
*/
public static String mapToXML(Map<String,String> param){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String,String> entry : param.entrySet()) {
sb.append("<"+ entry.getKey() +">");
sb.append(entry.getValue());
sb.append("</"+ entry.getKey() +">");
}
sb.append("</xml>");
return sb.toString();
}
}
2、前台根據code_url生成支付二維碼
這裡我使用了第三方js控件qrcode來生成二維碼。
function toWechatPay(){
var data = "order_id=" + ${id};
$.ajax({
url: rootPath + '/pay/doWechatPay',
type: "post",
data: data,
processData: true,
success: function (result) {
var json = eval('('+result+')');
$("#wechatQrcode").qrcode({
render: "table", //table方式
width: 200, //寬度
height:200, //高度
text: json.code_url //任意内容
});
$(document).ready(function () {
setInterval("ajaxstatus()", 3000);
});
},
error: function (XMLHttpRequest, error, errorThrown) {
layer.msg("支付失敗,原因:儲存預支付交易流水失敗");
}
});
}
因為二維碼為靜态的,是以設定一個三秒的定時任務查詢訂單的狀态,來改變頁面。
function ajaxstatus() {
var data = "order_id=" + ${id};
$.ajax({
url: rootPath + '/pay/getPayStatus',
type: "GET",
dataType:"json",
data: data,
success: function (data) {
if (data.status == "3") { //訂單狀态為3表示支付成功
layer.msg("支付成功,請重新整理頁面~",{shift: -1,time:2000},function(){
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
});
}
},
error: function () {
layer.msg("請求訂單狀态出錯");
}
});
}
3、異步通知
/**
* 微信支付異步通知
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/wechatPayNo", method = RequestMethod.POST)
@ResponseBody
public void wechatPayNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
ServletInputStream instream = request.getInputStream();
StringBuffer sb = new StringBuffer();
int len = -1;
byte[] buffer = new byte[1024];
while((len = instream.read(buffer)) != -1){
sb.append(new String(buffer,0,len));
}
instream.close();
Map<String,String> requestMap = PayUtil.xmlToMap(sb.toString());//接受微信的通知參數
String return_code = "";
String returnXml = "";
// BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
if (requestMap.containsKey("return_code")) {
return_code = requestMap.get("return_code");
}else{
returnXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[return_code不存在]]></return_msg>" + "</xml> ";
// out.write(returnXml.getBytes());
}
if("SUCCESS".equals(return_code)){
if (requestMap.containsKey("sign") ) {
String respSign = requestMap.get("sign");
if(respSign.equals(PayUtil.getWechatSign(requestMap))){
DealUserTradModel dealUserTradModel = new DealUserTradModel();
dealUserTradModel.setOut_trad_no(requestMap.get("transaction_id"));
dealUserTradModel.setTrad_no(requestMap.get("out_trade_no"));
dealUserTradModel.setCompany_amount(new BigDecimal(requestMap.get("total_fee")).divide(new BigDecimal("100")));//微信以分為機關,除以100……
dealUserTradModel.setPay_amount(new BigDecimal(requestMap.get("total_fee")).divide(new BigDecimal("100")));
dealUserTradModel.setUser_account(requestMap.get("openid"));
dealUserTradModel.setUser_account_name(requestMap.get("openid"));
payService.dealTrad(dealUserTradModel);
// returnXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
// + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
// out.write(returnXml.getBytes());
String msg = "success";
response.setContentType("text/xml");
response.getWriter().println(msg);
}else{
returnXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[sign不正确]]></return_msg>" + "</xml> ";
// out.write(returnXml.getBytes());
}
}else{
returnXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[sign不正确]]></return_msg>" + "</xml> ";
// out.write(returnXml.getBytes());
}
}else{
returnXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[return_code不正确]]></return_msg>" + "</xml> ";
// out.write(returnXml.getBytes());
}
// out.flush();
// out.close();
}
校驗參數,并修改資料庫的訂單狀态,同時傳回success,否則微信會重複通知。
String msg = "success";
response.setContentType("text/xml");
response.getWriter().println(msg);