前由: 最近作java的微信支付,查看微信支付sdk的maven倉庫,發現最新一次更新在2017年3月16日,版本號停留在0.0.3:php
<!-- https://mvnrepository.com/artifact/com.github.wxpay/wxpay-sdk --> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
同時,微信官方SDK(下載地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1) 內容版本以下:java
<groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>3.0.9</version>
官方SDK爲3.0.9版本一樣也是基於maven構建,只不過沒有上傳到maven的公共倉庫,那麼咱們就能夠本身動手打成jar包從而導入本地maven項目。git
打包步驟:github
- 修改配置文件WXPayConfig的抽象屬性。原版的缺省(default)抽象屬性只能在同一個package下可見,在不一樣package下面缺省屬性是沒有訪問權限的,跨包必須改爲公共public屬性才能夠。
public abstract class WXPayConfig { /** * 獲取 App ID * * @return App ID */ public abstract String getAppID(); /** * 獲取 Mch ID * * @return Mch ID */ public abstract String getMchID(); /** * 獲取 API 密鑰 * * @return API密鑰 */ public abstract String getKey(); /** * 獲取商戶證書內容 * * @return 商戶證書內容 */ public abstract InputStream getCertStream(); /** * HTTP(S) 鏈接超時時間,單位毫秒 * * @return */ public int getHttpConnectTimeoutMs() { return 6*1000; } /** * HTTP(S) 讀數據超時時間,單位毫秒 * * @return */ public int getHttpReadTimeoutMs() { return 8*1000; } /** * 獲取WXPayDomain, 用於多域名容災自動切換 * @return */ public abstract IWXPayDomain getWXPayDomain(); /** * 是否自動上報。 * 若要關閉自動上報,子類中實現該函數返回 false 便可。 * * @return */ public boolean shouldAutoReport() { return true; } /** * 進行健康上報的線程的數量 * * @return */ public int getReportWorkerNum() { return 6; } /** * 健康上報緩存消息的最大數量。會有線程去獨立上報 * 粗略計算:加入一條消息200B,10000消息佔用空間 2000 KB,約爲2MB,能夠接受 * * @return */ public int getReportQueueMaxSize() { return 10000; } /** * 批量上報,一次最多上報多個數據 * * @return */ public int getReportBatchSize() { return 10; } }
2.自行打包完成,安裝本地jar包到本地倉庫須要以下命令(注意不要換行):web
mvn install:install-file -Dfile=E:\wxpay-sdk-3.0.9.jar -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dversion=3.0.9 -Dpackaging=jar
3.maven的pom.xml引入wxpay-sdk依賴。算法
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>3.0.9</version> </dependency>
同時須要注意打包wxpay-sdk-3.0.9.jar
並無把wxpay依賴的包打進去,咱們還需引入wxpay-sdk
依賴的包:spring
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.12</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency>
wxpay-sdk依賴的slf4j與spring-boot-starter-web
所依賴的的spring-boot-starter-logging
有衝突,請注意依賴排除,本人排除的是spring-boot-starter-logging
的依賴。數據庫
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions><!-- 去掉springboot默認配置的日誌 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
SDK使用:apache
1.微信支付yml配置文件api
# 微信app支付 pay: wxpay: app: appID: "xxxxx" mchID: "xxxx" key: "xxxxxxxxxxxxx" #從微信商戶平臺下載的安全證書存放的路徑、放在resources下面,切記必定要看看target目錄下的class文件下有沒有打包apiclient_cert.p12文件 certPath: static/cert/wxpay/apiclient_cert.p12 #微信支付成功的異步通知接口 #payNotifyUrl: ${server.service-domain}/wxPay/notify payNotifyUrl: http://xxx.com/wxPay/notify
2.讀取配置信息
@Component @ConfigurationProperties(prefix = "pay.wxpay.app") public class WxPayAppConfig extends WXPayConfig { /** * appID */ private String appID; /** * 商戶號 */ private String mchID; /** * API 密鑰 */ private String key; /** * API證書絕對路徑 (本項目放在了 resources/cert/wxpay/apiclient_cert.p12") */ private String certPath; /** * HTTP(S) 鏈接超時時間,單位毫秒 */ private int httpConnectTimeoutMs = 8000; /** * HTTP(S) 讀數據超時時間,單位毫秒 */ private int httpReadTimeoutMs = 10000; /** * 微信支付異步通知地址 */ private String payNotifyUrl; /** * 微信退款異步通知地址 */ private String refundNotifyUrl; /** * 獲取商戶證書內容(這裏證書須要到微信商戶平臺進行下載) * * @return 商戶證書內容 */ @Override public InputStream getCertStream() { InputStream certStream =getClass().getClassLoader().getResourceAsStream(certPath); return certStream; } public String getAppID() { return appID; } public void setAppID(String appID) { this.appID = appID; } public String getMchID() { return mchID; } public void setMchID(String mchID) { this.mchID = mchID; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getCertPath() { return certPath; } public void setCertPath(String certPath) { this.certPath = certPath; } public int getHttpConnectTimeoutMs() { return httpConnectTimeoutMs; } public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) { this.httpConnectTimeoutMs = httpConnectTimeoutMs; } public int getHttpReadTimeoutMs() { return httpReadTimeoutMs; } public void setHttpReadTimeoutMs(int httpReadTimeoutMs) { this.httpReadTimeoutMs = httpReadTimeoutMs; } public String getPayNotifyUrl() { return payNotifyUrl; } public void setPayNotifyUrl(String payNotifyUrl) { this.payNotifyUrl = payNotifyUrl; } public String getRefundNotifyUrl() { return refundNotifyUrl; } public void setRefundNotifyUrl(String refundNotifyUrl) { this.refundNotifyUrl = refundNotifyUrl; } public IWXPayDomain getWXPayDomain() { IWXPayDomain iwxPayDomain = new IWXPayDomain() { public void report(String domain, long elapsedTimeMillis, Exception ex) { } public DomainInfo getDomain(WXPayConfig config) { return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true); } }; return iwxPayDomain; } }
3.通用返回結果集
/** * @Description 通用返回結果集 * @Author */ public class ResultMap extends HashMap<String, Object> { public ResultMap() { put("state", true); put("code", 0); put("msg", "success"); } public static ResultMap error(int code, String msg) { ResultMap r = new ResultMap(); r.put("state", false); r.put("code", code); r.put("msg", msg); return r; } public static ResultMap error(String msg) { return error(HttpStatus.METHOD_NOT_ALLOWED.value(), msg); } public static ResultMap error() { return error(HttpStatus.METHOD_NOT_ALLOWED.value(), "未知異常,請聯繫管理員"); } public static ResultMap ok(String msg) { ResultMap r = new ResultMap(); r.put("msg", msg); return r; } public static ResultMap ok(Map<String, Object> par) { ResultMap r = new ResultMap(); r.putAll(par); return r; } public static ResultMap ok() { return new ResultMap(); } public ResultMap put(String key, Object value) { super.put(key, value); return this; } }
4.微信支付服務接口
/** * 微信支付服務接口 */ public interface WxPayService { /** * @Description: 微信支付統一下單 * @param orderNo: 訂單編號 * @param amount: 實際支付金額 * @param body: 訂單描述 * @Author: * @return */ ResultMap unifiedOrder(String orderNo, double amount, String body) ; /** * @Description: 訂單支付異步通知 * @param notifyStr: 微信異步通知消息字符串 * @Author: * @return */ String notify(String notifyStr) throws Exception; /** * @Description: 退款 * @param orderNo: 訂單編號 * @param amount: 實際支付金額 * @param refundReason: 退款緣由 * @Author: * @return */ ResultMap refund(String orderNo, double amount, String refundReason) throws Exception; }
5.服務接口實現類
@Service public class WxPayServiceImpl implements WxPayService { private final Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class); @Autowired private WxPayAppConfig wxPayAppConfig; @Override public ResultMap unifiedOrder(String orderNo, double amount, String body) { Map<String, String> returnMap = new HashMap<>(); Map<String, String> responseMap = new HashMap<>(); Map<String, String> requestMap = new HashMap<>(); try { WXPay wxpay = new WXPay(wxPayAppConfig); requestMap.put("body", body); // 商品描述 requestMap.put("out_trade_no", orderNo); // 商戶訂單號 requestMap.put("total_fee", String.valueOf((int)(amount*100))); // 總金額 //requestMap.put("spbill_create_ip", HttpContextUtils.getIpAddr()); // 終端IP requestMap.put("trade_type", "APP"); // App支付類型 requestMap.put("notify_url", wxPayAppConfig.getPayNotifyUrl()); // 接收微信支付異步通知回調地址 Map<String, String> resultMap = wxpay.unifiedOrder(requestMap); for (String resultKey : resultMap.keySet()) { logger.info("訂單key:{}", resultMap.get(resultKey)); } //獲取返回碼 String returnCode = resultMap.get("return_code"); String returnMsg = resultMap.get("return_msg"); //若返回碼爲SUCCESS,則會返回一個result_code,再對該result_code進行判斷 if ("SUCCESS".equals(returnCode)) { String resultCode = resultMap.get("result_code"); String errCodeDes = resultMap.get("err_code_des"); if ("SUCCESS".equals(resultCode)) { responseMap = resultMap; } } if (responseMap == null || responseMap.isEmpty()) { return ResultMap.error("獲取預支付交易會話標識失敗"); } // 三、簽名生成算法 Long time = System.currentTimeMillis() / 1000; String timestamp = time.toString(); returnMap.put("appid", wxPayAppConfig.getAppID()); returnMap.put("partnerid", wxPayAppConfig.getMchID()); returnMap.put("prepayid", responseMap.get("prepay_id")); returnMap.put("noncestr", responseMap.get("nonce_str")); returnMap.put("timestamp", timestamp); returnMap.put("package", "Sign=WXPay"); returnMap.put("sign", WXPayUtil.generateSignature(returnMap, wxPayAppConfig.getKey()));//微信支付簽名 return ResultMap.ok().put("data", returnMap); } catch (Exception e) { logger.error("訂單號:{},錯誤信息:{}", orderNo, e.getMessage()); return ResultMap.error("微信支付統一下單失敗"); } } @Override public String notify(String notifyStr) { String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[報文爲空]]></return_msg></xml> "; try { // 轉換成map Map<String, String> resultMap = WXPayUtil.xmlToMap(notifyStr); WXPay wxpayApp = new WXPay(wxPayAppConfig); if (wxpayApp.isPayResultNotifySignatureValid(resultMap)) { String returnCode = resultMap.get("return_code"); //狀態 String outTradeNo = resultMap.get("out_trade_no");//商戶訂單號 String transactionId = resultMap.get("transaction_id"); if (returnCode.equals("SUCCESS")) { if (! StringUtils.isEmpty(outTradeNo)) { /** * 注意!!! * 請根據業務流程,修改數據庫訂單支付狀態,和其餘數據的相應狀態 * */ logger.info("微信手機支付回調成功,訂單號:{}", outTradeNo); xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } } } } catch (Exception e) { e.printStackTrace(); } return xmlBack; } @Override public ResultMap refund(String orderNo, double amount, String refundReason) throws Exception { if(StringUtils.isEmpty(orderNo)){ return ResultMap.error("訂單編號不能爲空"); } if(amount <= 0){ return ResultMap.error("退款金額必須大於0"); } Map<String, String> responseMap = new HashMap<>(); Map<String, String> requestMap = new HashMap<>(); WXPay wxpay = new WXPay(wxPayAppConfig); System.out.println(wxPayAppConfig.toString()); requestMap.put("out_trade_no", orderNo); requestMap.put("out_refund_no", "xxxx自行手動生成");//商戶系統內部的退款單號,商戶系統內部惟一,只能是數字、大小寫字母_-|*@ ,同一退款單號屢次請求只退一筆。 requestMap.put("total_fee", "訂單總金額"); requestMap.put("refund_fee", String.valueOf((int)(amount*100)));//所需退款金額 requestMap.put("refund_desc", refundReason); try { responseMap = wxpay.refund(requestMap); } catch (Exception e) { e.printStackTrace(); } for (String responseKey : responseMap.keySet()) { logger.info("訂單key:{}", responseMap.get(responseKey)); } String return_code = responseMap.get("return_code"); //返回狀態碼 String return_msg = responseMap.get("return_msg"); //返回信息 if ("SUCCESS".equals(return_code)) { String result_code = responseMap.get("result_code"); //業務結果 String err_code_des = responseMap.get("err_code_des"); //錯誤代碼描述 if ("SUCCESS".equals(result_code)) { //表示退款申請接受成功,結果經過退款查詢接口查詢 //修改用戶訂單狀態爲退款申請中或已退款。退款異步通知根據需求,可選 return ResultMap.ok("退款申請成功"); } else { logger.info("訂單號:{}錯誤信息:{}", orderNo, err_code_des); return ResultMap.error(err_code_des); } } else { logger.info("訂單號:{}錯誤信息:{}", orderNo, return_msg); return ResultMap.error(return_msg); } } }
6.微信支付對外REST接口(用到Swagger2)
@Api(tags = "微信支付接口管理") @RestController @RequestMapping("/wxPay") public class WxPayController{ @Autowired private WxPayService wxPayService; private final Logger logger = LoggerFactory.getLogger(WxPayController.class); /** * 統一下單接口 */ @ApiOperation(value = "統一下單", notes = "統一下單") @GetMapping("/unifiedOrder") public ResultMap unifiedOrder( @ApiParam(value = "訂單號") @RequestParam String orderNo, @ApiParam(value = "訂單金額") @RequestParam double amount, @ApiParam(value = "商品名稱") @RequestParam String body, HttpServletRequest request) { try { // 一、驗證訂單是否存在 // 二、開始微信支付統一下單 ResultMap resultMap = wxPayService.unifiedOrder(orderNo, amount, body); return resultMap;//系統通用的返回結果集,見文章末尾 } catch (Exception e) { logger.error(e.getMessage()); return ResultMap.error("運行異常,請聯繫管理員"); } } /** * 微信支付異步通知 */ @RequestMapping(value = "/notify") public String payNotify(HttpServletRequest request) { InputStream is = null; String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[報文爲空]]></return_msg></xml> "; try { is = request.getInputStream(); // 將InputStream轉換成String BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } xmlBack = wxPayService.notify(sb.toString()); } catch (Exception e) { logger.error("微信手機支付回調通知失敗:", e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } return xmlBack; } @ApiOperation(value = "退款", notes = "退款") @PostMapping("/refund") public ResultMap refund(@ApiParam(value = "訂單號") @RequestParam String orderNo, @ApiParam(value = "退款金額") @RequestParam double amount, @ApiParam(value = "退款緣由") @RequestParam(required = false) String refundReason) throws Exception { return wxPayService.refund(orderNo, amount, refundReason); } }