weChatPay心路歷程

微信公衆平臺

 (此處只講pay)

  微信商戶平臺,公衆號的後臺管理工具,包含公衆號的商戶信息,公衆號支付,掃碼支付,刷卡支付

  1.商戶信息包含商戶號,和此公衆平臺關聯的商戶號,需登錄商戶平臺設置商戶祕鑰key

  2.公衆號支付包含支付授權目錄,測試白名單(作用於微信Web開發者工具測試),掃碼支付回調URL,刷卡支付

  3.公衆平臺基本配置中查看本公衆號的AppId,支付時使用 


微信商戶平臺

  微信商戶平臺,微信支付的後臺管理工具,包含流水,訂單,數據中心,賬戶中心

  1.每個公衆號對應一個商戶平臺,商戶平臺有自己祕鑰key,支付中使用

  2.APP支付的微信開放平臺也對應一個商戶平臺,APP支付的key需使用此商戶平臺的


微信開放平臺

  微信開放平臺,移動端使用的平臺,APP支付需創建此平臺,創建移動應用

  1.創建此平臺,審覈移動應用,通過後會發郵件,其中包含商戶賬戶信息,APP支付需使用

  2.開放平臺使用自己的AppId,和對應商戶的key,切記

  3.App支付和公衆號的支付使用的AppId和key都不相同


 

微信公衆號支付

  JsApi支付

  1.調用支付接口,判斷訂單號是否正確

 

        /**
	 * 獲取微信支付第三方授權URL
	 * 
	 * @param orderNo
	 *            訂單編號,授權之前要拿到自己服務端的訂單號
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@RequestMapping(value = "/generateAuthurlWithOuttradeno")
	public String generateAuthurlWithOuttradeno(
			String orderNo,
			ModelMap modelMap,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		
		// 重定向URL,用戶授權後會自動重定向到這裏,就可以獲取用戶授權信息
		String basePath = Constant.domain+"wechat/wechatPay/doWeChatAuth.do";
		if (orderNo != null && orderNo.length() > 0) {
			// 根據重定向URL構建授權URL,具體方法見下面的方法:buildAuthUrl
			String authUrl = WechatUrl.buildAuthUrl(Constant.APPID, basePath,
					orderNo);
			modelMap.put("stateCode", 200);
			modelMap.put("msg", "操作成功");
			modelMap.put("data", authUrl);
		} else {
			modelMap.put("stateCode", 500);
			modelMap.put("msg","錯誤的訂單號:" + orderNo );
			modelMap.put("data", "");
		}
		logger.info("進來了----------"+modelMap.get("data"));
		return "gcar/weChat/weChatUserInfo";
	}
	
	/**
	 * AUTH2.0 網頁授權處理,這個不需要自己調用,微信會重定向到這裏
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@RequestMapping("doWeChatAuth")
	public void doWeiChatAuth(
			@RequestParam("state") String outTradeNo,// 訂單編號,來自授權url
			ModelAndView modelAndView, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		// 獲取授權後的code
		String code = request.getParameter("code");
		logger.info("微信回調,獲取用戶授權code"+code);
		if ("authdeny".equals(code) == false) {
			// 獲取用戶授權後的信息
			String authMsg = WechatUrl.fetchAuthReturnMsg(Constant.APPID,
					Constant.APPSECRET, code, "POST");
			logger.info("微信回調,獲取用戶授權信息"+authMsg);
			// JSONObject爲阿里的fastjson提供
			AuthJsonObject authJsonObject = JSONObject.parseObject(authMsg,
					AuthJsonObject.class);
			// 刷新AccessToken,默認獲取的有效期太短
			String refreshTokenobject = WechatUrl.refreshAccessToken(
					Constant.APPID, authJsonObject.getRefresh_token(), "POST");
			AuthJsonObject refreshObject = JSONObject.parseObject(
					refreshTokenobject, AuthJsonObject.class);
			// 根據AccessToken和用戶的openId獲取用戶信息
			String userInfo = WechatUrl.getUserinfo(
					refreshObject.getAccess_token(), refreshObject.getOpenid(),
					"POST");
			WeChatUserInfo weChatUserInfo = JSONObject.parseObject(userInfo,
					WeChatUserInfo.class);
			if (weChatUserInfo != null) {
				// 請求轉發到處理微信統一下單的接口,獲取JS SDK調用的配置參數和支付用的參數
				response.sendRedirect(Constant.domain+"wechat/wechatPay/redirectToPayDetail.do?no="
						+ outTradeNo
						+ "&param_0="
						+ weChatUserInfo.getOpenid());
			}
		} else {
			response.getOutputStream().write(new String("用戶拒絕授權").getBytes());
		}
	}

	/**
	 * 微信公衆號 支付
	 * 獲取微信JS SDK初始化配置參數,調用微信統一下單接口獲取微信prepay_id,獲取JS支付用的參數
	 * 
	 * @param op_d
	 *            用戶openId
	 * @param no
	 *            訂單號
	 * @param modelAndView
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException
	 * @throws IllegalAccessException
	 */
	@RequestMapping("redirectToPayDetail")
	public String redirectToPayDetail(
			@RequestParam("param_0") String op_d,
			@RequestParam("no") String no, ModelMap modelMap,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException, IllegalAccessException {
		logger.info("進去系統統一下單接口");
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		// JS SDK簽名URL,是當前頁面的location,這裏就是第三步重定向的URL,用來參與計算JS SDK初始化的簽名
		String signUrl = Constant.domain+"wechat/wechatPay/redirectToPayDetail.do?no="
				+ no + "&param_0=" + op_d;
		// 簽名隨機字符串,由於微信裏面的坑太多,所以我們所有的簽名字符串和時間戳最好公用,不然真的會蒙圈的
		String weiPaySignStr = WechatUrl.getRandomStringByLength(20);
		// 簽名用時間戳 單位秒
		String timestamp = (System.currentTimeMillis()/1000) + "";
		// 獲取JS API 初始化配置參數
		Map<String, String> initConfigParams = WechatUrl.FetchConfigParams(signUrl,
				weiPaySignStr, timestamp);
		// 根據訂單號驗證訂單是否存在,根據自己業務寫  查詢訂單
		//Userorderlist order = userorderlistMapper.selectByOrderNo(no);
	//	if (order != null && order.getOrderno().length() > 0) {// 訂單存在的時候就進行支付前一系列準備工作
			// 統一下單簽名參數哈希表
			// 統一下單的body,就是商品詳情,要是傳中文記得newString(body.getBytes("ISO8859-1")),不然JS簽名失敗,
			// 但是即使這樣,支付成功後微信給你發的支付憑證中商品詳情還是亂碼顯示,所以傳英文吧,這坑懶得去填
			String body = "jiche WeChat Order Pay";
			Map<String, Object> prepaySignParam = new HashMap<String, Object>();
			prepaySignParam.put("appid",  Constant.APPID);
			prepaySignParam.put("mch_id",  Constant.MCHID);
			prepaySignParam.put("body", body);
			prepaySignParam.put("nonce_str", weiPaySignStr);
			// 微信異步通知地址,在這裏處理後續訂單邏輯
			prepaySignParam.put("notify_url",
					Constant.domain+"wechat/wechatPay/weipayCallBack.do");
			prepaySignParam.put("out_trade_no", no);
			prepaySignParam.put("spbill_create_ip", request.getRemoteAddr());// 用戶IP地址
			// 單位分
			//prepaySignParam.put("total_fee",new BigDecimal(Double.parseDouble(order.getTotalprice()) * 100).intValue());
			prepaySignParam.put("total_fee",1);
			prepaySignParam.put("trade_type", "JSAPI");
			prepaySignParam.put("openid", op_d);

			// 構造微信預支付訂單
			WeiChatPreOrder weiChatPreOrder = new WeiChatPreOrder();
			weiChatPreOrder.setAppid(Constant.APPID);
			weiChatPreOrder.setMch_id(Constant.MCHID);
			weiChatPreOrder.setBody(body);
			weiChatPreOrder.setNonce_str(weiPaySignStr);
			weiChatPreOrder
					.setNotify_url(Constant.domain+"wechat/wechatPay/weipayCallBack.do");
			weiChatPreOrder.setOut_trade_no(no);// 系統訂單號
			weiChatPreOrder.setSpbill_create_ip(request.getRemoteAddr());
			//weiChatPreOrder.setTotal_fee(new BigDecimal(Double
			//		.parseDouble(order.getTotalprice()) * 100).intValue());// 交易金額
			weiChatPreOrder.setTotal_fee(1);
			weiChatPreOrder.setTrade_type("JSAPI");
			weiChatPreOrder.setOpenid(op_d);
			// 計算統一下單簽名參數計算預支付訂單的MD5簽名
			weiChatPreOrder.setSign(Signature.getSign(prepaySignParam));
			// 生成XML訂單
			String xmlOrder = weiChatPreOrder.toXml();
			logger.info("統一下單接口的xml"+xmlOrder);
			// 通過微信下單接口獲取prepay_id
			String prepayId = WechatUrl.getPrepayId(xmlOrder);
			logger.info("統一下單接口的prepayId"+prepayId);
			// JS支付參數
			SortedMap<String, String> paySignparams = new TreeMap<String, String>();
			//切記此處的參數一定要和微信文檔一樣,最好粘貼過來,別自己寫
			paySignparams.put("appId", Constant.APPID);
			paySignparams.put("timeStamp", timestamp);
			paySignparams.put("nonceStr", weiPaySignStr);
			paySignparams.put("signType", "MD5");
			// 計算JS SDK支付調用簽名字符串【appId, nonceStr,package,signType,timeStamp】
			StringBuffer signBuff = new StringBuffer();
			signBuff.append("appId=").append(Constant.APPID + "&nonceStr=")
					.append(weiPaySignStr + "&package=prepay_id=")
					.append(prepayId + "&signType=MD5&timeStamp=")
					.append(timestamp + "&key=").append(Constant.WECHAT_KEY);
			// 計算MD5簽名
			String paySign = MD5Util.MD5Encode(signBuff.toString());
			paySignparams.put("paySign", paySign);
			modelMap.put("configParam", initConfigParams);
			modelMap.put("payParams", paySignparams);
			modelMap.put("msg", "ok");
			modelMap.put("payId", prepayId);
			//modelMap.put("order", order);// 訂單信息
			
			//request.setAttribute("payId", prepayId);
		/*} else {
			modelAndView.addObject("msg", "找不到訂單/無效的訂單編號");
		}*/
			// 返回支付頁面
		return "gcar/weChat/weChatPay";
	}
	/**
	 *  微信支付異步通知處理接口
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException
	 */
	@RequestMapping("weipayCallBack")
	public void weipayCallBack(HttpServletRequest request,HttpServletResponse response) throws IOException{
	logger.info("**************************微信支付異步回調通知開始***********************");
		//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);
	 }
	 outSteam.close();
	 inStream.close();
	 String resultStr  = new String(outSteam.toByteArray(),"utf-8");
	 Map<String, Object> resultMap = new HashMap<String, Object>();
	 try {
	  resultMap = XMLParser.getMapFromXML(resultStr);
	  String out_trade_no = (String) resultMap.get("out_trade_no");
	  String return_code = (String) resultMap.get("return_code");
	  String total_fee = (String) resultMap.get("total_fee");
	  String bank = (String) resultMap.get("bank_type");
	  String transaction_id = (String) resultMap.get("transaction_id");
	  Integer fee = Integer.decode(total_fee)/100;
	  //簽名驗證
	  boolean valid = Signature.checkIsSignValidFromResponseString(resultStr);
	  ChargeOrderLog chargeOrderLog = chargeOrderLogService.findChargeOrderLogByOrderNo(out_trade_no);
	  ChargeOrderLog log = new ChargeOrderLog();
	  if(chargeOrderLog == null){
          this.logger.info(out_trade_no + ",訂單不存在.....");
      }else{
    	  //日誌
    	  log.setOrderNo(chargeOrderLog.getOrderNo());
    	  log.setBank(fee.toString());
		  log.setWechatJson(resultMap.toString());
		  log.setLogType(0);
		  log.setCompletionTime(new Date());
		  log.setBank(bank);
		  log.setPayWaterNo(transaction_id);
      }	  
	  if(return_code.equals("SUCCESS") && valid){
		  try {
			chargeOrderLogService.updateChargeOrderLogComplete(log);
		  } catch (Exception e) {
				 logger.info("************微信支付異步回調查詢訂單異常"+out_trade_no);
		  } 
	      //處理訂單後續業務
		  logger.info("************微信支付異步回調通知支付成功"+resultMap);
	  }else{
		  try {
			  chargeOrderLogService.updateChargeOrderLogFail(log);
		} catch (Exception e) {
			 logger.info("************微信支付異步回調插入日誌異常"+e.getMessage());
		}
		logger.info("************微信支付異步回調通知支付失敗"+resultMap);
		 
	  }
	 } catch (ParserConfigurationException e) {
		 logger.info("************微信支付異步回調異常"+e.getMessage()+"微信返回"+resultStr);
	  e.printStackTrace();
	 } catch (SAXException e) {
		 logger.info("************微信支付異步回調異常"+e.getMessage()+"微信返回"+resultStr);
	  e.printStackTrace();
	 }
	 //通知微信.異步確認成功.必寫.不然會一直通知後臺.八次之後就認爲交易失敗了.[一定別手賤傳return_msg回去,他們傻逼會繼續回調的]
	 String success = "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
	 response.getOutputStream().write(new String(success).getBytes());
	 logger.info("**************************微信支付異步回調通知結束***********************");
	}
//jsp頁面,發起jsAPI	
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/common/init-taglib.jsp"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=320, user-scalable=0, initial-scale=1,maximum-scale=1">
    <meta content="yes" name="apple-mobile-web-app-capable"/>
    <meta content="yes" name="apple-touch-fullscreen"/>
    <meta content="telephone=no" name="format-detection"/>
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
<title>微信支付訂單詳情確認頁面</title>
 <link href="${ctx }/static/gcar_html/css/service.css" rel="stylesheet" type="text/css" />
    
      <link href="${ctx}/static/gcar_html/css/css.css" rel="stylesheet">
    <link href="${ctx}/static/gcar_html/css/popup.css" rel="stylesheet">
  <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> 
    <script src="${ctx}/static/gcar_html/js/popup.js"></script>
    <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
    
</head>
<body>
<div class="body">
<div class="head">
 <%-- <div class="logo"><img src="<%=basePath%>jsp/weixin/img/logo1.png"/></div> --%>
 <div><span>極車公社科技有限公司</span></div>
 
</div>

<c:if test="${msg eq 'ok' }">
<script type="text/JavaScript">
console.log('${payParams.appId}');
console.log('${configParam.timestamp}');
console.log('${configParam.signStr}');
console.log('${configParam.sign}');
console.log('${payParams.timeStamp}');
console.log('${payParams.nonceStr}');
console.log('${payParams.signType}');
console.log('${payParams.paySign}');
console.log('${payId}');
function payWeixin(){
	window.wx.config({
		debug:false,//關閉了JS調試,開發階段可爲true
		appId: '${payParams.appId}',
		timestamp: '${configParam.timestamp}',
		nonceStr: '${configParam.signStr}',
		signature: '${configParam.sign}',
		jsApiList: ['checkJsApi', 'chooseWXPay']
	});
	wx.ready(function() {
	wx.checkJsApi({
	   jsApiList: ['chooseWXPay'],
	   success: function(res) 
	   {
	    	console.log(JSON.stringify(res));
	   }
	   });
	wx.chooseWXPay({
	   timestamp: '${payParams.timeStamp}', 
	   nonceStr: '${payParams.nonceStr}', 
	   package: "prepay_id=${payId}",
	   signType: '${payParams.signType}',
	   paySign: '${payParams.paySign}',
	   success: function (res) 
	   {
	   // 支付成功後的回調函數
	     alert("支付成功");//後續動作自己定
	     console.log('支付成功');
	   }
});
})
}
</script>
<div class="oderDetail">
 <!-- 支付訂單詳情顯示,自己弄-->
   <h1>我要支付</h1>
</div>
<div class="ok">
<button onClick="payWeixin()" class="button button-raised button-caution" type="button">確定支付</button>
</div>
</c:if>
<c:if test="${msg ne 'ok' }">
  <div style="margin-top: 25% auto;">
 <h3>
   ${msg }
 </h3>
  </div>
</c:if>
</div>
</body>
</html>


App支付 

  App支付就簡單多了,只需要統一下單接口,傳給APP端,發起調用就ok

  

    /**
     * 構造微信統一下單接口 返回數據
    * @Description:TODO      
    * @author:JIAZHIPENG  
    * @time:2016-10-19 下午7:17:19   
    * @return String
     */
     private SortedMap<String, Object> makeWechatAppPay(ChargeOrder chargeOrder,HttpServletRequest request){
    	logger.info("進去系統統一下單接口");
    	SortedMap<String, Object> paySignparams = new TreeMap<String, Object>();
    	SortedMap<String, Object> newPaySignparams = new TreeMap<String, Object>();
    	try {
			// 簽名隨機字符串,由於微信裏面的坑太多,所以我們所有的簽名字符串和時間戳最好公用,不然真的會蒙圈的
			String weiPaySignStr = WechatUrl.getRandomStringByLength(20);
			// 簽名用時間戳 秒
			String timestamp = (System.currentTimeMillis()/1000) + "";
			// 統一下單簽名參數哈希表
			// 統一下單的body,就是商品詳情,要是傳中文記得newString(body.getBytes("ISO8859-1")),不然JS簽名失敗,
			// 但是即使這樣,支付成功後微信給你發的支付憑證中商品詳情還是亂碼顯示,所以傳英文吧,這坑懶得去填
			String body = "jiche WeChat Order Pay";
			Map<String, Object> prepaySignParam = new HashMap<String, Object>();
			prepaySignParam.put("appid",  Constant.APPID);
			prepaySignParam.put("mch_id",  Constant.MCHID);
			prepaySignParam.put("body", body);
			prepaySignParam.put("nonce_str", weiPaySignStr);
			// 微信異步通知地址,在這裏處理後續訂單邏輯
			prepaySignParam.put("notify_url",
					Constant.domain+"wechat/wechatPay/weipayCallBack.do");
			prepaySignParam.put("out_trade_no", chargeOrder.getChargeOrderNo()); //獲取系統訂單編號
			prepaySignParam.put("spbill_create_ip", request.getRemoteAddr());// 用戶IP地址
			//測試先改爲0.01
			// 單位分
			prepaySignParam.put("total_fee",1);
			//正式
			//prepaySignParam.put("total_fee",chargeOrder.getTotal().multiply(new BigDecimal(100)).intValue());
			prepaySignParam.put("trade_type", "APP");
			
			// 構造微信預支付訂單
			WeiChatPreAppOrder weiChatPreOrder = new WeiChatPreAppOrder();
			weiChatPreOrder.setAppid(Constant.APPID);
			weiChatPreOrder.setMch_id(Constant.MCHID);
			weiChatPreOrder.setBody(body);
			weiChatPreOrder.setNonce_str(weiPaySignStr);
			weiChatPreOrder
					.setNotify_url(Constant.domain+"wechat/wechatPay/weipayCallBack.do");
			weiChatPreOrder.setOut_trade_no(chargeOrder.getChargeOrderNo());// 系統訂單號
			weiChatPreOrder.setSpbill_create_ip(request.getRemoteAddr());
			//測試先改爲0.01
			weiChatPreOrder.setTotal_fee(1);
			//正式
			//weiChatPreOrder.setTotal_fee(chargeOrder.getTotal().multiply(new BigDecimal(100)).intValue());
			weiChatPreOrder.setTrade_type("APP");
			// 計算統一下單簽名參數計算預支付訂單的MD5簽名
			logger.info("微信的key"+Constant.WECHAT_KEY);
			weiChatPreOrder.setSign(Signature.getSign(prepaySignParam));
			// 生成XML訂單
			String xmlOrder = weiChatPreOrder.toXml();
			logger.info("統一下單接口的xml"+xmlOrder);
			// 通過微信下單接口獲取prepay_id
			String prepayId = WechatUrl.getPrepayId(xmlOrder);
			logger.info("統一下單接口的prepayId"+prepayId);
			// 支付參數 切記全小寫,對照APP支付微信文檔 粘貼過來
			paySignparams.put("appid", Constant.APPID);
			paySignparams.put("partnerid", Constant.MCHID);
			paySignparams.put("prepayid", prepayId);
			paySignparams.put("package", "Sign=WXPay");
			paySignparams.put("noncestr", weiPaySignStr);
			paySignparams.put("timestamp", timestamp);
			logger.info("傳給app的參數 appId"+Constant.APPID+"partnerId"+Constant.MCHID
					+"prepayId"+prepayId+"noncestr"+weiPaySignStr+"timeStamp"+timestamp+"key"+Constant.WECHAT_KEY);
			// 計算MD5簽名 APP發起支付參與 [參與簽名的字段名爲appId,partnerId,prepayId,nonceStr,timeStamp,package]
			String paySign = Signature.getSign(paySignparams);
			logger.info(paySign);
			//String paySign = MD5Util.MD5Encode(signBuff.toString());
			paySignparams.put("sign", paySign);
			newPaySignparams.put("appId", Constant.APPID);
			newPaySignparams.put("partnerId", Constant.MCHID);
			newPaySignparams.put("prepayId", prepayId);
			newPaySignparams.put("packageValue", "Sign=WXPay");
			newPaySignparams.put("nonceStr", weiPaySignStr);
			newPaySignparams.put("timeStamp", timestamp);
			newPaySignparams.put("sign", paySign);
		} catch (Exception e) {
			logger.info("app獲取支付所需參數錯誤"+e.getMessage());
		}
		// 返回支付頁面
         return newPaySignparams;
    }
  

   App回調支付和JsApi的一樣,不做詮釋

   後面附件會上次支付需要的工具類,需要的小夥伴可以下載,記得好評!

                                                 author:賈小仙

                                                 time:2016/10/19 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章