package com.ruoyi.system.wxPay.utils; import com.fasterxml.jackson.core.type.TypeReference; import com.ruoyi.system.utils.wx.tools.WebUtils; import com.ruoyi.system.utils.wx.tools.WxJsonUtils; import com.ruoyi.system.utils.wx.tools.WxUtils; import com.ruoyi.system.wxPay.model.WxCloseOrderModel; import com.ruoyi.system.wxPay.model.WxPaymentInfoModel; import com.ruoyi.system.wxPay.model.WxPaymentRefundModel; import com.ruoyi.system.wxPay.resp.NotifyV3PayDecodeRespBody; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; import com.wechat.pay.contrib.apache.httpclient.notification.Notification; import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler; import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.*; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*; /** * @author xiaochen * @ClassName WxWifiV3Pay * @Description * @date 2021-11-13 21:10 */ @Slf4j public abstract class WxAbstractPay { /** * 请求成功相应码 */ private static final int STATUS_CODE = 200; /** * 请求成功相应码 */ private static final int OTHER_STATUS_CODE = 204; /** * 请求根地址 */ private static final String HOST = "https://api.mch.weixin.qq.com"; private static RuntimeException parameterError(String message, Object... args) { message = String.format(message, args); return new IllegalArgumentException("parameter error: " + message); } /** * 封装基础数据 * * @param requestBody * @param notifyUrl * @return */ protected String buildBaseParam(WxPaymentInfoModel requestBody, String notifyUrl) { // 封装基础数据 requestBody.setNotify_url(notifyUrl + requestBody.getNotify_url()); String reqBody = WxJsonUtils.toJsonString(requestBody); return reqBody; } /** * 微信调起支付参数 * 返回参数如有不理解 请访问微信官方文档 * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml * * @param prepayId 微信下单返回的prepay_id * @param appId 应用ID(appid) * @return 当前调起支付所需的参数 * @throws Exception */ protected Map wxTuneUp(PrivateKeySigner privateKeySigner, String appId, String prepayId) { String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); String nonceStr = WxUtils.generateNonceStr(); String packageStr = "prepay_id=" + prepayId; //加载签名 String signStr = Stream.of(appId, timeStamp, nonceStr, packageStr).collect(Collectors.joining("\n", "", "\n")); String packageSign = privateKeySigner.sign(signStr.getBytes(StandardCharsets.UTF_8)).getSign(); Map map = new HashMap<>(6); map.put("appId", appId); map.put("timeStamp", timeStamp); map.put("nonceStr", nonceStr); map.put("package", packageStr); map.put("signType", "RSA"); map.put("paySign", packageSign); return map; } /** * 构建方法请求 * * @param uri * @param socketTimeout * @param connectTimeout * @return */ protected HttpGet requestGet(String uri, int socketTimeout, int connectTimeout) { //请求URL HttpGet httpGet = new HttpGet(HOST + uri); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) .setConnectTimeout(connectTimeout).build(); httpGet.setConfig(requestConfig); httpGet.setHeader("Content-type", "application/json"); httpGet.setHeader("Accept", "application/json"); return httpGet; } /** * 构建方法请求 * * @param uri * @param socketTimeout * @param connectTimeout * @param reqdata * @return */ protected HttpPost requestPost(String uri, int socketTimeout, int connectTimeout, String reqdata) { //请求URL HttpPost httpPost = new HttpPost(HOST + uri); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) .setConnectTimeout(connectTimeout).build(); httpPost.setConfig(requestConfig); StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); return httpPost; } public abstract T verifyNotify(HttpServletRequest request, TypeReference valueTypeRef) throws Exception; /** * 接收回调 * * @param request * @return * @throws Exception */ public T verifyNotify(HttpServletRequest request, Verifier verifier, String apiKey, TypeReference valueTypeRef) throws Exception { String body = WxUtils.streamBodyByReceive(request); String requestId = request.getHeader(REQUEST_ID); String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP}; String value; // 验签必须参数检验 for (String headerName : headers) { value = request.getHeader(headerName); if (value == null || "".equals(value)) { throw parameterError("empty [%s], request-id=[%s]", headerName, requestId); } } String serial = request.getHeader(WECHAT_PAY_SERIAL); String signature = request.getHeader(WECHAT_PAY_SIGNATURE); String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); String nonce = request.getHeader(WECHAT_PAY_NONCE); // 构建request,传入必要参数 NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serial) .withNonce(nonce) .withTimestamp(timestamp) .withSignature(signature) .withBody(body) .build(); NotificationHandler handler = new NotificationHandler(verifier, apiKey.getBytes(StandardCharsets.UTF_8)); // 验签和解析请求体 Notification notification = handler.parse(notificationRequest); assert notification != null; T respBody = WxJsonUtils.parseObject(notification.getDecryptData(), valueTypeRef); return respBody; } /** * 订单查询 * * @param httpClient * @param socketTimeout * @param connectTimeout * @param url * @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody * @author xiaochen * @date 2021-12-20 17:12 */ protected NotifyV3PayDecodeRespBody query(CloseableHttpClient httpClient, int socketTimeout, int connectTimeout, String url) { //请求URL HttpGet httpGet = requestGet( url , socketTimeout , connectTimeout); CloseableHttpResponse response = null; try { response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); String respBodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); if (WxUtils.getLogger().isDebugEnabled()) { WxUtils.debug("请求成功:{}", respBodyStr); } if(404 == statusCode){ return null; } // 成功相应 if (STATUS_CODE == statusCode || OTHER_STATUS_CODE == statusCode ) { NotifyV3PayDecodeRespBody body = WxJsonUtils.parseObject(respBodyStr, NotifyV3PayDecodeRespBody.class); return body; } else { WxUtils.error("failed,resp code = {},return body = {}", statusCode, respBodyStr); throw new RuntimeException(respBodyStr); } } catch (ConnectTimeoutException e) { e.printStackTrace(); throw new RuntimeException("接口超时"); } catch (SocketTimeoutException e) { e.printStackTrace(); throw new RuntimeException("读取接口数据超时"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("接口请求失败,请尝试检查网络环境或请求接口是否能正常访问"); } finally { // 关闭响应 try { if (response != null) { //关闭结果集 response.getEntity().getContent().close(); response.close(); } } catch (IOException e) { throw new RuntimeException("关闭流异常"); } } } /** * 子级实现 * * @param out_trade_no * @return */ public abstract NotifyV3PayDecodeRespBody query(String out_trade_no); /** * 订单退款 * * @param refundModel * @return */ public abstract Map refund(WxPaymentRefundModel refundModel); public abstract String close(String out_trade_no); /** * 订单退款 * * @param httpClient * @param uri * @param httpReadTimeoutMs * @param httpConnectTimeoutMs * @param refundModel * @return */ public Map refund(CloseableHttpClient httpClient, String uri, int httpReadTimeoutMs, int httpConnectTimeoutMs, WxPaymentRefundModel refundModel) { String reqBody = WxJsonUtils.toJsonString(refundModel); //请求URL HttpEntityEnclosingRequestBase httpPost = requestPost( uri , httpReadTimeoutMs , httpConnectTimeoutMs, reqBody); String repBody = result(httpClient, httpPost); Map body = WxJsonUtils.parseObject(repBody, Map.class); return body; } /** * 请求结果 * * @param request * @return */ protected String result(CloseableHttpClient httpClient, HttpRequestBase request) { CloseableHttpResponse response = null; try { response = httpClient.execute(request); int statusCode = response.getStatusLine().getStatusCode(); String respBodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); if (WxUtils.getLogger().isDebugEnabled()) { WxUtils.debug("请求成功:{}", respBodyStr); } // 成功相应 if (STATUS_CODE == statusCode || OTHER_STATUS_CODE == statusCode) { return respBodyStr; } else { WxUtils.error("failed,resp code = {},return body = {}", statusCode, respBodyStr); throw new RuntimeException(respBodyStr); } } catch (ConnectTimeoutException e) { e.printStackTrace(); throw new RuntimeException("接口超时"); } catch (SocketTimeoutException e) { e.printStackTrace(); throw new RuntimeException("读取接口数据超时"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("接口请求失败,请尝试检查网络环境或请求接口是否能正常访问"); } finally { // 关闭响应 try { if (response != null) { //关闭结果集 response.getEntity().getContent().close(); response.close(); } } catch (IOException e) { throw new RuntimeException("关闭流异常"); } } } /** * 微信结果确认应答 * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。 * * @param * @throws IOException */ public void ack() throws IOException { ack(true, null); } /** * 微信结果确认应答 * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。 * * @param * @throws IOException */ public void ack(boolean ackSucc, String erroMsg) throws IOException { HttpServletResponse response = WebUtils.response(); PrintWriter writer = response.getWriter(); if (ackSucc) { log.info("响应微信回调成功!"); response.setStatus(200); writer.write("{\"code\": \"SUCCESS\",\"message\": \"成功\"}"); } else { log.info("响应微信回调失败:{}!", erroMsg); response.setStatus(500); writer.write("{\"code\": \"FAIL\",\"message\": " + (StringUtils.hasLength(erroMsg) ? erroMsg : "业务处理失败") + "}"); } // 关闭流 if (Objects.nonNull(writer)) { writer.close(); } } /** * 关闭订单 * @param httpClient * @param uri * @param httpReadTimeoutMs * @param httpConnectTimeoutMs * @param closeModel * @return */ public String close(CloseableHttpClient httpClient, String uri, int httpReadTimeoutMs, int httpConnectTimeoutMs, WxCloseOrderModel closeModel) { String reqBody = WxJsonUtils.toJsonString(closeModel); //请求URL HttpEntityEnclosingRequestBase httpPost = requestPost( uri , httpReadTimeoutMs , httpConnectTimeoutMs, reqBody); String repBody = result(httpClient, httpPost); return repBody; } }