天天看點

微信和支付寶支付實戰

最近的項目中要用到移動支付。在此總結下

1、微信支付 :因為需求是掃碼支付即時到賬,用的是native方式。按照官方文檔的說法,調用“https://api.mch.weixin.qq.com/pay/unifiedorder” 這個接口生成一個預支付的訂單,微信會傳回給你一個訂單的二維碼的位址,然後把二維碼解析展示出來使用者掃碼就可以支付了。

    //微信支付

String random = RandomStringGenerator.getRandomStringByLength(32);
  Map<String,Object> params = new HashMap<String,Object>();
  params.put("appid", TenConfig.getAppid());
  params.put("mch_id", TenConfig.getMchid());
  params.put("nonce_str", random);
  params.put("body", body.toString().trim());
  params.put("out_trade_no", order.getOrderNumber());
  params.put("total_fee", sump);
  params.put("spbill_create_ip", TenConfig.SPBILL_CREATE_IP);
  params.put("notify_url", TenConfig.getNotify_url());
  params.put("trade_type", "NATIVE");
  params.put("product_id", productId);
  String sign = TenUtils.getSign(params);
  //拼接xml
  StringBuilder xml = new StringBuilder("<xml>");
  xml.append("<appid>").append("<![CDATA["+TenConfig.getAppid()+"]]>").append("</appid>");
  xml.append("<mch_id>").append("<![CDATA["+TenConfig.getMchid()+"]]>").append("</mch_id>");
  xml.append("<nonce_str>").append("<![CDATA["+random+"]]>").append("</nonce_str>");
  xml.append("<body>").append("<![CDATA["+body.toString().trim()+"]]>").append("</body>");
  xml.append("<out_trade_no>").append("<![CDATA["+order.getOrderNumber()+"]]>").append("</out_trade_no>");
  xml.append("<total_fee>").append("<![CDATA["+sump+"]]>").append("</total_fee>");
  xml.append("<spbill_create_ip>").append("<![CDATA["+TenConfig.SPBILL_CREATE_IP+"]]>").append("</spbill_create_ip>");
  xml.append("<notify_url>").append("<![CDATA["+TenConfig.getNotify_url()+"]]>").append("</notify_url>");
  xml.append("<trade_type>").append("<![CDATA[NATIVE]]>").append("</trade_type>");
  xml.append("<product_id>").append("<![CDATA["+productId+"]]>").append("</product_id>");
  xml.append("<sign>").append("<![CDATA["+sign+"]]>").append("</sign>");
  xml.append("</xml>");<pre name="code" class="java">  logger.info("XML:\n"+xml.toString());
  String resStr = TenUtils.postData(TenConfig.UNIFIED_ORDER, xml.toString(), "text/html;charset=utf-8");
           

這一步主要是拼接預下單接口的參數,要注意的是請求的要用utf-8編碼下,不然可能會出現簽名錯誤之類的傳回。

把MD5簽名算法也貼上 在這裡被編碼坑了。

ArrayList<String> list = new ArrayList<String>();
        for(Map.Entry<String,Object> entry:map.entrySet()){
            if(entry.getValue()!=""){
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String [] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < size; i ++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + TenConfig.getKey();  	    
        result = MD5Encrypt.MD5Encode(result, "utf-8").toUpperCase();
        return result;
           

MD5簽名的時候切記要用“utf-8”編碼,不然會出現簽名錯誤,這是個深坑,接口文檔是沒有說明的,

貼上MD5算法

public static String md5(String s)
  {
    byte[] input = s.getBytes();
    String output = null;
    
    char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
      'a', 'b', 'c', 'd', 'e', 'f' };
    try
    {
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(input);
      


      byte[] tmp = md.digest();
      char[] str = new char[32];
      byte b = 0;
      for (int i = 0; i < 16; i++)
      {
        b = tmp[i];
        str[(2 * i)] = hexChar[(b >>> 4 & 0xF)];
        str[(2 * i + 1)] = hexChar[(b & 0xF)];
      }
      output = new String(str);
    }
    catch (NoSuchAlgorithmException e)
    {
      e.printStackTrace();
    }
    return output;
  }
  
  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];
  }
  
  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 localException) {}
    return resultString;
  }
  
  private static final String[] hexDigits = { "0", "1", "2", "3", "4", "5", 
    "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
           

然後建立連接配接去請求微信接口

BufferedReader reader = null;
        try {
            URL url = new URL(urlStr);
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(CONNECT_TIMEOUT);
            if(contentType!=null && !"".equals(contentType))
                conn.setRequestProperty("content-type", contentType);
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
            if(data == null)
                data = "";
            writer.write(data); 
            writer.flush();
            writer.close();  
 
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\r\n");
            }
            return sb.toString();
        } catch (IOException e) {
        	logger.error("Error connecting to " + urlStr + ": " + e.getMessage());
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
            }
        }
        return null;
           

調用之後會拿到一個xml 裡面是傳回值。解析xml

Map resMap = XMLParser.getMapFromXML(resStr);
						
  if(resMap == null){
     logger.info("統一下單接口調用失敗");
  }else{
     String returnCode = (String) resMap.get("return_code");
     if(returnCode.equals("SUCCESS")){
     logger.info("統一下單接口調用成功");
     String codeUrl = (String) resMap.get("code_url");
     //把二維碼連接配接傳回給頁面
     result.put("msg", codeUrl);
								
     //更新訂單的支付方式,金額等
     order.setPayAmount(Float.parseFloat(sumprice));
     order.setPayType(payType);
     order.setWxPayNonceStr(random);
     order.setBody(body.toString());
     if(orderService.updateWhenPay(order) > 0){
	 result.put("code", "ok");
     }else{
	 result.put("code", "err");
     }
  }
           

解析xml把生成的二維碼給頁面 通過jQuery的qrcode.js把位址生成二維碼。

<span style="white-space:pre">	</span>$.post("/xxx/xxxxx.jspx",{"orderId":orderId
	,"payType":payType,"sumPrice":price},
	function(data){
		if(data&&data.code=="ok"){
			if(payType==1){
				//微信支付
				$("#model_content").css("display","block");
				jQuery('#qrcodeCanvas').qrcode({
					render   : "canvas",//設定渲染方式  
					width       : 210,     //設定寬度  
					height      : 210,     //設定高度  
					typeNumber  : -1,      //計算模式  
					correctLevel    : QRErrorCorrectLevel.H,//糾錯等級  
					background      : "#ffffff",//背景顔色  
					foreground      : "#000000", //前景顔色
					text	: data.msg
				});
				intel =setInterval(function(){
					//請求查詢訂單狀态
					queryOrderStatus(orderId,intel);
				},2000); 
			}else if(payType==2){
				//支付寶
				$("#ali_pay").html(data.msg);
			}
		}else{
			window.wxc.xcConfirm("支付失敗!", window.wxc.xcConfirm.typeEnum.error);
		}
	});
           

用qrcode.js的生成二維碼的時候 render   建議用canvas方式 雖然它還提供了table方式,但是我用的時候table方式無法生成二維碼的但是canvas是可以的 ,沒弄懂為什麼。如果順利的話 二維碼就生成了,掃碼就可以支付了。解釋下這段js代碼,請求成功後用一個遮罩層,把二維碼放遮罩層上,然後用一個函數去輪詢這個訂單的狀态,因為調用時異步的嗎,如果查詢到訂單已經支付了的話,清除掉輪詢并就跳轉到支付完成頁面。

下面貼微信回調的代碼

try {
		//擷取微信回調的流
		BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
<span style="white-space:pre">	</span>        String line = null;
        <span style="white-space:pre">	</span>StringBuilder sb = new StringBuilder();
<span style="white-space:pre">	</span>        while((line = br.readLine())!=null){
	            sb.append(line);
	        }
		//把微信傳回的xml轉換成map
	        Map result = XMLParser.getMapFromXML(sb.toString());
	        
		//傳回給微信的xml
	        StringBuilder reXML = new StringBuilder("<xml>");
	        
	        //return_code 字段是通信辨別,非交易辨別,交易是否成功需要檢視result_code來判斷
	        if(result.get("return_code").toString().equals("SUCCESS")){
	        	//回調成功
	        	logger.info("微信支付成功,傳回的XML:\n"+sb.toString());
	        	
		        //擷取微信回調的參數
		        String orderNum = (String) result.get("out_trade_no");
		        //業務結果
		        String resultCode = (String) result.get("result_code");
		        
		        String transactionId = result.get("transaction_id").toString();
		        
		        String sign = result.get("sign").toString();
		        if(result.get("sign")!=null){
		        	//去除Sign
		        	result.remove("sign");
		        }
		        if(sign.equals(TenUtils.getSign(result))){
	        		//簽名通過,判斷交易狀态
	        		if(resultCode.equals("SUCCESS")){
	        			if(orderNum !=null && !"".equals(orderNum)){
	    		        	Order order = orderService.findByOrderNum(orderNum);
	    		        	if(order != null){
	    	        			//支付成功
	    	        			if(order.getStatus() == 0){
	    						//訂單如果是待支付狀态,更改訂單狀态為确認中狀态(1),更新支付完成時間
	    						orderService.updateStatus(order.getId(), 1);
	    						orderService.updatePayCompleteTime(order.getId());
	    						
	    						reXML.append("<return_code><![CDATA[SUCCESS]]></return_code>");
	    						reXML.append("<return_msg><![CDATA[OK]]></return_msg>");
	    							
	    						//插入支付日志
	    						Map<String,String> map = new HashMap<String,String>();
	    						map.put("pro_type", "銷售");
	    						map.put("pay_type", "微信");
	    						map.put("pro_info", order.getBody());
	    						map.put("user_name", order.getBuyMember().getUsername());
	    						map.put("trade_no", orderNum);
	    						map.put("pay_amount", order.getPayAmount()+"");
	    						map.put("status", "支付完成");
	    						map.put("buyer_email", transactionId);
	    						orderService.insertPayLog(map);
	    	        			}else{
	    						reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
	    						reXML.append("<return_msg><![CDATA[訂單狀态異常]]></return_msg>");
	    	        			}
			        		}else{
							reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
							reXML.append("<return_msg><![CDATA[參數[out_trade_no]錯誤]]></return_msg>");
			        		}
				        }else{
						reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
						reXML.append("<return_msg><![CDATA[參數[out_trade_no]為空]]></return_msg>");
				        }
		        	}else{
		        	 <span style="white-space:pre">	</span>logger.info("交易狀态異常:"+resultCode);
		        	}
		        }else{
				reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
				reXML.append("<return_msg><![CDATA[簽名失敗]]></return_msg>");
		        }
	        }else{
	        	logger.info("微信支付回調失敗,失敗原因:"+result.get("return_msg").toString());
	        }
	        <span style="white-space:pre">	</span>reXML.append("</xml>");
	        <span style="white-space:pre">	</span>logger.info("傳回給微信的XML:\n"+reXML);
	        <span style="white-space:pre">	</span>response.getWriter().write(new String(reXML.toString().getBytes("UTF-8"),"ISO-8859-1"));
		} catch (Exception e) {
		<span style="white-space:pre">	</span>logger.info(e.getMessage(), e);
		}
           

支付成功後微信會調用這個方法,這個方法主要用于修改訂單狀态。當訂單狀态改變了,js的queryOrderStatus方法檢測到訂單支付完成了,就會跳轉到支付完成頁面。就完成了。把js輪訓方法也貼上

//查詢訂單狀态
function queryOrderStatus(orderId,intel){
	$.post("/xxx/xxxx.jspx",{"orderId":orderId},
	function(data){
		if(data && data.orderId == orderId){
			//查詢成功
			if(data.code=="ok"){
				//訂單查詢成功
				if(data.status==1){
					//支付成功,清除輪訓并重新整理頁面
					clearInterval(intel);
					window.location.href = "/xxxx/xxxx.jspx?orderId="+orderId+"&guid="+guid();			
				}
			}
		}
	});
}
           

醬紫微信支付就完成了。

微信和支付寶支付實戰

下面說說支付寶支付,因為支付官方提供的有demo 拿過來改改就能直接用了,這個比微信友善多了。直接貼代碼

//支付寶支付
//把請求參數打包成數組
Map<String, String> params = new HashMap<String, String>();
params.put("service", AlipayConfig.service);
params.put("partner", AlipayConfig.partner);
params.put("seller_id", AlipayConfig.seller_id);
params.put("_input_charset", AlipayConfig.input_charset);
//params.put("qr_pay_mode", AlipayConfig.qr_pay_mode);
//異步通知支付結果連接配接
//params.put("notify_url", AlipayConfig.notify_url);
//支付寶處理完請求後,自動跳轉到指定頁面
params.put("return_url", AlipayConfig.return_url);
//傻逼支付寶“anti_phishing_key”同步回調不傳回這個參數,請求的時候不傳遞
//params.put("anti_phishing_key", AlipaySubmit.query_timestamp());
//支付使用者的ip
params.put("exter_invoke_ip", AlipayConfig.exter_invoke_ip);
params.put("out_trade_no", order.getOrderNumber());
params.put("subject", body.toString());
params.put("payment_type", AlipayConfig.payment_type);
params.put("it_b_pay", AlipayConfig.it_b_pay);
params.put("total_fee", sumprice);
params.put("body", body.toString());
						
//請求支付接口
html = AlipaySubmit.buildRequest(params, "POST","确認");
logger.info("支付寶請求HTML:\n"+html);
//更新訂單的支付方式,金額等
order.setPayAmount(Float.parseFloat(sumprice));
order.setPayType(payType);
if(orderService.updatePay(order) > 0){
	result.put("code", "ok");
}else{
	result.put("code", "err");
}
result.put("msg", html);
           

我這裡用的是同步回調方式,當然也可以用異步和微信是一樣的,支付寶是拼接一個自動送出的form表單,然後把這歌表單寫到頁面就能跳轉到支付寶收銀台,代碼在上面微信生成二維碼那裡,拿下來

//支付寶
$("#ali_pay").html(data.msg);
           

這樣子表單會自動送出,頁面會跳轉到支付寶收銀台,完成支付後會從支付寶那裡跳轉 需要一個處理的方法,貼代碼

try {
			//擷取支付寶POST過來回報資訊
			Map<String,String> params = new HashMap<String,String>();
			Map requestParams = request.getParameterMap();
			for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
				String name = (String) iter.next();
				String[] values = (String[]) requestParams.get(name);
				String valueStr = "";
				for (int i = 0; i < values.length; i++) {
					valueStr = (i == values.length - 1) ? valueStr + values[i]
							: valueStr + values[i] + ",";
				}
				//亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
				//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
				params.put(name, valueStr);
			}
			
			//商戶訂單号
			String out_trade_no = request.getParameter("out_trade_no");

			//支付寶交易号
			String trade_no = request.getParameter("trade_no");

			//交易狀态
			String trade_status = request.getParameter("trade_status");
			
			//支付價格
			String total_fee = request.getParameter("total_fee");
			
			String seller_id = request.getParameter("seller_id");
			
			//買家支付寶賬号
			String buyer_email = request.getParameter("buyer_email");
			
			//産品描述
			String body = request.getParameter("body");

			Float total_pay = Float.parseFloat(total_fee);
			
			boolean verify_result = AlipayNotify.verify(params);
			
			if(verify_result){//驗證成功
				Order o = orderService.findByOrderNum(out_trade_no);
				if(o != null){
					//請務必判斷請求時的total_fee、seller_id與通知時擷取的total_fee、seller_id為一緻的
					if(o.getPayAmount() == total_pay && AlipayConfig.seller_id.equals(seller_id)){
						//請求參數和回調參數一緻
						if(trade_status.equals("TRADE_FINISHED") || trade_status.equals("TRADE_SUCCESS")){
							//注意:付款完成後,支付寶系統發送該交易狀态通知
							//判斷該筆訂單是否是待付款狀态
							if(o.getStatus() == 0){
								//如果沒有做過處理,更改訂單狀态為确認中狀态(1),更新支付完成時間
								orderService.updateStatus(o.getId(), 1);
								orderService.updatePayCompleteTime(o.getId());
								
								//插入支付日志
								Map<String,String> map = new HashMap<String,String>();
								map.put("pro_type", "銷售");
								map.put("pay_type", "支付寶");
								map.put("pro_info", body);
								map.put("user_name", o.getBuyMember().getUsername());
								map.put("trade_no", trade_no);
								map.put("pay_amount", o.getPayAmount()+"");
								map.put("status", "支付完成");
								map.put("buyer_email", buyer_email);
								orderService.insertPayLog(map);
								//跳轉到支付成功頁面
								return "redirect:/xxx/xxxx.jspx?orderId="+o.getId();
							}
						}
					}
				}
			}else{//驗證失敗
				response.setContentType("text/html;charset=UTF-8");
				response.setCharacterEncoding("UTF-8");
				response.getWriter().write("Sign Failure , 驗簽失敗!");
			}
		} catch (Exception e) {
			logger.info(e.getMessage(), e);
		}
           

這個地方要注意 支付寶給的demo 是這樣寫的

<span style="white-space:pre">	</span>亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
<span style="white-space:pre">	</span>valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
           

這裡千萬不要一上來就用“UTF-8” 不然會亂碼 然後就會出現簽名錯誤的問題。至此微信和支付寶支付都做成功了。

微信和支付寶支付實戰

繼續閱讀