1. 程式人生 > >全面詳細的微信支付思路流程以及專案程式碼分享

全面詳細的微信支付思路流程以及專案程式碼分享

之前一直沒有接觸微信支付這方面的業務,現在因專案需要,需要用到此功能,開始各種百度,稍微瞭解了一下,微信支付分為:

支付寶支付、APP支付、掃碼支付,但是對於H5支付和支付寶支付現在還是沒有徹底搞明白他兩的區別,希望大佬們可以稍微提點一二,小弟先在此謝過!

大體思路如下:

1】.獲取code(使用者進入微信公眾號即可獲得code,直接通過request.getParameter("code")獲得),

       一般情況下前臺會傳過來,後臺直接拿;

      或者自己去獲取code值,參考連結https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

      裡邊appid要改變換成自己專案中的appid即可,redirect_uri要改變,其餘不用改變。

     其中可能出錯的地方是redirect_uri,我自己也遇到過這個坑,在此把解決方案也發出來,方便大家參考

   獲取code時,出現微信的redirect_uri引數錯誤解決辦法

      參考連結:https://www.cnblogs.com/xbj-2016/p/7086149.html

若沒有錯誤,在微信客戶端點開下面連結,會開啟的頁面就是你上圖中域名的地址,其頁面中的地址就會包含code值,現在就獲取到code了,棒棒噠!

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

2】.根據code獲取openid

      code獲取到的是access_token,json字串,裡面有個引數為openid,這是我們需要的;

      openid即就是每個使用者在同一個公眾號appid中的唯一標識,他就是用來識別不同使用者的;

      Code值只可以使用一次,然後就過期失效,若使用同一個code值去獲取openid,顯示已被使用,報錯,

      即使同一個人使用也不行;Code獲取openid,同一個使用者所獲取到的openid永遠是同一個,不會改變。

      Access_token兩個小時就會失效, 但是Openid不會失效。

3】.客戶端頁面中點選支付按鈕時,給資料庫中儲存訂單,(如此單多少錢等等)

4】.調微信支付下單介面(最終是要拿到預付單資訊prepay_id,說明就成功了)

     裡面傳入各種引數,和上面生成的openid,來生成sign簽名,然後把這些引數(包括openid、sign簽名)轉成xml格式,

    呼叫微信伺服器,生成xml字串,把他轉成map格式,然後拿取預付單資訊prepay_id,把其當成引數,再次獲取生成sign

    簽名,然後把這些引數傳給前端(json格式),到這塊已經支付成功了;

5】.回撥方法

回撥方法中作相應的業務邏輯處理,到了回撥方法中,就說明已經支付成功了,現在就是把資料庫中的對應訂單改變其狀態啥的,例如為已支付,完了後返回給前端;

程式碼:

(1):通過code獲取openid

/**
	 * 獲取openId
	 * @param code
	 * @return
	 */
	@ApiOperation(value="根據code獲取openid",notes="")
	@PostMapping("/getOpenId")
	public ResBuilder<?> getOpenId(@RequestParam("code") String code){
		Map<String,Object> map = new HashMap<String,Object>();
		try {
			map.put("status", 0);//預設失敗
			map.put("appId", WxPayConfig.APPID);//為了給前端用
			//根據code獲取openid(使用者進入微信公眾號即可獲得code,前臺直接傳過來)
			if(!StringUtil.isNullOrEmpty(code)){//有code
				//請求api獲取openid
				String str = HttpInvoker.sendGetRequest(StringUtil.format(WxPayConfig.GET_OPENID, new Object[]{WxPayConfig.APPID,WxPayConfig.APPSECRET,code}));
				//將json轉map
				Map<String, Object> toMap = WeixinUtil.jsonStrToMap(str);
				System.out.println(toMap.toString());
				if(!StringUtil.isNullOrEmpty(toMap.get("openid"))){//獲取openid成功
					map.put("status", 1);//成功
					map.put("openId", toMap.get("openid").toString());
					return new ResBuilder<>("ok",map);
				}else {
					return new ResBuilder<>("error","獲取openid失敗");
				}
			}else {
				return new ResBuilder<>("error1","沒有傳入code值");
			}	
		} catch (Exception e) {
			e.printStackTrace();
			return new ResBuilder<>("no","系統異常");
		}		
	}

 (2):統一下單介面程式碼


	/**
	 * 統一下單 (預支付)
	 * @author zl
	 * @date 2018年9月29日  上午11:10:22
	 * @param map	openId--微信id;orderId--·預存訂單id body--商品名;deviceId--組櫃裝置id;total_fee--金額(分) -- 實際金額需乘以 100
	 * @return
	 */
	@ApiOperation(value="統一下單介面",notes="")
	@PostMapping("/unifiedOrder")
	public ResBuilder<?> unifiedOrder(@RequestBody Indents indents,HttpServletRequest request){
		Map<String,Object> rsMap = new HashMap<String, Object>();
		try {
			Client client=clientRepository.findById(indents.getUserid());//客戶
			if(StringUtil.isNullOrEmpty(client.getOpenId())) {
				return new ResBuilder<>("no","openId為空");
			}
			Map<String,String> paramMap = new HashMap<String, String>();
			paramMap.put("appid", WxPayConfig.APPID);//appid
			paramMap.put("mch_id", WxPayConfig.MCHID);//商戶號
			paramMap.put("nonce_str", WeixinUtil.createNoncestr(32));//隨機字串
			StoreGoods storeGoods=storeGoodsRepository.findById(indents.getStoreid());//商品
			paramMap.put("body", storeGoods.getName());//商品名稱
			paramMap.put("out_trade_no", indents.getIndents());//訂單號
			paramMap.put("total_fee",indents.getBigmoney()+"");//總金額
			paramMap.put("spbill_create_ip", WeixinUtil.getRemoteHost(request));//客戶(下單者的)ip地址
			paramMap.put("notify_url", WxPayConfig.NOTIFY_URL);//回撥地址
			//paramMap.put("trade_type", "JSAPI");//交易型別為公眾號支付
			paramMap.put("trade_type", "MWEB");//(交易型別)H5支付必須為MWEB
			paramMap.put("openid", client.getOpenId());//使用者openid
			paramMap.put("sign", WeixinUtil.getSign(paramMap));//簽名 ,預設sign_type是MD5
			//呼叫統一下單時多了一個sign的引數,其他引數名稱需要全部相同。
			String unifiedStr = HttpInvoker.sendPostRequest(WxPayConfig.UNIFIED_ORDER, WeixinUtil.arrayToXml(paramMap));
			//微信端生成的是xml格式,現在轉成map格式
			Map<String, String> preMap = WeixinUtil.xmltoMap(unifiedStr);
			//微信生成的預支付回話標識,用於後續介面呼叫中使用,該值有效期為2小時
			String prepayId = preMap.get("prepay_id");
			if(StringUtil.isNullOrEmpty(prepayId)){
				throw new Exception("獲取prepay_id錯誤:" + preMap.get("return_msg"));
			}
			
			
			//微信內H5調起支付,網頁端介面請求引數列表
			/*返回結果值說明
			返回值	描述
			get_brand_wcpay_request:ok	支付成功
			get_brand_wcpay_request:cancel	支付過程中使用者取消
			get_brand_wcpay_request:fail	支付失敗
			呼叫支付JSAPI缺少引數:total_fee	
			   1、請檢查預支付會話標識prepay_id是否已失效
			   2、請求的appid與下單介面的appid是否一致*/
			Map<String,String> jsApiMap = new HashMap<String,String>();
			jsApiMap.put("appId", WxPayConfig.APPID);
			jsApiMap.put("timeStamp", WeixinUtil.getTimestamp(new Date()) + "");
			jsApiMap.put("nonceStr", WeixinUtil.createNoncestr(32));
			jsApiMap.put("package", "prepay_id=" + prepayId);
			jsApiMap.put("signType", "MD5");
			jsApiMap.put("paySign", WeixinUtil.getSign(jsApiMap));      
			rsMap.put("status", 1);//成功
			rsMap.put("param", JSONObject.fromObject(jsApiMap));
			JSONObject json = JSONObject.fromObject(rsMap);
			//out.print(json);把這些引數傳送給前端,前端給微信伺服器發起一個微信支付請求,此時前端頁面顯示的是使用者輸入密碼介面,
			//然後輸完密碼返回給前端是支付成功還是失敗.
			return new ResBuilder<>("ok",json);						
		} catch (Exception e) {
			e.printStackTrace();
			return new ResBuilder<>("no","系統異常");
		}
	}

(3):回撥方法

/**
	 * 
	 * 回撥方法   此處作業務邏輯處理,到此處說明已支付成功,在這塊需要改變訂單狀態等邏輯處理的資訊
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException
	 */
	
	@ApiOperation(value="回撥業務處理",notes="")
	@PostMapping("/notifyUrl")
	public String notifyUrl(HttpServletRequest request,HttpServletResponse response) throws IOException {
		String resXml="";
		String xmlBack="";
        System.err.println("進入非同步通知");
		InputStream is = request.getInputStream();
        //將InputStream轉換成String
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                     sb.append(line + "\n");
                   }
          } catch (IOException e) {
               e.printStackTrace();
            } finally {
        try {
             is.close();
             } catch (IOException e) {
              e.printStackTrace();
              }
     }
        resXml=sb.toString();
        System.err.println(resXml);
        Map<String, String> notifyMap = new HashMap<String, String>();
        try {
        	notifyMap = WXPayUtil.xmlToMap(resXml);
			String  return_code = notifyMap.get("return_code");//狀態
            String out_trade_no = notifyMap.get("out_trade_no");//訂單號
            if("SUCCESS".equals(return_code)){
            	Indents indents=indentsRepository.findByIndents(out_trade_no);
            	if(indents==null) {
   				 xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";	
   				 return xmlBack;
   			 }
            	indents.setState(1);//修改狀態為1:已支付
            	SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            	indents.setPaytime(sdf.format(new Date()));//設定支付日期   
            	indentsRepository.save(indents);
            	xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            }else {
            	xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
            }	
		} catch (Exception e) {
		}
        return xmlBack;				
	}

(4):把微信配置檔案類在此也發出來

package org.travel.config;

/**
 * 微信相關引數配置
 * @author Administrator
 *
 */
public class WxPayConfig {

	//=======【基本資訊設定】=====================================
		//微信公眾號身份的唯一標識。稽核通過後,在微信傳送的郵件中檢視
		public static final String APPID = "*************";//需改,給客戶要
		//受理商ID,身份標識
		public static final String MCHID = "**********";//需改,給客戶要
		//商戶支付金鑰Key。稽核通過後,在微信傳送的郵件中檢視
		public static final String KEY = "**********";//需改,給客戶要
		//JSAPI介面中獲取openid,稽核後在公眾平臺開啟開發模式後可檢視
		public static final String APPSECRET = "*******";//需改,給客戶要
		
		
  //=======【非同步通知url設定】===================================
		//非同步通知url,商戶根據實際開發過程設定
		private static String SERVER_URL = "http://*.*.*.*:****";//需改專案在公網上的地址 
         (192伺服器不可以,只是區域網,59伺服器可以)
		
        //自己寫的回撥方法的地址
		public static final String NOTIFY_URL = SERVER_URL + "/WxGetOpenId/notifyUrl";

		
		//編碼方式
		public static final String ENCODE = "UTF-8";
		
		//微信uri-根據code獲取openid(獲取access_token,access_token裡面帶有openid)
		public static String GET_OPENID = 
                "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret= 
                {1}&code={2}&grant_type=authorization_code";//不變


		//微信uri-請求預支付介面    
		public static String UNIFIED_ORDER = 
                "https://api.mch.weixin.qq.com/pay/unifiedorder";//不變
	
	
}

(5):專案所需sdk


		<!-- 微信 -->
		<dependency>
			<groupId>com.github.wxpay</groupId>
			<artifactId>wxpay-sdk</artifactId>
			<version>0.0.3</version>
		</dependency>
		
		<dependency>
    		<groupId>net.sf.json-lib</groupId>
    		<artifactId>json-lib</artifactId>
    		<version>2.4</version>
    		<classifier>jdk15</classifier>
		</dependency>

至此:大功告成!