1. 程式人生 > >統一下單介面實現微信支付(未使用框架)

統一下單介面實現微信支付(未使用框架)

之前在做微信支付的時候,按照微信官方給的統一下單介面文件進行開發時,因為用到統一下單介面的支付方式有很多種,裡面的引數有時是必填的有時是非必填的,以及引數形式等各個方面帶的模糊帶來了很多問題。所以在成功完成支付後寫篇文章給剛接觸這塊的朋友們說明一下問題。

我所使用的統一下單的場景是:在微信中開啟有關商品的頁面(可以是一個網站連結,可以是公眾號裡按鈕的跳轉,總之,是在微信中開啟一個網站),點選下單按鈕後能夠彈出支付框,輸入密碼後完成支付。(具體場景大家根據需求自己改,這裡我只實現在微信中開啟連結,點選按鈕,調起支付,完成支付一個完整的流程)這裡強調一下,如果是想在除微信之外的其他瀏覽器呼叫微信支付,使用的是微信的 

H5支付  這個產品,並不是統一下單,統一下單功能只針對在微信內開啟瀏覽器實現支付的需求。

準備(重點):一個開通了微信認證的公眾號(300塊一年那個認證,如果你沒交那300塊是沒法用的),並開通了對應的微信商戶平臺,然後拿到對應資料    公眾號相關資料:appid、apisecret 商戶號相關資料 :mch_id、key 商戶平臺一定要完成支付申請

以上的場景所使用的統一下單介面的支付方式,是需要獲取付款使用者openid的,不瞭解的可以去看我另一篇獲取微信openid的文章http://blog.csdn.net/z880698223/article/details/78485243

準備好以上資訊後我們就可以開始了

首先簡單寫一個帶下單按鈕的jsp頁面,下單按鈕呼叫js的sub方法。(這個頁面各位可以按自己需求顯示資料了,比如商品圖片啦價格啦之類的然後在sub中的ajax請求引數中附帶上就可以,這裡只拿價格來做個演示)建議最好在這個頁面之前就先獲取openid,存到session或者cookie中以便後面使用,因為這裡主要介紹支付,我就把我們拿到的openid直接寫死在程式碼裡了,大家可以根據需要自己靈活修改

<input type="button" onclick="sub()" value="下單">

之後是js,這裡記得引入jquery的js包

var out_trade_no="";
function sub(){
  $.post("WxPayServlet",{price:1},function(data){
  var json=eval("("+data+")");
  out_trade_no=json.out_trade_no;
  var appid=json.appId;    
  var prepayid=json.package;
  var timeStamp=json.timeStamp;
  var sign=json.sign;
  var nonceStr=json.nonceStr;
//從這裡開始就是微信提供的jsapi了,
  if (typeof WeixinJSBridge == "undefined"){
    if( document.addEventListener ){
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        }else if (document.attachEvent){
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    }else{
        onBridgeReady();
    }
    
    function onBridgeReady(){
        WeixinJSBridge.invoke(
                'getBrandWCPayRequest', {
                    "appId" : appid,     //公眾號appid
                    "timeStamp":timeStamp,         //時間戳,自1970年以來的秒數
                    "nonceStr" : nonceStr, //隨機字串
                    "package" : prepayid,
                    "signType" : "MD5",         //微信簽名方式:
                    "paySign" : sign //微信簽名
                },
                function(res){
                    //支付結果回撥,當res.err_msg的值為get_brand_wcpay_request:ok 時即為支付成功
                    if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                    	$("#refund").show();
                    	alert("支付成功");
                    }else{
                    }
                }
        );
    }
    }
    
  );
  }

這樣我們支付用的頁面就寫好了,接下來我們來寫這個頁面中ajax請求的介面,這裡我使用的是servlet

為了實現下單我們還需要三個類幫助實現 分別是  XMLUtil   WxPayOrder   和 HttpUrlConnection下面分別介紹(諸位不要心急,一定要穩住)

1.XMLUtil

因為微信的統一下單介面提交引數要求我們使用xml格式,返回資料型別也是xml格式,所以我們先寫一個xml與map之間互相轉換的工具類XMLUtil,這裡需要jdom的jar,記得匯入

public class XMLUtil {
	/**xml格式字串與map集合之間互相轉換的工具
	*/
	
	//map集合轉xml字串
	public static String toXml(Map<String, Object> params) {
		StringBuffer xml = new StringBuffer();
		xml.append("<?xml version='1.0' encoding='ISO8859-1' standalone='yes' ?><xml>");

		ArrayList<String> arr = new ArrayList<String>();
		for (String key : params.keySet()) {
			if (params.get(key) != null && !params.get(key).equals("")) {
				arr.add(key);
			}
		}
		Collections.sort(arr);
		for (int i = 0; i < arr.size(); i++) {
			String k = arr.get(i);
			if (params.get(k) != null) {
				String v = params.get(k).toString();
				xml.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
			}
		}

		xml.append("</xml>");
		String xml2 = "";
		try {
			xml2 = new String(xml.toString().getBytes(), "ISO8859-1");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return xml2;
	}
	//xml字串轉map集合
	public static Map<String, Object> toMap(String xml) {


		Map<String, Object> result = new HashMap<String, Object>();
		
		if(xml.equals("")){
			return result; 
		}
		
		try {
		StringReader read = new StringReader(xml);
		InputSource source = new InputSource(read);
		SAXBuilder sb = new SAXBuilder();
		
			Document doc = (Document) sb.build(source);
			Element root = doc.getRootElement();
			result.put(root.getName(), root.getText());
			result = parse(root, result);
		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		result.remove("xml");
		return result;
	}

	private static Map<String, Object> parse(Element root,
			Map<String, Object> result) {
		List<Element> nodes = root.getChildren();
		int len = nodes.size();
		if (len == 0) {
			result.put(root.getName(), root.getText());
		} else {
			for (int i = 0; i < len; i++) {
				Element element = (Element) nodes.get(i);// 迴圈依次得到子元素
				result.put(element.getName(), element.getText());
				// parse(element,result);
			}
		}
		return result;
	}
}

2.HttpUrlConnection

用於發起http請求的工具,這裡專門針對提交資料格式為XML的請求方式,也是WxPayOrder類所必需的一個工具

public class HttpUrlConnection {


	public static String httpXML(String url, String xml) {
		String result = null;
		try {
			InputStream is = httpPostXML(url, xml);
			BufferedReader in = new BufferedReader(new InputStreamReader(is,
					"UTF-8"));
			StringBuffer buffer = new StringBuffer();
			String line = "";
			while ((line = in.readLine()) != null) {
				buffer.append(line);
			}
			result = buffer.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return result;
	}

	private static InputStream httpPostXML(String url, String xml) {
		InputStream is = null;

		PrintWriter out = null;
		try {

			URL u;
			u = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) u.openConnection();
			conn.setRequestProperty("Content-Type",
					"application/x-www-form-urlencoded");
			// conn.setRequestProperty("Content-Type",
			// "application/json;charset=UTF-8");
			// conn.setRequestProperty("Accept-Charset", "UTF-8");
			// conn.setRequestProperty("contentType", "UTF-8");
			conn.setConnectTimeout(20000);
			conn.setReadTimeout(50000);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setUseCaches(false);
			conn.setRequestMethod("POST");
			OutputStreamWriter outWriter = new OutputStreamWriter(
					conn.getOutputStream(), "UTF-8");
			out = new PrintWriter(outWriter);
			
			out.print(xml);
			out.flush();
			out.close();
			/*
			 * DataOutputStream dos = new
			 * DataOutputStream(conn.getOutputStream());
			 * 
			 * System.out.println(json.toString()); dos.write(new
			 * String(json.toString().getBytes("UTF-8"),"UTF-8").getBytes());
			 * dos.flush(); dos.close();
			 */

			is = conn.getInputStream();
		} catch (Exception e) {

			e.printStackTrace();
		}

		return is;
	}
}

3.WxPayOrder

這個類幫助我們編輯訂單並生成訂單,後面將我們從jsp頁面傳過來的價格引數設定到訂單裡就用的這個類,同時它也實現了發起下單的功能。生成訂單的方法中,對變數名含義不理解的可以去微信開發者文件中心檢視統一下單的介面文件

public class WxPayOrder {
//提交xml格式的訂單
	public static String makePayOrder(String xml) {
		String result = "";
		result = HttpUrlConnection.httpXML(
				"https://api.mch.weixin.qq.com/pay/unifiedorder", xml);

		return result;
	}
	//生成xml格式的訂單
	public static String haveTestPayOrderXml(String price){
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
String xml="";
Map <String,Object> map=new HashMap<String,Object>();
map.put("appid", StringInfo.APPID);
map.put("mch_id",StringInfo.MCH_ID);
//map.put("device_info", params.get("avm"));
map.put("nonce_str", RandomStrUtil.getRandomString(32));
map.put("sign_type", "MD5");
map.put("detail", "250ml");
String out_trade_no="99"+sdf.format(new Date());
map.put("out_trade_no", out_trade_no);
System.out.println(out_trade_no);
map.put("total_fee",price);//把頁面傳過來的價格price放到訂單map裡,其他引數同理可傳進來
map.put("openid", "oBFdiwBmB7lBNFdqrDKSxxxxx");//這裡的openid是個寫死的例子,大家可以根據自己獲取到的openid放在這裡
map.put("spbill_create_ip", "192.168.10.68");
map.put("notify_url", "http://xxx.xxxxxx.xxx/wxpay/WxGetPayResult");//這裡是微信支付結果通知的介面,用來接收訂單支付情況
map.put("trade_type", "JSAPI");
map.put("product_id", "12222223333111332");
map.put("body", "kekoukele");
String sign=SignUtil.getSign(map);
map.put("sign", sign);
xml=XMLUtil.toXml(map);
return xml;
}
}

有了以上三個類我們就可以開始寫我們的servlet了,這個servlet實現的功能就是接受jsp頁面提交過來的下單請求,並將頁面傳輸過來的訂單引數放進訂單最後下單得到下單結果,並將結果返回給jsp頁面。

我們新建一個servlet叫WxPayServlet,web.xml中的配置就寫<url-pattern>/WxPayServlet</url-pattern>就好

public class WxPayServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		response.setContentType("text/html");
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out=response.getWriter();
		System.out.println("執行統一下單");
                String price=request.getParameter("price");
		String xml=WxPayOrder.haveTestPayOrderXml(price);
		Map<String, Object> param=XMLUtil.toMap(xml);
		String result=WxPayOrder.makePayOrder(xml);
		Map<String, Object> map=new HashMap<String,Object>();
		map=XMLUtil.toMap(result);
		System.out.println("統一下單反回結果:"+map);
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date startDate=null;
		try {
			startDate = sdf.parse("1970-01-01 00:00:00");
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		String timeStamp =DateUtil.get(startDate)+"";
		//因為介面返回的資料的引數名與jsapi的請求引數名有出入,所以這裡我們轉換一下再返回給jsp頁面
		Map<String ,Object > params=new HashMap<String ,Object>();
		params.put("package","prepay_id="+ map.get("prepay_id"));
		params.put("appId",map.get("appid"));
		params.put("nonceStr",map.get("nonce_str"));
		params.put("signType", "MD5");
		params.put("timeStamp", timeStamp);
		String sign=SignUtil.getSign(params);
		params.put("sign", sign);
		params.put("out_trade_no", param.get("out_trade_no"));
		JSONObject json=JSONObject.fromObject(params);
		out.print(json);
		out.flush();
		out.close();

	}

}

寫好下單介面,我們後當我們的jsp頁面呼叫介面,發起下單請求後,我們的WxPayServlet接收到請求便會向微信發起下單請求,並將請求到的結果返回給jsp頁面,jsp頁面會通過jsAPI自動在使用者端調起微信支付的付款介面(如果不是在微信裡開啟的,這個jsapi是沒法使用的)

到這裡我們的支付功能已經完成了。只是微信會在支付成功後再來通知你一下,我們需要在寫一個介面來接收一下這個支付結果通知。這個介面也就是我們在WxPayOrder類中提交的一個引數notify_url。這個介面要實現兩個功能,一個是能接收微信發給我們的xml格式的支付結果,再就是返回一下成功給微信,不然他們每過一段時間就是再通知一次,直到25小時以後才停止通知。

還是新建一個servlet,web.xml配置<url-pattern>/WxGetPayResult</url-pattern>

public class WxGetPayResult extends HttpServlet {


	private static final long serialVersionUID = 1L;


	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doPost(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		response.setContentType("text/html");
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		 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);
         }
         outSteam.close();
         inStream.close();
         String result  = new String(outSteam.toByteArray(),"utf-8");
         Map<String,Object> map=XMLUtil.toMap(result);
         System.out.println("WxGetPayResult返回結果:"+map);
         Map<String ,Object>params=new HashMap<String,Object>();
         params.put("return_code", map.get("return_code"));
         params.put("return_msg", map.get("return_msg"));
         String xml=XMLUtil.toXml(params);
         out.print(xml);
		 out.flush();
		 out.close();
	}

}

到這裡整個支付流程就結束了,接下來測試一下,

1.在微信內開啟我們的jsp頁面,出現我們簡陋的下單頁面


2.點選下單按鈕後,彈出微信支付頁面


3.輸入密碼支付後彈出支付成功頁面

4.這時我們的後臺控制檯可以看見輸出了一行資料(涉及隱私,部分敏感資料我用xxxx代替了一下)

WxGetPayResult返回結果:{transaction_id=xxxxxxxxxxxxxxxx329820007, nonce_str=17xymgp434m2lo186l6xursal9a1wfve, bank_type=CFT, openid=oBFdiwBmB7lBNFdqrDKSxxxxx, sign=6E0F498DDFE1C692CA480C7A6F06DBF8, fee_type=CNY, mch_id=1xxxxxxx2, cash_fee=1, out_trade_no=9920180309xxxxx, appid=wx900000000000, total_fee=1, trade_type=JSAPI, result_code=SUCCESS, time_end=20180309103314, is_subscribe=Y, return_code=SUCCESS}

5.點選完成後返回jsp頁面,彈窗支付成功

到此,剛才的一分錢就入賬到我們商戶平臺所繫結的銀行卡里了

總結:

1.微信JSAPI支付成功的前提條件就是我們一開始的準備工作做充分了,很多朋友們在最開使的微信公眾號,商戶平臺,appid,key等引數的地方就被絆住了,導致後面的工作根本沒法展開,所以想要實現微信支付,最開始的準備工作一定要充分。公眾號相關資料:appid、apisecret 商戶號相關資料 :mch_id、key這四個引數必須準確無誤。

2.呼叫介面時引數格式引數名的必填與非必填一定要明確,這裡不明白的可以參考微信官方的介面文件,在我的例子中,我也寫了幾個非必填選項總之,當微信下單介面返回的結果不是success的時候,一定要好好檢視返回內容,裡面寫著失敗的錯誤程式碼。造成失敗的原因基本上就以下三點:1.appid,apisecret, mch_id,key這四個引數不正確,這裡還是強調,準備工作一定要做充分,因為這四個引數不對導致的支付失敗是最浪費時間的了。2.缺少必填引數,比如在jsapi這種支付模式下,openid就是必填項,而因為在別的支付模式下不是必填,所以官方文件上對openid的引數標識是非必填的,我就在這裡跪了半天。3.引數格式錯誤,商戶訂單號重複等。這裡就要求我們仔細對照官方介面文件了,到底是什麼格式,一定要明確。

3.遇到問題一定要仔細分析錯誤原因,解決不了可以去請求下微信官方的技術客服,他們解決問題還是一針見血的

以上就是實現微信jsapi支付的簡單例項,因為涉及東西比較多,可能會在一些地方出現錯誤,希望大家指正,有問題也可以討論,大家一起學習