JAVA項目實戰 -微信支付開發


微信支付豐富着人們日常生活,下面我們來依據微信提供的API文檔嘗試着學習編寫微信支付工具類,避免重復造輪子。

 1.JSAPI: 

         JSAPI支付是用戶在微信中打開商戶的H5頁面,商戶在H5頁面通過調用微信支付提供的JSAPI接口調起微信支付模塊完成支付。應用場景有:

  1. ◆ 用戶在微信公眾賬號內進入商家公眾號,打開某個主頁面,完成支付
  2. ◆ 用戶的好友在朋友圈、聊天窗口等分享商家頁面連接,用戶點擊鏈接打開商家頁面,完成支付
  3. ◆ 將商戶頁面轉換成二維碼,用戶掃描二維碼后在微信瀏覽器中打開頁面后完成支付

 2.Native支付

      Native支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信“掃一掃”完成支付的模式。該模式適用於PC網站支付、實體店單品或訂單支付、媒體廣告支付等場景。

 3.H5支付

      H5支付主要是在手機、ipad等移動設備中通過瀏覽器來喚起微信支付的支付產品。

 4.小程序支付

     小程序支付是專門被定義使用在小程序中的支付產品。目前在小程序中能且只能使用小程序支付的方式來喚起微信支付。

 5.App支付

     APP支付又稱移動端支付,是商戶通過在移動端應用APP中集成開放SDK調起微信支付模塊完成支付的模式。

注:微信支付功能開發 :1.必須擁有一個微信公眾號;2.具備開通微信支付功能;3登錄微信商戶平台(https://pay.weixin.qq.com/index.php/core/home/login),設置商戶號,支付秘鑰和下載證書(退款接口需要攜帶證書請求)。

===========================================================================

                                       Utils工具類代碼

================================================================

package com.sf.detectprocess.util.pay;

import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.sf.detectprocess.controller.param.*; import com.sf.detectprocess.core.constant.SystemConstants; import com.sf.detectprocess.core.constant.WXPayConstants; import com.sf.detectprocess.core.exception.WeChatPayException; import com.sf.detectprocess.entity.Order; import com.sf.detectprocess.entity.OrderPay; import com.sf.detectprocess.mapper.OrderMapper; import com.sf.detectprocess.mapper.OrderPayMapper; import com.sf.detectprocess.service.LimsService; import com.sf.detectprocess.util.FuntionUtils; import com.sf.detectprocess.util.R; import com.sun.org.apache.regexp.internal.RE; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; /** * @description: 微信支付接口工具類 * @author: zhucj * @date: 2019-09-25 10:37 */ @Slf4j @Component public class WeChatPayUtils { /** * 公眾號或小程序或app端 appid */ @Value("${wx.pay.appId}") private String appId; /** * 公眾號或小程序 key */ @Value("${wx.pay.key}") private String key; /** * 商戶號 (微信支付商戶號) */ @Value("${wx.pay.mchNo}") private String mchNo; /** * 通知地址 */ @Value("${wx.pay.notifyUrl}") private String notifyUrl; /** * 安全證書密碼(默認為商戶號) */ @Value("${wx.pay.lcePassWord}") private String lcePassWord; @ApiOperation(value = "請求微信統一支付接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "outTradeNo", required = true, value = "商戶訂單號(32個字符內)", dataType= "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "標價金額", dataType = "String"), @ApiImplicitParam(name = "body", required = true, value = "商品描述", dataType = "String"), @ApiImplicitParam(name = "tradeType", required = true, value = "交易類型 JSAPI:JSAPI支付(或小程序支付)," + "NATIVE:Native支付, APP:app支付, MWEB:H5支付", dataType = "String"), @ApiImplicitParam(name = "openId", required = false, value = "用戶openId(JSAPI/小程序/必傳)",dataType = "String"), @ApiImplicitParam(name = "spbillCreateIp", required = false, value = "APP和網頁支付提交用戶端ip,Native支付不需要傳)", dataType = "String"), @ApiImplicitParam(name = "productId", required = false, value = "二維碼中包含商品Id(Native支付必傳,其他支付不傳)", dataType = "String") }) public R unifiedOrder(UnfiedOrderParam map) throws WeChatPayException { /* 簽名參數的map對象 */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); // 請求預下單對象 UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest(); //=>1.appId 商戶Id  unifiedOrderRequest.setAppid(appId); //TODO:判斷當前微信支付類型 APP端/JSAPI/H5/Native/MWEB if (Objects.equals(WXPayConstants.APP,map.getTradeType())){ //TODO:App端支付 必傳用戶終端IP 和單獨的appId(應用Id) if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE, WXPayConstants.SPBILL_CREATE_APP_ERROR); } // => 2.終端Ip app支付由前端傳  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); } else if (Objects.equals(WXPayConstants.JSAPI,map.getTradeType())) { //TODO:JSAPI支付 必傳openId和用戶終端IP if (isNull(map.getOpenId())) { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.OPENID_JSAPI_ERROR); } if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_JSAPI_ERROR); } unifiedOrderRequest.setOpenid(map.getOpenId()); // => 2.終端Ip 小程序或公眾號支付由前端傳  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); //僅 JSAPI支付才傳openId packageParams.put("openid", unifiedOrderRequest.getOpenid()); }else if (Objects.equals(WXPayConstants.NATIVE,map.getTradeType())){ //TODO:Native掃碼支付 必傳prductId(商品Id) if (isNull(map.getProductId())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.PRODUCT_NATIVE_ERROR); } unifiedOrderRequest.setProduct_id(map.getProductId()); packageParams.put("product_id", unifiedOrderRequest.getProduct_id()); //TODO:Native 本地獲取終端Ip String localIp = WXPayUtil.getLocalIp(); //=> 2.終端Ip  unifiedOrderRequest.setSpbill_create_ip(localIp); }else if (Objects.equals(WXPayConstants.MWEB,map.getTradeType())){ //TODO:H5 前端必傳Ip if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_HWEB_ERROR); } // => 2.終端Ip H5支付由前端傳  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); }else { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"交易類型tradeType="+map.getTradeType()+"暫時不支持,請核對參數是否正確!"); } // => 3.商品描述  unifiedOrderRequest.setBody(map.getBody()); // => 4.商戶號  unifiedOrderRequest.setMchId(mchNo); // 生成隨機字符串 => 5.隨機字符串  unifiedOrderRequest.setNonceStr(WXPayUtil.generateNonceStr()); // => 6.異步通知地址  unifiedOrderRequest.setNotify_url(notifyUrl); // => 7.商戶訂單號  unifiedOrderRequest.setOut_trade_no(map.getOutTradeNo()); // => 8.支付金額 需要擴大100倍(1代表支付時是0.01)  unifiedOrderRequest.setTotal_fee(WXPayUtil.changeY2F(map.getTotalFee())); // => 9.交易類型  unifiedOrderRequest.setTrade_type(map.getTradeType()); //TODO: 封裝簽名map packageParams.put("appid", unifiedOrderRequest.getAppid()); packageParams.put("body", unifiedOrderRequest.getBody()); packageParams.put("mch_id", unifiedOrderRequest.getMchId()); packageParams.put("nonce_str", unifiedOrderRequest.getNonceStr()); packageParams.put("notify_url", unifiedOrderRequest.getNotify_url()); packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no()); packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip()); packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee()); packageParams.put("trade_type", unifiedOrderRequest.getTrade_type()); try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); // =>10.簽名  unifiedOrderRequest.setSign(signature); packageParams.put("sign",signature); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //調統一下單接口 String s = WXPayUtil.doPost(1, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s, UnifiedOrderResponse.class); //預下單后 返回前端封裝map TreeMap respMap = new TreeMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ //TODO: 依據交易類型返回前端參數 switch (resposeStr.getTrade_type()) { case WXPayConstants.NATIVE: { //Native掃碼直接返回二維碼鏈接 respMap.put("codeUrl", resposeStr.getCode_url()); break; } case WXPayConstants.APP : { //App端 需要商戶號進行簽名 返回 respMap.put("appid", resposeStr.getAppid()); respMap.put("partnerid", resposeStr.getMch_id()); respMap.put("prepayid", resposeStr.getPrepay_id()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonce_str", WXPayUtil.generateNonceStr()); respMap.put("package","Sign=WXPay"); respMap.put("sign", WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); break; } case WXPayConstants.JSAPI: { respMap.put("appId", resposeStr.getAppid()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonceStr",resposeStr.getNonce_str()); respMap.put("package","prepay_id="+resposeStr.getPrepay_id()); respMap.put("signType",WXPayConstants.MD5); respMap.put("paySign",WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); respMap.put("prepayid",resposeStr.getPrepay_id()); break; } case WXPayConstants.MWEB : { // h5支付鏈接地址 respMap.put("payUrl", resposeStr.getMweb_url()); break; } default: break; } return R.ok(respMap); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("預訂單異常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"預訂單異常"); } } @ApiOperation(value = "請求微信訂單查詢接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商戶訂單號(32個字符內)", dataType= "String") public R orderQuery(String outTradeNo) throws WeChatPayException { /* 簽名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //獲得簽名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //調微信查詢訂單服務接口 String s = WXPayUtil.doPost(2, WXPayUtil.mapToXml(packageParams), false, null); OrderQueryResponse resposeStr = JSON.parseObject(s, OrderQueryResponse.class); //查詢返回結果封裝map Map respMap = new HashMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ respMap.put("statusDesc",resposeStr.getTrade_state_desc()); respMap.put("data",resposeStr); /** * 自己設置的status => 1:已支付 ,2:轉入退款 3:未支付 4:已關閉 5:已撤銷 6:支付失敗 * */ if (Objects.equals(WXPayConstants.TradeState.SUCCESS.getName(),resposeStr.getTrade_state())){ respMap.put("status",1); }else if (Objects.equals(WXPayConstants.TradeState.REFUND.getName(),resposeStr.getTrade_state())){ respMap.put("status",2); }else if (Objects.equals(WXPayConstants.TradeState.NOTPAY.getName(),resposeStr.getTrade_state())){ respMap.put("status",3); }else if (Objects.equals(WXPayConstants.TradeState.CLOSED.getName(),resposeStr.getTrade_state())){ respMap.put("status",4); }else if (Objects.equals(WXPayConstants.TradeState.REVOKED.getName(),resposeStr.getTrade_state())){ respMap.put("status",5); }else if (Objects.equals(WXPayConstants.TradeState.PAYERROR.getName(),resposeStr.getTrade_state())){ respMap.put("status",6); } return R.ok(respMap,"查詢成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("查詢訂單異常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查詢訂單異常"); } } @ApiOperation(value = "請求微信關閉訂單接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商戶訂單號(32個字符內)", dataType = "String") public R closeOrder(String outTradeNo) throws WeChatPayException { /* 簽名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //獲得簽名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //請求微信關閉訂單服務接口 String s = WXPayUtil.doPost(4, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s,UnifiedOrderResponse.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ return R.ok(null,"訂單關閉成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("關閉訂單異常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"關閉訂單異常"); } } @ApiOperation(value = "請求微信退款訂單接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "transactionId", required = true, value = "微信訂單號", dataType = "String"), @ApiImplicitParam(name = "outRefundNo", required = true, value = "商戶退款單號(32個字符內 )", dataType = "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "訂單金額", dataType = "String"), @ApiImplicitParam(name = "refundFee", required = true, value = "退款金額", dataType = "String"), @ApiImplicitParam(name = "refundDesc", required = false, value = "退款原因", dataType = "String") }) public R refund(RefundParam map) throws WeChatPayException { /* 簽名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); packageParams.put("out_refund_no",map.getOutRefundNo()); packageParams.put("total_fee",WXPayUtil.changeY2F(map.getTotalFee())); packageParams.put("refund_fee",WXPayUtil.changeY2F(map.getRefundFee())); packageParams.put("transaction_id",map.getTransactionId()); //獲得簽名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //調統一請求退款服務接口 String s = WXPayUtil.doPost(3,WXPayUtil.mapToXml(packageParams), true,lcePassWord); RefundResp resposeStr = JSON.parseObject(s, RefundResp.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ /** * ##訂單已退款 此處更新數據庫訂單狀態和退款信息 ### */ return R.ok(null,"訂單退款成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("訂單退款異常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"訂單退款異常"); } } @ApiOperation(value = "請求微信退款查詢接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商戶訂單號", dataType= "String") public R refundQuery(String outTradeNo) throws WeChatPayException { /* 簽名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //獲得簽名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //統一請求退款服務接口 String s = WXPayUtil.doPost(5, WXPayUtil.mapToXml(packageParams), false, null); RefundQueryResponse resposeStr = JSON.parseObject(s,RefundQueryResponse.class); //返回結果封裝map Map respMap = new HashMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ /** * ##訂單查詢成功 此處更新數據庫退款訂單狀態 * 退款狀態: * SUCCESS—退款成功 status = 1 * REFUNDCLOSE—退款關閉 status = 2 * PROCESSING—退款處理中 status = 3 * CHANGE—退款異常 status = 4 */ respMap.put("statusDesc",resposeStr.getRefund_status_0()); respMap.put("data",resposeStr); if (Objects.equals(WXPayConstants.RefundState.SUCCESS.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",1); }else if (Objects.equals(WXPayConstants.RefundState.REFUNDCLOS.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",2); }else if (Objects.equals(WXPayConstants.RefundState.PROCESSING.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",3); }else if (Objects.equals(WXPayConstants.RefundState.CHANGE.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",4); } return R.ok(respMap,"訂單退款查詢成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("查詢退款訂單異常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查詢退款訂單異常"); } } /** * #### * 1、商戶系統對於支付結果通知的內容一定要做簽名驗證,並校驗返回的訂單金額是否與商戶的訂單金額一致 * ,防止數據泄漏導致出現“假通知”,造成資金損失。 * 2、當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,如果沒有處理過再 * 進行處理,如果處理過直接返回結果成功。在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行並發控制 * ,以避免函數重入造成的數據混亂。 */ @ApiOperation(value = "微信支付異步通知") public void notify(HttpServletRequest req, HttpServletResponse resp){ //定義一個通知微信消息 String noticeStr = null; String notifyString = WXPayUtil.getNotifyUtils(req, resp); //解析xml成map Map<String, String> map = new HashMap<>(); try { map = WXPayUtil.xmlToMap(notifyString); } catch (Exception e) { e.printStackTrace(); } //獲取異步通知請求 將map轉成指定對象 OrderQueryResponse orderResp = JSON.parseObject(JSON.toJSONString(map), OrderQueryResponse.class); //判斷return_code狀態 if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getResult_code())){ //TODO 驗簽 try { if (WXPayUtil.isSignatureValid(notifyString,key)){ //TODO 新增驗證預支付訂單金額和異步回調支付金額是否匹配 /** * ## 此處步驟 * 1.異步回調的商戶訂單號或微信訂單號 調用查詢接口 * 2.判斷此訂單是否真實支付成功 * 3.驗證返回的支付金額和數據庫中預支付訂單金額是否相等 * 4.鑒定完畢才更新數據庫數據 * 5.跳轉至成功頁面 */ log.info("------------驗簽成功 開始下一步----------"); //調用微信支付查詢訂單接口 R r = orderQuery(orderResp.getOut_trade_no()); if (r.getSuccess()){ Map respMap =(Map) r.getData(); OrderQueryResponse data =(OrderQueryResponse) respMap.get("data"); if (Objects.equals(WXPayConstants.SUCCESS,data.getTrade_state())){ //## 此處調用此商戶訂單號查詢數據庫預訂單的付款金額(注意單位:分)  noticeStr = setXML(WXPayConstants.SUCCESS, WXPayConstants.OK); }else { log.info("商戶訂單號:{},未支付成功狀態",orderResp.getOut_trade_no()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getOut_trade_no()+"商戶訂單號狀態是未支付成功狀態"); } }else { log.error("訂單查詢失敗:{}",r.getMsg()); } }else { log.error("驗簽錯誤 非法訪問"); noticeStr = setXML(WXPayConstants.FAIL, "非法訪問"); } } catch (Exception e) { e.printStackTrace(); log.error("驗簽異常:{}",e.getMessage()); noticeStr = setXML(WXPayConstants.FAIL, "驗簽異常"); } }else { log.error("錯誤信息:{}",orderResp.getErr_code_des()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg()); } }else { log.error("異常信息:{}",orderResp.getReturn_msg()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg()); } PrintWriter writer = null; try { writer = resp.getWriter(); } catch (IOException e) { e.printStackTrace(); } writer.write(noticeStr); writer.flush(); } /** * 判斷對象是否非空 * @param object * @return */ public Boolean isNull(Object object){ if (Objects.isNull(object)||Objects.equals("",object)){ return true; } return false; } /** * 拼接Xml字符串 * @param return_code * @param return_msg * @return */ public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } public String notifyReqUrl(Integer code) throws WeChatPayException { String reqUrl = null; switch (code){ case 1: reqUrl = refusalRefundUrl; break; case 2: reqUrl = notPickRefundUrl; break; case 3: reqUrl = detectionlRefundUrl; break; default: throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"參數未被定義"); } return reqUrl; } }
==============================================================================================
微信支付工具包
===============================================================================================

package com.sf.vsolution.hb.sfce.util.wechat.pay;

import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.KeyStore; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.*; /** * @Desc: 微信支付工具包 * @Author: zhucj * @Date: 2019/5/23 13:29 */ @Slf4j @Component public class WXPayUtil implements InitializingBean { /** * 安全證書位置 */ @Value("${wx.pay.certPath}") private String certPath; public static String CERT_PATH; private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final Random RANDOM = new SecureRandom(); /** * ##連接超時時間,默認10秒 */ private static int socketTimeout = 10000; /** * ## 傳輸超時時間,默認30秒 */ private static int connectTimeout = 30000; /** * ## 請求器的配置 */ private static RequestConfig requestConfig; /** * ## HTTP請求器 */ private static CloseableHttpClient httpClient; /** * 作用:統一請求地址<br> * 場景:公共號支付、掃碼支付、APP支付 * @param reqUrlNub 請求地址編號 1:下單 2:查詢訂單 3:申請退款 4:關閉訂單 5:查詢退款 * @param xmlDate 發送內容 xml字符串 * @param isLoadCert 是否需要證書 * @param lcePassword 證書密碼(默認商戶Id) isLoadCert=true必傳 如果isLoadCert=false 傳null即可 * @throws Exception * @return JSON格式字符串對象 */ public static String doPost(Integer reqUrlNub, String xmlDate, boolean isLoadCert, String lcePassword) throws Exception{ if (Objects.isNull(reqUrlNub) || Objects.equals("",reqUrlNub)){return null;} //獲取請求地址 String url = getReqUrl(reqUrlNub); log.info("POST請求參數,請求URL:{},請求XML信息:{}",url,xmlDate); HttpPost httpPost=new HttpPost(url); // 得指明使用UTF-8編碼,否則到API服務器XML的中文不能被成功識別 httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(new StringEntity(xmlDate,"UTF-8")); // 根據默認超時限制初始化requestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); // 設置請求器的配置  httpPost.setConfig(requestConfig); CloseableHttpResponse execute = null; if(isLoadCert) { // 加載證書 try { initCert(lcePassword,2); } catch (Exception e) { e.printStackTrace(); } //發送含有證書的http請求 execute = httpClient.execute(httpPost); }else { execute = HttpClients.custom().build().execute(httpPost); } return getJsonString(execute); } /** * 返回結果解析成Json字符串 * @param response * @return */ public static String getJsonString(HttpResponse response) throws Exception{ HttpEntity entity = null; StringBuilder sb = new StringBuilder(); try { entity = response.getEntity(); String text; if (entity != null) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); while (true) { if (!((text = bufferedReader.readLine()) != null)) break;sb.append(text); } }else { return null; } }catch (Exception e){ e.printStackTrace(); log.error("--------讀取異常---------"+e.getMessage()); } finally { try { EntityUtils.consume(entity); } catch (IOException ex) { ex.printStackTrace(); log.error("net io exception"); } } //將xml轉成map對象 Map<String, String> stringMap = xmlToMap(sb.toString()); log.info("響應參數內容:{}", JSON.toJSONString(stringMap)); return JSON.toJSONString(stringMap); } /** * 加載證書 (apiclient_cert.p12)文件 * @param lcePsw 證書密碼(默認是商戶Id) * @param loadType 加載證書方法 1:存放在項目下 2:存放本地磁盤中 * @throws Exception */ private static void initCert(String lcePsw,Integer loadType) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); if (Objects.isNull(loadType) || Objects.equals("",loadType) || Objects.equals(1,loadType)){ //方式一:默認存放在項目下 ClassPathResource cl = new ClassPathResource(CERT_PATH); try { keyStore.load(cl.getInputStream(), lcePsw.toCharArray()); } finally { cl.getInputStream().close(); } }else if (Objects.equals(2,loadType)){ //方式二:存放在本地磁盤 讀取本機存放的PKCS12證書文件 FileInputStream instream = new FileInputStream(new File(CERT_PATH)); try { keyStore.load(instream, lcePsw.toCharArray()); } finally { instream.close(); } }else {throw new WeChatPayException(400,"loadType參數格式錯誤,請核對");} // 信任自己的CA和所有自簽名證書 SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore,lcePsw.toCharArray()) .build(); // 只允許TLSv1協議 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); } /** * 作用:獲取請求地址<br> * 場景:1:下單 2:查詢訂單 3:申請退款 4:關閉訂單 5:查詢退款 * @param reqUrlNub * @return 請求地址字符串 * @throws Exception */ public static String getReqUrl(Integer reqUrlNub) throws WeChatPayException { StringBuilder reqUrl = new StringBuilder(); reqUrl.append(WXPayConstants.BASE_URL); switch (reqUrlNub){ case 1: reqUrl.append( WXPayConstants.UNIFIED_ORDER_URL); break; case 2: reqUrl.append(WXPayConstants.ORDER_QUERY_URL); break; case 3: reqUrl.append(WXPayConstants.REFUND_URL); break; case 4: reqUrl.append(WXPayConstants.CLOSEORDER_URL_SUFFIX); break; case 5: reqUrl.append(WXPayConstants.REFUNDQUERY_URL_SUFFIX); break; default: throw new WeChatPayException(400,"傳入的請求地址編號暫不支持"); } return reqUrl.toString(); } /** * 獲取隨機字符串 Nonce Str * * @return String 隨機字符串 */ public static String generateNonceStr() { char[] nonceChars = new char[32]; for (int index = 0; index < nonceChars.length; ++index) { nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); } return new String(nonceChars); } /** * 獲取本機Ip地址 * @return */ public static String getLocalIp(){ try { InetAddress addr = InetAddress.getLocalHost(); //獲取本機ip return addr.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); return null; } } /** * 判斷簽名是否正確 * * @param xmlStr XML格式數據 * @param key API密鑰 * @return 簽名是否正確 * @throws Exception */ public static boolean isSignatureValid(String xmlStr, String key) throws Exception { Map<String, String> data = xmlToMap(xmlStr); if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key,WXPayConstants.SignType.MD5).equals(sign); } /** * 生成簽名. 注意,若含有sign_type字段,必須和signType參數保持一致。 * * @param data 待簽名數據 * @param key API密鑰 * @param signType 簽名方式 * @return 簽名 */ public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (k.equals(WXPayConstants.FIELD_SIGN)) { continue; } if (data.get(k).trim().length() > 0) // 參數值為空,則不參與簽名 {sb.append(k).append("=").append(data.get(k).trim()).append("&");} } sb.append("key=").append(key); if (WXPayConstants.SignType.MD5.equals(signType)) { return MD5(sb.toString()).toUpperCase(); } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) { return HMACSHA256(sb.toString(), key); } else { throw new Exception(String.format("Invalid sign_type: %s", signType)); } } /** * 生成 MD5 * * @param data 待處理數據 * @return MD5結果 */ public static String MD5(String data) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.digest(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 生成 HMACSHA256 * @param data 待處理數據 * @param key 密鑰 * @return 加密結果 * @throws Exception */ public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 獲取當前時間戳,單位秒 * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis()/1000; } /** * 支付金額單位:元轉分 (12.23元 >>> 1223分) * @param amount * @return */ public static String changeY2F(String amount) { String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 或者$的金額 int index = currency.indexOf("."); int length = currency.length(); Long amLong = 0l; if (index == -1) { amLong = Long.valueOf(currency + "00"); } else if (length - index >= 3) { amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", "")); } else if (length - index == 2) { amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0); } else { amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00"); } return amLong.toString(); } /** * XML格式字符串轉換為Map * * @param strXML XML字符串 * @return XML數據轉換后的Map * @throws Exception */ public static Map<String, String> xmlToMap(String strXML) throws Exception { try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilder documentBuilder = newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } try { stream.close(); } catch (Exception ex) { // do nothing  } return data; } catch (Exception ex) { log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); throw ex; } } /** * 將Map轉換為XML格式的字符串 * * @param data Map類型數據 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { Document document = newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key: data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); try { writer.close(); } catch (Exception ex) { } return output; } public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setXIncludeAware(false); documentBuilderFactory.setExpandEntityReferences(false); return documentBuilderFactory.newDocumentBuilder(); } public static Document newDocument() throws ParserConfigurationException { return newDocumentBuilder().newDocument(); } /** * 將微信異步通知消息解析成map對象 * @param req * @param response * @return */ public static String getNotifyUtils(HttpServletRequest req, HttpServletResponse response){ //返回字符串內容 String noticeStr = null; InputStream inputStream ; StringBuffer sb = new StringBuffer(); try{ inputStream = req.getInputStream(); String s ; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null){ sb.append(s); } in.close(); inputStream.close(); log.info("異步通知結果=>xml值:"+sb.toString()); //解析xml成map return sb.toString(); }catch (Exception e){ log.error("解析異步消息異常",e.getMessage()); return null; } } /** * 注入的屬性轉靜態屬性 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { CERT_PATH = this.certPath; } }
=======================================================================================================
微信支付相關參數(請求參數,響應參數,枚舉,異常類)
=======================================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 微信下單前端傳遞參數 * @author: zhucj * @date: 2019-09-26 11:08 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class UnfiedOrderParam { /** * 商戶訂單號(32個字符內) */ private String outTradeNo; /** * 標價金額 */ private String totalFee; /** * 商品描述 */ private String body; /** * 交易類型 */ private String tradeType; /** * 用戶openId */ private String openId; /** * 終端IP */ private String spbillCreateIp; /** * 產品Id */ private String productId; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 統一下單實體 * @Author:zhucj * @Date: 2019/5/23 13:29 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class UnifiedOrderRequest { /** * 公眾賬號ID * 必填 String(32) * wxd678efh567hg6787微信支付分配的公眾賬號ID(企業號corpid即為此appId) */ private String appid; /** * 商戶號 * 必填 String(32) * 1230000109 微信支付分配的商戶號 */ private String mchId; /** * 設備號 * 否 * String(32) * 013467007045764 自定義參數,可以為終端設備號(門店號或收銀設備ID),PC網頁或公眾號內支付可以傳"WEB" */ private String deviceInfo; /** * 隨機字符串 * 必填 String(32) * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 隨機字符串,長度要求在32位以內。推薦隨機數生成算法 */ private String nonceStr; /** * 簽名 * 必填 String(32) * C380BEC2BFD727A4B6845133519F3AD6 通過簽名算法計算得出的簽名值,詳見簽名生成算法MD5加密 */ private String sign; /** * 簽名類型 * 否 String(32) * HMAC-SHA256 簽名類型,默認為MD5,支持HMAC-SHA256和MD5。 */ private String sign_type; /** * 商品描述 * 必填 String(128) * 騰訊充值中心-QQ會員充值 商品簡單描述,該字段請按照規范傳遞,具體請見參數規定 */ private String body; /** * 商品詳情 * 否 String(6000) * 單品優惠字段(暫未上線) */ private String detail; /** * 商品詳情 * 否 String(6000) * 單品優惠字段(暫未上線) */ private String attach; /** * 商戶訂單號 * 必傳 String(32) * 20150806125346 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|*@ ,且在同一個商戶號下唯一。詳見商戶訂單號 */ private String out_trade_no; /** * 標價幣種 * 否 String(16) * CNY 符合ISO 4217標准的三位字母代碼,默認人民幣:CNY,詳細列表請參見貨幣類型 */ private String fee_type; /** * 標價金額 * 必傳 Int 88 訂單總金額,單位為分,詳見支付金額 */ private String total_fee; /** * 終端IP * 必傳 String(16) * 123.12.12.123 APP和網頁支付提交用戶端ip,Native支付填調用微信支付API的機器IP。 */ private String spbill_create_ip; /** * 交易起始時間 * 否 String(14) * 20091225091010 訂單生成時間,格式為yyyyMMddHHmmss,如2009年12月25日9點10分10秒表示為20091225091010。其他詳見時間規則 */ private String time_start; /** * 交易結束時間 * 否 String(14) * 20091227091010 訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒表示為20091227091010。其他詳見時間規則 注意:最短失效時間間隔必須大於5分鍾 */ private String time_expire; /** * 訂單優惠標記 * 否 String(32) * WXG 訂單優惠標記,使用代金券或立減優惠功能時需要的參數,說明詳見代金券或立減優惠 */ private String goods_tag; /** * 通知地址 * 必傳 String(32) * 異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數。 */ private String notify_url; /** * 交易類型 * 必傳 String(16) * JSAPI 取值如下:JSAPI,NATIVE,APP等,說明詳見參數規定 */ private String trade_type; /** * 商品ID * 否 String(32) * 12235413214070356458058 trade_type=NATIVE時(即掃碼支付),此參數必傳。此參數為二維碼中包含的商品ID,商戶自行定義。 */ private String product_id; /** * 指定支付方式 * 否 String(32)上傳此參數no_credit--可限制用戶不能使用信用卡支付 */ private String limit_pay; /** * 用戶標識 *否 String(128) * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI時(即公眾號支付),此參數必傳,此參數為微信用戶在商戶對應appid下的唯一標識。openid如何獲取, * 可參考【獲取openid】。企業號請使用【企業號OAuth2.0接口】獲取企業號內成員userid,再調用【企業號userid轉openid接口】進行轉換 */ private String openid; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @description 統一下單返回參數 * @author zhucj * @Date: 2019/5/23 13:29 */ @Data @NoArgsConstructor @AllArgsConstructor @ToString public class UnifiedOrderResponse { /** * 返回狀態碼 * SUCCESS/FAIL * 此字段是通信標識,非交易標識,交易是否成功需要查看result_code來判斷 */ private String return_code; /** *返回信息 * 返回信息,如非空,為錯誤原因 簽名失敗 * 參數格式校驗錯誤 */ private String return_msg; //TODO 以下字段在return_code為SUCCESS的時候有返回 /** * 公眾賬號ID */ private String appid; /** * 商戶號 */ private String mch_id; /** * 設備號 */ private String device_info; /** * 隨機字符串 */ private String nonce_str; /** * 簽名 */ private String sign; /** * 業務結果 */ private String result_code; /** * 錯誤代碼 */ private String err_code; /** * 錯誤代碼描述 */ private String err_code_des; //TODO 以下字段在return_code 和result_code都為SUCCESS的時候有返回 /** * 交易類型 */ private String trade_type; /** * 預支付交易會話標識 */ private String prepay_id; /** * 二維碼鏈接 */ private String code_url; /** *H5支付跳轉頁面 */ private String mweb_url; /** * 商戶支付訂單號 */ private String out_trade_no; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 微信退款參數 * @author:zhucj * @date: 2019-09-26 11:21 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class RefundParam { /** * 微信訂單號 */ private String transactionId; /** * 退款訂單號 */ private String outRefundNo; /** * 訂單總金額 */ private String totalFee; /** * 退款金額 */ private String refundFee; /** * 退款信息描述 */ private String refundDesc; /** * 退款異步地址編號 1:為拒簽退款異步地址 2:不接樣退款地址 3:檢測完成退款地址 */ private Integer refundStatus; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 退款回調信息 * @author: zhucj * @date: 2019-10-24 17:53 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class RefundInfoParam { private String return_code; private String return_msg; private String appid; private String mch_id; private String nonce_str; private String req_info; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 退款查詢返回視圖 * @author: zhucj * @date: 2019-07-24 18:30 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class RefundQueryResponse extends UnifiedOrderResponse { //TODO 處理退款結果查詢參數,如果通過微信單號或商戶支付訂單號查詢, // 可能出現多次退款情況。 /** * 微信退款單號..... */ private String refund_id_0; private String refund_id_1; private String refund_id_2; private String refund_id_3; private String refund_id_4; /** * 微信單號 */ private String transaction_id; /** * 商戶退款單號 ...... */ private String out_refund_no_0; private String out_refund_no_1; private String out_refund_no_2; private String out_refund_no_3; private String out_refund_no_4; /** * 商戶訂單號 */ private String out_trade_no; /** * 現金支付金額 */ private Integer cash_fee; /** * 訂單總金額 */ private Integer total_fee; /** * 退款筆數 */ private String refund_count; /** * 退款金額...... */ private String refund_fee_0; private String refund_fee_1; private String refund_fee_2; private String refund_fee_3; private String refund_fee_4; /** * 退款狀態 ...... */ private String refund_status_0; private String refund_status_1; private String refund_status_2; private String refund_status_3; private String refund_status_4; /** * 退入賬號..... */ private String refund_recv_accout_0; private String refund_recv_accout_1; private String refund_recv_accout_2; private String refund_recv_accout_3; private String refund_recv_accout_4; /** * 退款成功時間 ..... */ private String refund_success_time_0; private String refund_success_time_1; private String refund_success_time_2; private String refund_success_time_3; private String refund_success_time_4; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*; /** * @description: 退款返回參數 * @author: zhucj * @date: 2019-10-23 17:54 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class RefundResp extends UnifiedOrderResponse { /** * 微信訂單號 */ private String transaction_id; /** * 商戶訂單號 */ private String out_trade_no; /** * 商戶退款單號 */ private String out_refund_no; /** * 微信退款單號 */ private String refund_id; /** * 退款金額 */ private String refund_fee; /** * 訂單總金額 */ private String total_fee; /** * 現金支付金額 */ private String cash_fee; private String refund_status; private String success_time; }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @author :zhucj
 * @date :Created in 2019/6/17 17:46
 * @description: 自定義微信支付異常類
 * @modified By:
 */
public class WeChatPayException extends Exception { private static final long serialVersionUID = -5317007026578376164L; /** * 錯誤碼 */ private Integer errorCode; /** * 錯誤描述 */ private String errorMsg; /** * @param errorCode * @param errorMsg */ public WeChatPayException(Integer errorCode, String errorMsg) { super(errorMsg); this.errorCode = errorCode; this.errorMsg = errorMsg; } public Integer getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @Author: zhucj
 * @Date: 2019/5/23 13:29
 * @apiNote: 微信支付常量
 */
public class WXPayConstants { /** * 簽名枚舉 */ public static enum SignType { MD5, HMACSHA256; private SignType() { } } /** * 公眾號、小程序支付 */ public static final String JSAPI = "JSAPI" ; /** * NATIVE 掃碼支付 */ public static final String NATIVE = "NATIVE"; /** * app支付 */ public static final String APP = "APP"; /** * 小程序支付 */ public static final String MWEB = "MWEB"; public enum TradeState{ /** * 交易狀態 */ SUCCESS("SUCCESS","支付成功",1), REFUND("REFUND","轉入退款",2), NOTPAY("NOTPAY","未支付",3), CLOSED("CLOSED","已關閉",4), REVOKED("REVOKED","已撤銷",5), PAYERROR("PAYERROR","支付失敗",6); private String name; private String values; private Integer index; TradeState(String name, String values, int index) { this.name = name; this.values = values; this.index = index; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValues() { return values; } public void setValues(String values) { this.values = values; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } } /** * 退款狀態 */ public enum RefundState{ SUCCESS("SUCCESS","退款成功",1), REFUNDCLOS("REFUNDCLOS","退款關閉",2), PROCESSING("PROCESSING","退款處理中",3), CHANGE("CHANGE","退款異常",4); private String name; private String values; private Integer index; RefundState(String name, String values, int index) { this.name = name; this.values = values; this.index = index; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValues() { return values; } public void setValues(String values) { this.values = values; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } } //TODO: 返回狀態碼 public static final String FAIL = "FAIL"; public static final String SUCCESS = "SUCCESS"; //TODO: 驗簽加密方法 public static final String HMACSHA256 = "HMAC-SHA256"; public static final String MD5 = "MD5"; //TODO: 簽名 public static final String FIELD_SIGN = "sign"; public static final String FIELD_SIGN_TYPE = "sign_type"; public static final String OK = "OK"; //TODO:微信支付服務名 /* 系統地址 */ public static final String BASE_URL = "https://api.mch.weixin.qq.com"; /* 統一下單地址 */ public static final String UNIFIED_ORDER_URL = "/pay/unifiedorder"; /* 查詢訂單 */ public static final String ORDER_QUERY_URL = "/pay/orderquery"; /* 關閉訂單 */ public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder"; /* 申請退款 */ public static final String REFUND_URL = "/secapi/pay/refund"; /* 查詢退款 */ public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery"; //TODO:請求狀態碼 public static final Integer PARAM_ERROR_CODE = 400; public static final Integer EXCEPTION_ERROR_CODE = 500; //TODO: 異常信息狀態 public static final String SPBILL_CREATE_APP_ERROR = "選擇APP支付時,用戶終端IP不為空"; public static final String SPBILL_CREATE_JSAPI_ERROR = "選擇JSAPI支付時,用戶終端IP不為空"; public static final String SPBILL_CREATE_HWEB_ERROR = "選擇HWEB支付時,用戶終端IP不為空"; public static final String OPENID_JSAPI_ERROR = "選擇JSAPI支付時,用戶openId不為空"; public static final String PRODUCT_NATIVE_ERROR = "選擇NATIVE支付時,商品Id不為空"; public static final String SIGN_EXCEPTION_ERROR = "簽名異常"; }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.ToString; import java.io.Serializable; /** * 返回前端格式類型 * @author choleece * @date 2018/9/27 */ @ApiModel @ToString public class R<T> implements Serializable { private static final long serialVersionUID = -6287952131441663819L; /** * 編碼 */ @ApiModelProperty(value = "響應碼", example = "200") private int code = 200; /** * 成功標志 */ @ApiModelProperty(value = "成功標志", example = "true") private Boolean success; /** * 返回消息 */ @ApiModelProperty(value = "返回消息說明", example = "操作成功") private String msg="操作成功"; /** * 返回數據 */ @ApiModelProperty(value = "返回數據") private T data; /** * 創建實例 * @return */ public static R instance() { return new R(); } public int getCode() { return code; } public R setCode(int code) { this.code = code; return this; } public Boolean getSuccess() { return success; } public R setSuccess(Boolean success) { this.success = success; return this; } public String getMsg() { return msg; } public R setMsg(String msg) { this.msg = msg; return this; } public T getData() { return data; } public R setData(T data) { this.data = data; return this; } public static R ok() { return R.instance().setSuccess(true); } public static R ok(Object data) { return ok().setData(data); } public static R ok(Object data, String msg) { return ok(data).setMsg(msg); } public static R error() { return R.instance().setSuccess(false); } public static R error(String msg) { return error().setMsg(msg); } /** * 無參 */ public R() { } public R(int code, String msg) { this.code = code; this.msg = msg; } public R(int code, T data){ this.code = code; this.data = data; } /** * 有全參 * @param code * @param msg * @param data * @param success */ public R(int code, String msg, T data, Boolean success) { this.code = code; this.msg = msg; this.data = data; this.success = success; } /** * 有參 * @param code * @param msg * @param data */ public R(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } private int code; private String msg; ResultEnum(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } }

====================================================================================
支付查詢定時任務
===================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.task;

import com.sf.vsolution.hb.sfce.util.wechat.pay.WeChatPayUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; /** * @description: 定時任務-訂單支付狀態查詢 * @author: zhucj * @date: 2019-08-28 10:18 */ @Slf4j @Component @Order(value = 1) public class OrderPayTask implements CommandLineRunner { @Autowired private WeChatPayUtils weChatPayUtils; @Scheduled(cron= "${task.cron.order}") public void orderTask(){ SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm"); log.info(s.format(new Date())+"=>開始執行支付訂單查詢定時任務"); //當前時間點 Date date = new Date(); //10分鍾之前點 String fastFormat = s.format(date.getTime()-600000); String lastFormat = s.format(date); /** * #### 訂單狀態查詢定時任務 ##### * 1.設置每隔10分鍾查詢一次條件為 * type:支付訂單 * status:未支付狀態的數據庫預訂單數據 * 2.通過商戶支付訂單號,調用微信查詢接口 獲取支付狀態 * 3.判斷是否狀態一致,如不一致修改 */ } @Override public void run(String... args) throws Exception { this.orderTask(); } }
==================================================================================
yml配置信息
==================================================================================
#微信支付參數
wx:
  pay:
    #微信平台appId
    appId: ******* #支付商戶號 mchNo: ******* #支付成功,異步回調地址 notifyUrl: ********** #支付秘鑰 key: ***************** #證書密碼 lcePassWord: ************* #證書存放路徑 certPath: ****/apiclient_cert.p12 #定時任務配置 task: cron: #支付訂單推送:每隔10分鍾查詢一次 order: 0 */10 * * * ?
===================================================================================
依賴maven jar包
===================================================================================
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

<!--htttpclient依賴-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.55</version>
</dependency>
<!-- swagger API -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.0</version>
</dependency>

 



 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM