日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【来C站:一起聊java】用java实现微信支付功能的详细设计思路

發布時間:2023/12/29 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【来C站:一起聊java】用java实现微信支付功能的详细设计思路 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

由于上一個項目的小程序支付模塊的歷練,讓我意識到支付確實是一個復雜且測試起來需要的配置特別復雜的模塊,這么說吧,學生想要實打實的測試微信支付太難了,它需要你有企業的相關證明,營業執照呀,公眾號商戶號,不論資質是否滿足辦理條件,單論開戶費用就是幾百塊的收,但是微信和支付寶都提供了沙箱環境,對于沙箱這個東西,我真的沒什么話說!bug很多,大家經歷過的可以噴一噴。所幸甲方提供了開發所需要的一些參數與配置(商戶號的一些信息,Apv3密鑰與商戶id,商戶證書等)。那么現在不噴別的了,我們來看一些微信支付的開發文檔:


一、首先需要選取商戶的類型(區別最大的就是資金流的流動):

1、直連商戶(即資金流與信息流直接與我們的個體商戶進行流動)

2、服務商(類似于加盟形式,資金流先中轉到中間的服務商商戶再定時轉入加盟的子商戶)

需要注意的是不同的商戶類型開發的模式也是有一定的區別,特別是參數需求的不同,如果誤以為二者的開發模式一樣,可能會導致后續的支付返回信息是參數校驗不正確


二、選取好類型后我們來配置參數(這里我們以直連商戶開聊):

主要的參數為:mchid(商戶號),mchSerialNo(商戶證書序列號),apiV3Key(商戶配置的apiv3密鑰),PrivateKey(證書私鑰),服務器以及配置好的域名(微信后臺是需要頻繁與服務器進行數據交換的)。

下面是微信官方給出的文檔供大家參考:

JSAPI支付-接入前準備 | 微信支付商戶平臺文檔中心 (qq.com)https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml


三、了解流程并分析哪些事情是服務器端需要做的?哪些是小程序需要做的?

先看一下時序圖(來源:微信開發者社區):

?分析流程(注意這里我們用服務器來代表后端,序號不代表處理順序):

1、小程序對服務器發起下單請求(攜帶業務參數如商品信息以及由wx.login()得到的臨時憑證code

2、服務器接收參數,生成業務訂單插入數據庫記錄并帶著code請求微信登錄API得到openidopenid是每一個微信用戶使用小程序時獲取的唯一身份標識,永久唯一且在登錄小程序之后不變)

3、服務器根據商品信息結合openid以微信規定的參數格式生成用戶訂單

4、帶著訂單數據進行一次簽名的驗證之后請求支付統一下單API:

https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

獲取統一預支付會話prepay_id

5、服務器將獲得的prepay_id根據微信需要的數據格式將它與其他參數加載簽名之后得到sign并再次封裝時間戳,sign,隨機字串,小程序appid,包含prepay_id的簽名,將封裝之后的數據發送給小程序。

6、小程序鑒權支付之后,微信后臺會發送一條支付成功的信息給服務器確認處理。

7、服務器端更新業務訂單的狀態。


四、設計關鍵點的處理思路,以及根據微信官方給的文檔設計簽名驗證請求發送封裝數據生成簽名獲取隨機字符串等工具類。

這里我處理的關鍵點openid的獲取與prepay_id的獲取:因為兩次都需要請求微信后臺并實現服務器端與微信后臺的交互,其次服務器端的二次簽名如果出現問題會產生小程序支付時參數驗證錯誤的報錯

(1)看一下獲取openid我的處理代碼:

/*** 為保護APPSecret信息,需要將這次請求放在后臺進行* @param AppId 小程序Id* @param code 換取的臨時憑證* @return 帶有openId的通用對象*/@RequestMapping("/WxLogin")@ResponseBodypublic R<String> WeChatLogin(@RequestParam("AppId")String AppId,@RequestParam("code")String code){try{System.out.println("---------入庫查詢小程序secret---------");String servet = appInfoService.findAppServetByAppId(AppId);System.out.println("---------封裝訪問路由獲取小程序唯一openId-------");String Url = "https://api.weixin.qq.com/sns/jscode2session" +"?appid="+AppId+"&" +"secret="+servet+"&" +"js_code="+code+"&grant_type=authorization_code";System.out.println("封裝路由為"+Url);BasicHttpClientConnectionManager connectionManager;connectionManager = new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(),null,null,null);CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build();HttpGet httpGet = new HttpGet(Url);try{System.out.println("客戶端連接成功,執行請求----------");HttpResponse httpResponse = httpClient.execute(httpGet);HttpEntity entity = httpResponse.getEntity();String s = EntityUtils.toString(entity, "UTF-8");System.out.println("請求執行結束,結果為"+ s);JSONObject jsonObject = new JSONObject(s);String openid = jsonObject.get("openid").toString();R<String> r= new R<>();r.setData(openid);r.setCode(1);r.setMsg("登錄驗證");return r;}catch (Exception e){e.printStackTrace();}}catch (Exception e){return R.error("登錄異常");}return R.error("登錄錯誤");}

不難發現我將敏感的關鍵信息都放在了服務器處理,將openid返回給小程序,后面為了能夠不需要在支付時再發起請求獲取一次openid,我將openid存儲在小程序的全局參數里以供本次會話范圍內調用。(當然為了之后的登錄不再訪問微信后臺獲取openid,我也會將openid存儲在數據庫該用戶的表里)

(2)看一下獲取prepay_id我的處理代碼:

/*** 用戶普通訂單支付后回調* 修改設備信息以及在訂單記錄中插入一條記錄** @param receipt 支付金額* @param deviceId 設備編號* @param userId 用戶編號* @param useTime 使用自習室時長* @return 統一響應對象*/@RequestMapping("/insertUserDevice")@ResponseBody/**設置事務回滾(一般用于多表操作,單表使用異常捕獲就可以實現)*/@Transactional(rollbackFor = Exception.class)public R<Object> InsertUserDevice(@RequestParam("receipt") double receipt, @RequestParam("deviceId") String deviceId,@RequestParam("userId") String userId, @RequestParam("useTime") Integer useTime,@RequestParam("orderTime") String orderTime, @RequestParam("openid")String openid) {try {R<Object> r = new R<>();System.out.println("-------------生成訂單對象---------------");String nonceStr = generateNonceStr();int payNum = new Double(receipt*100).intValue();String money = payNum +"";String order = "{"+ "\"amount\": {"+ "\"total\": "+money+","+ "\"currency\": \"CNY\""+ "},"+ "\"mchid\": \""+mchId+"\","+ "\"description\": \"自習室座位使用\","+ "\"notify_url\": \"https://你的服務器域名.cn/支付成功后異步確認處理接口\","+ "\"payer\": {"+ "\"openid\": \""+openid+"\"" + "},"+ "\"out_trade_no\": \""+nonceStr+"\","+ "\"appid\": \""+appid+"\"" + "}";System.out.println(order);/**封裝整個付款參數*/String prepay_id = Tools.V3PayGet(order);System.out.println("獲取會話id:"+prepay_id+"執行小程序二次加簽");JSONObject jsonObject = Tools.WxTuneUp(prepay_id, WxPayConfig.appid);System.out.println("小程序所需參數封裝完畢,二次加簽完成!");r.setData(jsonObject);/**付款參數封裝完畢*/UserDevice userDevice = new UserDevice();userDevice.setDeviceId(deviceId);userDevice.setUserId(userId);userDevice.setUseTime(useTime);/*** 此時應該在插入這條用戶使用記錄的地方設置redis緩存鍵值對,* key是用戶id+設備id,value是時間。* */System.out.println("--------------插入緩存------------------");/**緩存記錄該訂單時間,到期緩存消失觸發業務*/jedisUtil.InsertOrderServerListener(deviceId,userId,useTime);System.out.println("--------緩存成功,繼續裝箱--------");userDevice.setReceipt(receipt);userDevice.setOrderTime(orderTime);userDevice.setOrderState(true);System.out.println("-----------裝箱完畢,校驗成功后緩存記錄------------");/**將該訂單信息插入訂單表,持久化到數據庫*/boolean insertUserDevice = userDeviceService.InsertUserDevice(userDevice);/**將設備狀態修改為已經被使用*/boolean updateDeviceState = deviceService.updateDeviceStateByDeviceId(deviceId);if (!insertUserDevice || !updateDeviceState) {throw new Exception("插入訂單記錄失敗");}r.setCode(1);r.setMsg("下單成功");return r;} catch (Exception e) {e.printStackTrace();/**回滾支持*/TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return R.error("添加新的訂單失敗");}}/*** 刪除未付款訂單* @param userId 用戶Id 9位* @param deviceId 設備Id 四位* @return*/@RequestMapping("/deleteNoPayOrder")@ResponseBodypublic R<String> deleteNoPayOrder(@RequestParam("userId")String userId,@RequestParam("deviceId")String deviceId){try{/**刪除訂單,刪除緩存,其后釋放設備加的鎖*/boolean deleteNoPayOrder = userDeviceService.deleteNoPayOrder(userId);jedisUtil.DeleteOrderByDeviceId(deviceId);boolean freeDeviceByDeviceId = deviceService.freeDeviceByDeviceId(deviceId);if (deleteNoPayOrder && freeDeviceByDeviceId){return R.success("已刪除未付款訂單");}elsethrow new Exception("刪除未付款訂單失敗");}catch (Exception e){System.out.println("刪除未付款訂單失敗");return R.error("刪除未付款訂單失敗");}}

大家不難發現,這里的openid是直接由小程序端傳過來的,就是上面我存在全局參數里的,當然當你沒有支付的時候,確認訂單狀態在本次支付中沒有改變,就默認你是沒有支付,那么就會把這沒支付的訂單刪除。

大家可能會好奇? ? ?String prepay_id = Tools.V3PayGet(order); 這一句才是核心呀!

不急在下面呢:

public static String V3PayGet(String jsonStr) throws Exception {String body = "";//創建httpclient對象CloseableHttpClient client = HttpClients.createDefault();//創建post方式請求對象HttpPost httpPost = new HttpPost(url_prex + url);//裝填參數StringEntity s = new StringEntity(jsonStr, charset);s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,"application/json"));//設置參數到請求對象中httpPost.setEntity(s);String post = getToken(HttpUrl.parse(url_prex + url), jsonStr);//設置header信息//指定報文頭【Content-type】、【User-Agent】httpPost.setHeader("Content-type", "application/json");httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");httpPost.setHeader("Accept", "application/json");httpPost.setHeader("Authorization","WECHATPAY2-SHA256-RSA2048 " + post);//執行請求操作,并拿到結果(同步阻塞)System.out.println("-------------執行統一下單請求--------------------------------");CloseableHttpResponse response = client.execute(httpPost);System.out.println("--------------請求統一下單獲取預付單會話prepay_id--------------");//獲取結果實體HttpEntity entity = response.getEntity();if (entity != null) {//按指定編碼轉換結果實體為String類型body = EntityUtils.toString(entity, charset);System.out.println(body);}EntityUtils.consume(entity);//釋放鏈接response.close();System.out.println("-------------方法可走出?-------------");//返回JSAPI支付所需的參數String prepay_id = JSONObject.fromObject(body).getString("prepay_id");System.out.println("我覺得可以走出來!");return prepay_id;}

這里面為什么我在上面寫的上二次加簽驗證呢?大家觀察這一次的請求頭的封裝:

String post = getToken(HttpUrl.parse(url_prex + url), jsonStr);

我們看一下getToken()方法的代碼:

/*** 生成組裝請求頭** @param url 請求地址* @param body 請求體* @return 組裝請求的數據* @throws Exception 加密異常*/static String getToken(HttpUrl url, String body) throws Exception {String nonceStr = UUID.randomUUID().toString().replace("-", "");long timestamp = System.currentTimeMillis() / 1000;String message = buildMessage(url, timestamp, nonceStr, body);String signature = sign(message.getBytes(StandardCharsets.UTF_8));return "mchid=\"" + mchId + "\","+ "nonce_str=\"" + nonceStr + "\","+ "timestamp=\"" + timestamp + "\","+ "serial_no=\"" + mchSerialNo + "\","+ "signature=\"" + signature + "\"";}

里面有sign()方法,就是第一次簽名的加載啦:

/*** 生成簽名** @param message 請求體* @return 生成base64位簽名信息* @throws Exception 加密異常*/static String sign(byte[] message) throws Exception {Signature sign = Signature.getInstance("SHA256withRSA");sign.initSign(getPrivateKey());sign.update(message);return Base64.getEncoder().encodeToString(sign.sign());}

(3)輔助工具類的設計與實現:

public class Tools {/*** 微信支付下單** @param jsonStr 請求體 json字符串 此參數與微信官方文檔一致* @return 訂單支付的參數* @throws Exception 客戶端處理異常*/public static String V3PayGet(String jsonStr) throws Exception {String body = "";//創建httpclient對象CloseableHttpClient client = HttpClients.createDefault();//創建post方式請求對象HttpPost httpPost = new HttpPost(url_prex + url);//裝填參數StringEntity s = new StringEntity(jsonStr, charset);s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,"application/json"));//設置參數到請求對象中httpPost.setEntity(s);String post = getToken(HttpUrl.parse(url_prex + url), jsonStr);//設置header信息//指定報文頭【Content-type】、【User-Agent】httpPost.setHeader("Content-type", "application/json");httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");httpPost.setHeader("Accept", "application/json");httpPost.setHeader("Authorization","WECHATPAY2-SHA256-RSA2048 " + post);//執行請求操作,并拿到結果(同步阻塞)System.out.println("-------------執行統一下單請求--------------------------------");CloseableHttpResponse response = client.execute(httpPost);System.out.println("--------------請求統一下單獲取預付單會話prepay_id--------------");//獲取結果實體HttpEntity entity = response.getEntity();if (entity != null) {//按指定編碼轉換結果實體為String類型body = EntityUtils.toString(entity, charset);System.out.println(body);}EntityUtils.consume(entity);//釋放鏈接response.close();System.out.println("-------------方法可走出?-------------");//返回JSAPI支付所需的參數String prepay_id = JSONObject.fromObject(body).getString("prepay_id");System.out.println("我覺得可以走出來!");return prepay_id;}/*** 微信調起支付參數* 返回參數如有不理解 請訪問微信官方文檔* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml** @param prepayId 微信下單返回的prepay_id* @param appId 應用ID(appid)* @return 當前調起支付所需的參數* @throws Exception 加密異常*/public static JSONObject WxTuneUp(String prepayId, String appId) throws Exception {String time = System.currentTimeMillis() / 1000 + "";String nonceStr = UUID.randomUUID().toString().replace("-", "");String packageStr = "prepay_id=" + prepayId;ArrayList<String> list = new ArrayList<>();list.add(appId);list.add(time);list.add(nonceStr);list.add(packageStr);//加載簽名System.out.println("----------小程序調起支付參數封裝-----------");String packageSign = sign(buildSignMessage(list).getBytes());JSONObject jsonObject = new JSONObject();jsonObject.put("appid", appId);jsonObject.put("timeStamp", time);jsonObject.put("nonceStr", nonceStr);jsonObject.put("packages", packageStr);jsonObject.put("signType", "RSA");jsonObject.put("paySign", packageSign);return jsonObject;}/*** 處理微信異步回調** @param request 請求* @param response 響應* @param privateKey 32的秘鑰*/public static String notify(HttpServletRequest request, HttpServletResponse response, String privateKey) throws Exception {Map<String, String> map = new HashMap<>(12);String result = readData(request);// 需要通過證書序列號查找對應的證書,verifyNotify 中有驗證證書的序列號String plainText = verifyNotify(result, privateKey);if (StrUtil.isNotEmpty(plainText)) {response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "SUCCESS");} else {response.setStatus(500);map.put("code", "ERROR");map.put("message", "簽名錯誤");}response.setHeader("Content-type", ContentType.JSON.toString());response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();return JSONObject.fromObject(plainText).getString("out_trade_no");}/*** 生成組裝請求頭** @param url 請求地址* @param body 請求體* @return 組裝請求的數據* @throws Exception 加密異常*/static String getToken(HttpUrl url, String body) throws Exception {String nonceStr = UUID.randomUUID().toString().replace("-", "");long timestamp = System.currentTimeMillis() / 1000;String message = buildMessage(url, timestamp, nonceStr, body);String signature = sign(message.getBytes(StandardCharsets.UTF_8));return "mchid=\"" + mchId + "\","+ "nonce_str=\"" + nonceStr + "\","+ "timestamp=\"" + timestamp + "\","+ "serial_no=\"" + mchSerialNo + "\","+ "signature=\"" + signature + "\"";}/*** 生成簽名** @param message 請求體* @return 生成base64位簽名信息* @throws Exception 加密異常*/static String sign(byte[] message) throws Exception {Signature sign = Signature.getInstance("SHA256withRSA");sign.initSign(getPrivateKey());sign.update(message);return Base64.getEncoder().encodeToString(sign.sign());}/*** 組裝簽名加載** @param url 請求地址* @param timestamp 請求時間* @param nonceStr 請求隨機字符串* @param body 請求體* @return 組裝的字符串*/static String buildMessage(HttpUrl url, long timestamp, String nonceStr, String body) {String canonicalUrl = url.encodedPath();if (url.encodedQuery() != null) {canonicalUrl += "?" + url.encodedQuery();}return "POST" + "\n"+ canonicalUrl + "\n"+ timestamp + "\n"+ nonceStr + "\n"+ body + "\n";}/*** 獲取私鑰。** @return 私鑰對象*/static PrivateKey getPrivateKey() throws IOException {/*getPrivateKey靜態定義的私鑰路徑,可放在數據庫也可直接放在config文件存儲為靜態*/ // String content = Files.readString(Paths.get(getPrivateKey));try {String privateKey = getPrivateKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("當前Java環境不支持RSA", e);} catch (InvalidKeySpecException e) {throw new RuntimeException("無效的密鑰格式");}}/*** 構造簽名串** @param signMessage 待簽名的參數* @return 構造后帶待簽名串*/static String buildSignMessage(ArrayList<String> signMessage) {if (signMessage == null || signMessage.size() <= 0) {return null;}StringBuilder sbf = new StringBuilder();for (String str : signMessage) {sbf.append(str).append("\n");}return sbf.toString();}/*** v3 支付異步通知驗證簽名** @param body 異步通知密文* @param key api 密鑰* @return 異步通知明文* @throws Exception 異常信息*/static String verifyNotify(String body, String key) throws Exception {// 獲取平臺證書序列號cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");String cipherText = resource.getStr("ciphertext");String nonceStr = resource.getStr("nonce");String associatedData = resource.getStr("associated_data");AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));// 密文解密return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonceStr.getBytes(StandardCharsets.UTF_8),cipherText);}/*** 處理返回對象** @param request 請求* @return 返回對象內容*/static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();br = request.getReader();for (String line; (line = br.readLine()) != null; ) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 獲取隨機字符串** @return 隨機字串*/public static String generateNonceStr() {return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);} }

這里對于輔助工具類不太了解的兄弟可以去看微信支付文檔:

開發指引-小程序支付 | 微信支付商戶平臺文檔中心 (qq.com)https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml到這里我們微信支付開發中一個簡單的案例就完成了,這是我實際自己研究的一套微信支付的設計,其中也有很多的優化點,但是實習期間我對自己的要求就是可以跑起來實現甲方的功能需求就可以,至于其他的毛病我們的項目達不到并發很高,所以我也沒有系統的去優化,當然微信支付真的坑了我好久。

想看源碼的可以評論下意圖和郵箱

?

總結

以上是生活随笔為你收集整理的【来C站:一起聊java】用java实现微信支付功能的详细设计思路的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。