Java集成APIV3微信支付(2024-11-27 更新后完整版)

要使用Java集成API V3微信支付,你需要按照以下步骤操作:

  1. 准备工作
    APPID:绑定支付的APPID(必须配置)
    小程序和JSAPI支付需要公众号的APPID
    APP支付需要微信支付绑定的APP的APPID
    MCHID:商户号(必须配置)
    KEY:商户支付密钥,参考开户邮件设置(必须配置)
    APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置)

  2. 引入相关依赖:在项目的pom.xml文件中添加以下依赖项:

3.完整代码

1、引入wxPay SDK

<!--微信支付SDK-->
<dependency>
   <groupId>com.github.wechatpay-apiv3</groupId>
   <artifactId>wechatpay-apache-httpclient</artifactId>
   <version>0.4.8</version>
</dependency>

2、微信支付统一入口

@Data
public class wechatPayGoodsDto {

/**
* 币种 CNY
*/
private String currency;

/**
* 订单描述
*/
private String description;

/**
* 回调通知接口
*/
private String notifyUrl;

/**
* 支付方式
*/
private String type;

/**
* 支付金额
*/
private Integer money;

/**
* 场景类型:IOS, Android, Wap
*/
private String mobileType;

/**
* 订单号
*/
private String orderCode;

/**
* openId
*/
private String openId;

/**
* unionId
*/
private String unionId;

}
/**
 * @Author:
 * @Description:
 **/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/pay")
@Tag(name = "支付统一管理表")
public class WechatPayCommonController {


   @Resource
   private WechatPayConfig wechatPayConfig;

   @Resource
   private WechatPayRequest wechatPayRequest;

   /**
    * 无需应答签名
    */
   @Resource
   private CloseableHttpClient wxPayNoSignClient;

   /**
    * type:h5、jsapi、app、native、sub_jsapi
    *
    * @param wechatPayGoodsDto
    * @return
    */
   @Operation(summary = "统一下单-统一接口")
   @PostMapping("/transactions")
   public R<Map<String, Object>> transactions(@RequestBody WechatPayGoodsDto wechatPayGoodsDto, HttpServletRequest request) {
      String type = wechatPayGoodsDto.getType().toLowerCase();
      // 统一参数封装
      Map<String, Object> params = new HashMap<>(8);
      params.put("appid", type.equals(WechatPayUrlEnum.APP.getType())
            ? wechatPayConfig.getAppAppId() : wechatPayConfig.getAppId());
      params.put("mchid", wechatPayConfig.getMchId());
      params.put("description", wechatPayGoodsDto.getDescription());
      params.put("out_trade_no", wechatPayGoodsDto.getOrderCode());
      params.put("notify_url", wechatPayGoodsDto.getNotifyUrl());

      Map<String, Object> amountMap = new HashMap<>(4);
      amountMap.put("total", wechatPayGoodsDto.getMoney());// 金额单位为分
      amountMap.put("currency", wechatPayGoodsDto.getCurrency());
      params.put("amount", amountMap);

      // 场景信息
      Map<String, Object> sceneInfoMap = new HashMap<>(4);
      // 客户端IP
      sceneInfoMap.put("payer_client_ip", IpUtilV2.getIp(request));
      // 除H5与JSAPI有特殊参数外,其他的支付方式都一样
      if (type.equals(WechatPayUrlEnum.H5.getType())) {
         sceneInfoMap.put("h5_info", MapBuilder.create().put("type", wechatPayGoodsDto.getMobileType()).build());
      } else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
         params.put("payer", MapBuilder.create().put("openid", wechatPayGoodsDto.getOpenId()).build());
      }
      params.put("scene_info", sceneInfoMap);
      String paramsStr = JSON.toJSONString(params);
      // 重写type值,因为小程序会多一个下划线(sub_type)
      String[] split = type.split("_");
      String newType = split[split.length - 1];
      String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType)), paramsStr);
      Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
      });
      Map<String, Object> signMap = paySignMsg(resMap, type);
      resMap.put("type", type);
      resMap.put("signMap", signMap);
      return R.ok(resMap);
   }


   private Map<String, Object> paySignMsg(Map<String, Object> map, String type) {
      // 设置签名信息,Native与H5不需要
      if (type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType())) {
         return null;
      }
      long timeMillis = System.currentTimeMillis();
      String appId = type.equals(WechatPayUrlEnum.APP.getType()) ? wechatPayConfig.getAppAppId() : wechatPayConfig.getAppId();
      String timeStamp = timeMillis / 1000 + "";
      String nonceStr = timeMillis + "";
      String prepayId = map.get("prepay_id").toString();
      String packageStr = "prepay_id=" + prepayId;
      // 公共参数
      Map<String, Object> resMap = new HashMap<>();
      // JSAPI、SUB_JSAPI(小程序)
      if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
         resMap.put("nonceStr", nonceStr);
         resMap.put("timeStamp", timeStamp);
         resMap.put("appId", appId);
         resMap.put("package", packageStr);
         // 使用字段appId、timeStamp、nonceStr、package进行签名
         String paySign = createSign(resMap);
         resMap.put("paySign", paySign);
         resMap.put("signType", "HMAC-SHA256");
//       resMap.put("signType", "RSA");
      }
      // APP
      if (type.equals(WechatPayUrlEnum.APP.getType())) {
         return prepayWithRequestPayment(prepayId);
      }
      return resMap;
   }

   public String getSign(String message) {
      byte[] sign;
      try {
         Signature signature = Signature.getInstance("SHA256withRSA");
         PrivateKey privateKey = wechatPayConfig.getPrivateKey();
         signature.initSign(privateKey);
         signature.update(message.getBytes(StandardCharsets.UTF_8));
         sign = signature.sign();
      } catch (Exception e) {
         throw new BusinessException(e.getMessage());
      }
      return Base64.getEncoder().encodeToString(sign);
   }

   public Map<String, Object> prepayWithRequestPayment(String prepayId) {
      long timestamp = Instant.now().getEpochSecond();
      String nonceStr = RandomUtil.randomNumbers(32);
      String appId = wechatPayConfig.getAppAppId();
      String message = appId + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";
      Map<String, Object> resMap = new HashMap<>();
      String sign = getSign(message);
      resMap.put("appid", appId);
      resMap.put("timeStamp", timestamp);
      resMap.put("nonceStr", nonceStr);
      resMap.put("prepayid", prepayId);
      resMap.put("package", "Sign=WXPay");
      resMap.put("partnerid", wechatPayConfig.getMchId());
      resMap.put("signType", "HMAC-SHA256");
      resMap.put("paySign", sign);
      resMap.put("sign", sign);
      System.out.println("resMap: " + resMap);
      return resMap;
   }

   /**
    * 获取加密数据
    */
   private String createSign(Map<String, Object> params) {
      try {
         List<String> signList = new ArrayList<>(5);
         for (Map.Entry<String, Object> entry : params.entrySet()) {
            signList.add(entry.getKey() + "=" + entry.getValue());
         }
         String signStr = String.join("&", signList);
         signStr = signStr + "&key=" + wechatPayConfig.getApiV3Key();
         Mac sha = Mac.getInstance("HmacSHA256");
         SecretKeySpec secretKey = new SecretKeySpec(wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
         sha.init(secretKey);
         byte[] array = sha.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
         StringBuilder sb = new StringBuilder();
         for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
         }
         signStr = sb.toString().toUpperCase();
         System.out.println(signStr);
         return signStr;
      } catch (Exception e) {
         throw new RuntimeException("加密失败!");
      }
   }

   /**
    * TODO 如果是扫码支付时,该接口就很有必要,应该前端通过轮询的方式请求该接口查询订单是否支付成功
    *
    * @param orderNo
    * @return
    */
   @Operation(summary = "根据订单号查询订单-统一接口")
   @GetMapping("/transactions/{orderNo}")
   public R<Map<String, Object>> transactionsByOrderNo(@PathVariable("orderNo") String orderNo) {
      String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.ORDER_QUERY_BY_NO.getType().concat(orderNo))
            .concat("?mchid=").concat(wechatPayConfig.getMchId());

      String res = wechatPayRequest.wechatHttpGet(url);
      Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
      });
      String outTradeNo = resMap.get("out_trade_no").toString();
      String appId = resMap.get("appid").toString();
      String mchId = resMap.get("mchid").toString();
      String tradeState = StrUtil.toString(resMap.get("trade_state"));
      return R.ok(resMap);
   }

   /**
    * 关闭(取消)订单
    * TODO 用于在客户下单后,不进行支付,取消订单的场景
    *
    * @param orderNo
    * @return
    */
   @Operation(summary = "关闭(取消)订单-统一接口")
   @GetMapping("/closeOrder/{orderNo}")
   public R<String> closeOrder(@PathVariable("orderNo") String orderNo) {
      String url = String.format(WechatPayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
      url = wechatPayConfig.getBaseUrl().concat(url);
      Map<String, String> params = new HashMap<>(2);
      params.put("mchid", wechatPayConfig.getMchId());
      String paramsStr = JSON.toJSONString(params);
      String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
      return R.ok(res);
   }

   /**
    * 申请退款
    *
    * @param wechatReturnMoneyDto
    */
   @Operation(summary = "申请退款-统一接口")
   @PostMapping("/refundOrder")
   public R<String> refundOrder(@RequestBody WechatReturnMoneyDto wechatReturnMoneyDto) {
      String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS.getType());
      Map<String, Object> params = new HashMap<>(2);
      params.put("out_trade_no", wechatReturnMoneyDto.getOrderCode()); // 订单编号
      params.put("out_refund_no", wechatReturnMoneyDto.getReturnCode());    // 退款单编号 - 自定义
      params.put("reason", wechatReturnMoneyDto.getReason());// 退款原因
      params.put("notify_url", wechatReturnMoneyDto.getRefundNotifyUrl()); // 退款通知回调地址
      Map<String, Object> amountMap = new HashMap<>();
      amountMap.put("refund", wechatReturnMoneyDto.getRefund()); //退款金额,单位:分
      amountMap.put("total", wechatReturnMoneyDto.getTotal()); //原订单金额,单位:分
      amountMap.put("currency", wechatReturnMoneyDto.getCurrency()); //退款币种
      params.put("amount", amountMap);
      String paramsStr = JSON.toJSONString(params);
      String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
      return R.ok(res);
   }

   /**
    * 查询单笔退款信息
    *
    * @param refundNo
    * @return
    */
   @Operation(summary = "查询单笔退款信息-统一接口")
   @GetMapping("/queryRefundOrder/{refundNo}")
   public Map<String, Object> queryRefundOrder(@PathVariable("refundNo") String refundNo) {
      String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo));
      String res = wechatPayRequest.wechatHttpGet(url);
      Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
      });

      String successTime = resMap.get("success_time").toString();
      String refundId = resMap.get("refund_id").toString();
      /**
       * 款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
       * 枚举值:
       * SUCCESS:退款成功
       * CLOSED:退款关闭
       * PROCESSING:退款处理中
       * ABNORMAL:退款异常
       */
      String status = resMap.get("status").toString();
      /**
       * 枚举值:
       * ORIGINAL:原路退款
       * BALANCE:退回到余额
       * OTHER_BALANCE:原账户异常退到其他余额账户
       * OTHER_BANKCARD:原银行卡异常退到其他银行卡
       */
      String channel = resMap.get("channel").toString();
      // TODO 在查询单笔退款信息时,可以再去查询一次订单的状态,保证该订单已经退款完毕了
      return resMap;
   }

   /**
    * 申请交易账单
    *
    * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ,如果传入日期未为当天则会出错
    * @param billType 分为:ALL、SUCCESS、REFUND
    *                 ALL:返回当日所有订单信息(不含充值退款订单)
    *                 SUCCESS:返回当日成功支付的订单(不含充值退款订单)
    *                 REFUND:返回当日退款订单(不含充值退款订单)
    * @return
    */
   @Operation(summary = "申请交易账单-统一接口")
   @GetMapping("/tradeBill")
   public String tradeBill(@RequestParam("billDate") String billDate, @RequestParam("billType") String billType) {
      String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.TRADE_BILLS.getType())
            .concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);
      String res = wechatPayRequest.wechatHttpGet(url);
      Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
      });
      String downloadUrl = resMap.get("download_url").toString();
      return downloadUrl;
   }

   /**
    * @param billDate    格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
    * @param accountType 分为:BASIC、OPERATION、FEES
    *                    BASIC:基本账户
    *                    OPERATION:运营账户
    *                    FEES:手续费账户
    * @return
    */
   @Operation(summary = "申请资金账单-统一接口")
   @GetMapping("/fundFlowBill")
   public String fundFlowBill(@RequestParam("billDate") String billDate, @RequestParam("accountType") String accountType) {
      String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.FUND_FLOW_BILLS.getType())
            .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType);
      String res = wechatPayRequest.wechatHttpGet(url);
      Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
      });
      String downloadUrl = resMap.get("download_url").toString();
      return downloadUrl;
   }


   @Operation(summary = "下载账单-统一接口")
   @GetMapping("/downloadBill")
   public void downloadBill(String downloadUrl) {
      HttpGet httpGet = new HttpGet(downloadUrl);
      httpGet.addHeader("Accept", "application/json");
      CloseableHttpResponse response = null;
      try {
         //使用wxPayClient发送请求得到响应
         response = wxPayNoSignClient.execute(httpGet);
         String body = EntityUtils.toString(response.getEntity());
         int statusCode = response.getStatusLine().getStatusCode();
         if (statusCode == 200 || statusCode == 204) {
            log.info("下载账单,返回结果 = " + body);
         } else {
            throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + body);
         }
         // TODO 将body内容存入本地或者输出到浏览器,演示存入本地
         writeStringToFile(body);

      } catch (Exception e) {
         throw new RuntimeException(e.getMessage());
      } finally {
         if (response != null) {
            try {
               response.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
   }

   private void writeStringToFile(String body) {
      FileWriter fw = null;
      try {
         String filePath = "D:\\wxPay.txt";
         fw = new FileWriter(filePath, true);
         BufferedWriter bw = new BufferedWriter(fw);
         bw.write(body);
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         try {
            if (fw != null) {
               fw.close();
            }
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }
/回调通知


   private final ReentrantLock lock = new ReentrantLock();
   @Resource
   private WechatPayCommons wechatPayCommons;

   /**
    * 支付回调应该写在各个服务中,进行对应的处理,回调地址,通过服务传递到pay中
    *
    * @param request
    * @param response
    * @return
    */
   @Operation(summary = "支付回调")
   @PostMapping("/payNotify")
   public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
      log.info("支付回调");
      // 处理通知参数
      String body = HttpUtils.readData(request);
      Map<String, String> headers = HttpUtils.getHeaders(request);
      Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headers);
      if (bodyMap == null) {
         return ResponseCommon.falseMsg(response);
      }
      log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
      if (lock.tryLock()) {
         try {
            // 解密resource中的通知数据
            String resource = bodyMap.get("resource").toString();
            Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
            String orderNo = resourceMap.get("out_trade_no").toString();
            String transactionId = resourceMap.get("transaction_id").toString();

            // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
            log.warn("=========== 根据订单号,做幂等处理 ===========");
         } finally {
            //要主动释放锁
            lock.unlock();
         }
      }
      //成功应答
      return ResponseCommon.trueMsg(response);
   }


   @Operation(summary = "退款回调")
   @PostMapping("/refundNotify")
   public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
      log.info("退款回调");
      // 处理通知参数
      String body = HttpUtils.readData(request);
      Map<String, String> headers = HttpUtils.getHeaders(request);
      Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headers);
      if (bodyMap == null) {
         return ResponseCommon.falseMsg(response);
      }
      log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
      if (lock.tryLock()) {
         try {
            // 解密resource中的通知数据
            String resource = bodyMap.get("resource").toString();
            Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
            String orderNo = resourceMap.get("out_trade_no").toString();
            String transactionId = resourceMap.get("transaction_id").toString();
            // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
            log.warn("=========== 根据订单号,做幂等处理 ===========");
         } finally {
            //要主动释放锁
            lock.unlock();
         }
      }
      //成功应答
      return ResponseCommon.trueMsg(response);
   }


   /**
    * 微信回调信息解密
    *
    * @param resource 加密的回调信息
    * @return R
    */
   @Operation(summary = "解密微信回调信息")
   @GetMapping("/decryptFromResource")
   public R<Map<String, Object>> decryptFromResource(@RequestParam String resource) {
      Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
      return R.ok(resourceMap);
   }

   /**
    * 解析微信支付回调信息
    *
    * @param body
    * @param headers
    * @return
    */
   @Operation(summary = "解密微信回调信息")
   @GetMapping("/getNotifyBody")
   R<Map<String, Object>> getNotifyBody(@RequestParam(name = "body") String body, @RequestParam(name = "headers") String headers) {
      Map<String, String> headerMap = (Map<String, String>) JSONObject.parse(headers);
      Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headerMap);
      return R.ok(bodyMap);
   }

   @Operation(summary = "验签获取微信返回内容")
   @GetMapping("/validNotifyBody")
   R<Map<String, Object>> validNotifyBody(@RequestParam(name = "body") String body, @RequestParam(name = "headers") String headers) {
      Map<String, String> headerMap = (Map<String, String>) JSONObject.parse(headers);
      Map<String, Object> bodyMap = wechatPayCommons.validNotifyBody(body, headerMap);
      return R.ok(bodyMap);
   }

}

 

2、相关的配置类



/**
 * @Author:
 * @Description:
 **/
@Slf4j
public class WechatPayValidator {
   /**
    * 应答超时时间,单位为分钟
    */
   private static final long RESPONSE_EXPIRED_MINUTES = 5;
   private final Verifier verifier;
   private final String requestId;
   private final String body;


   public WechatPayValidator(Verifier verifier, String requestId, String body) {
      this.verifier = verifier;
      this.requestId = requestId;
      this.body = body;
   }

   protected static IllegalArgumentException parameterError(String message, Object... args) {
      message = String.format(message, args);
      return new IllegalArgumentException("parameter error: " + message);
   }

   protected static IllegalArgumentException verifyFail(String message, Object... args) {
      message = String.format(message, args);
      return new IllegalArgumentException("signature verify fail: " + message);
   }

   public final boolean validate(Map<String, String> headers) {
      try {
         //处理请求参数
         validateParameters(headers);

         //构造验签名串
         String message = buildMessage(headers);

         String serial = headers.get(WechatPayHttpHeaders.WECHAT_PAY_SERIAL);
         String signature = headers.get(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE);

         //验签
         if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
            throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                  serial, message, signature, requestId);
         }
      } catch (IllegalArgumentException e) {
         log.warn(e.getMessage());
         return false;
      }

      return true;
   }

   private void validateParameters(Map<String, String> headerMap) {

      // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
      String[] headers = {WechatPayHttpHeaders.WECHAT_PAY_SERIAL, WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE, WechatPayHttpHeaders.WECHAT_PAY_NONCE, WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP};

      String header = null;
      for (String headerName : headers) {
         header = headerMap.get(headerName);
         if (header == null) {
            throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
         }
      }

      //判断请求是否过期
      String timestampStr = header;
      try {
         Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
         // 拒绝过期请求
         if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
            throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
         }
      } catch (DateTimeException | NumberFormatException e) {
         throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
      }
   }

   private String buildMessage(Map<String, String> headers) {
      String timestamp = headers.get(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP);
      String nonce = headers.get(WechatPayHttpHeaders.WECHAT_PAY_NONCE);
      return timestamp + "\n"
            + nonce + "\n"
            + body + "\n";
   }

   private String getResponseBody(CloseableHttpResponse response) throws IOException {
      HttpEntity entity = response.getEntity();
      return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
   }
   public static Map<String, Object> decryptFromResource(Map encrypt, String apiV3Key) {
      try {
         if(!encrypt.containsKey("resource")){
            throw new ValidateCodeException("未包含待解密数据");
         }
         Map<String, String> resource = (Map) encrypt.get("resource");
         String ciphertext = resource.get("ciphertext");
         String nonce = resource.get("nonce");
         String associatedData = resource.get("associated_data");

         log.info("密文: {}", ciphertext);
         AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
         String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
               nonce.getBytes(StandardCharsets.UTF_8),
               ciphertext);
         return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {});
      }catch (Exception e){
         e.printStackTrace();
      }
      return null;
   }
   /**
    * 对称解密,异步通知的加密数据
    *
    * @param resource 加密数据
    * @param apiV3Key apiV3密钥
    * @param type     1-支付,2-退款
    * @return
    */
   public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {

      String msg = type == 1 ? "支付成功" : "退款成功";
      try {
         Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
         });
         String ciphertext = resourceMap.get("ciphertext");
         String nonce = resourceMap.get("nonce");
         String associatedData = resourceMap.get("associated_data");
         AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
         String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
               nonce.getBytes(StandardCharsets.UTF_8),
               ciphertext);
         return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
         });
      } catch (Exception e) {
         throw new RuntimeException("回调参数,解密失败!");
      }
   }
}
/**
 * @Author:
 * @Description:
 **/
@Component
@Slf4j
public class WechatPayRequest {
   @Resource
   private CloseableHttpClient wxPayClient;
   public  String wechatHttpGet(String url) {
      try {
         // 拼接请求参数
         HttpGet httpGet = new HttpGet(url);
         httpGet.setHeader("Accept", "application/json");

         //完成签名并执行请求
         CloseableHttpResponse response = wxPayClient.execute(httpGet);

         return getResponseBody(response);
      }catch (Exception e){
         throw new RuntimeException(e.getMessage());
      }
   }

   public  String wechatHttpPost(String url,String paramsStr) {
      try {
         HttpPost httpPost = new HttpPost(url);
         StringEntity entity = new StringEntity(paramsStr, "utf-8");
         entity.setContentType("application/json");
         httpPost.setEntity(entity);
         httpPost.setHeader("Accept", "application/json");

         CloseableHttpResponse response = wxPayClient.execute(httpPost);
         return getResponseBody(response);
      }catch (Exception e){
         throw new RuntimeException(e.getMessage());
      }
   }

   private String getResponseBody(CloseableHttpResponse response) throws IOException {

      //响应体
      HttpEntity entity = response.getEntity();
      String body = entity==null?"":EntityUtils.toString(entity);
      //响应状态码
      int statusCode = response.getStatusLine().getStatusCode();

      //处理成功,204是,关闭订单时微信返回的正常状态码
      if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
         log.info("成功, 返回结果 = " + body);
      } else {
         String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
         log.error(msg);
         throw new RuntimeException(msg);
      }
      return body;
   }
}

/**
 * @Author:
 * @Description:
 **/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
   /**
    * 应用编号
    */
// @Value("${wxpay.appId}")
   private String appId;

   private String appAppId;
   /**
    * 商户号
    */
   private String mchId;
   /**
    * 服务商商户号
    */
   private String slMchId;
   /**
    * APIv2密钥
    */
   private String apiKey;
   /**
    * APIv3密钥
    */
   private String apiV3Key;
   /**
    * 支付通知回调地址
    */
   private String notifyUrl;
   /**
    * 退款回调地址
    */
   private String refundNotifyUrl;

   /**
    * API 证书中的 key.pem
    */
   private String keyPemPath;

   /**
    * 商户序列号
    */
   private String serialNo;

   /**
    * 微信支付V3-url前缀
    */
   private String baseUrl;

   /**
    * 获取商户的私钥文件
    *
    * @param keyPemPath
    * @return
    */
   public PrivateKey getPrivateKey(String keyPemPath) {

      InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
      if (inputStream == null) {
         throw new RuntimeException("私钥文件不存在");
      }
      return PemUtil.loadPrivateKey(inputStream);
   }

   /**
    * 获取商户的私钥文件
    *
    * @return
    */
   public PrivateKey getPrivateKey() {
      InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
      if (inputStream == null) {
         throw new RuntimeException("私钥文件不存在");
      }
      return PemUtil.loadPrivateKey(inputStream);
   }

   /**
    * 获取证书管理器实例
    *
    * @return
    */
   @Bean
   public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
      log.info("获取证书管理器实例");
      //获取商户私钥
      PrivateKey privateKey = getPrivateKey(keyPemPath);
      //私钥签名对象
      PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
      //身份认证对象
      WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
      // 使用定时更新的签名验证器,不需要传入证书
      CertificatesManager certificatesManager = CertificatesManager.getInstance();
      certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));

      return certificatesManager.getVerifier(mchId);
   }


   /**
    * 获取支付http请求对象
    *
    * @param verifier
    * @return
    */
   @Bean(name = "wxPayClient")
   public CloseableHttpClient getWxPayClient(Verifier verifier) {

      //获取商户私钥
      PrivateKey privateKey = getPrivateKey(keyPemPath);

      WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, serialNo, privateKey)
            .withValidator(new WechatPay2Validator(verifier));

      // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
      return builder.build();
   }

   /**
    * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
    */
   @Bean(name = "wxPayNoSignClient")
   public CloseableHttpClient getWxPayNoSignClient() {

      //获取商户私钥
      PrivateKey privateKey = getPrivateKey(keyPemPath);

      //用于构造HttpClient
      WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            //设置商户信息
            .withMerchant(mchId, serialNo, privateKey)
            //无需进行签名验证、通过withValidator((response) -> true)实现
            .withValidator((response) -> true);

      // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
      return builder.build();
   }
}

@Slf4j
@Configuration
public class WechatPayCommons {

   @Resource
   private Verifier verifier;
   @Resource
   private WechatPayConfig wechatPayConfig;

   public Map<String, Object> validNotifyBody(String body, Map<String, String> headers) {
      Map<String, Object> encrypt = this.getNotifyBody(body, headers);
      return WechatPayValidator.decryptFromResource(encrypt, wechatPayConfig.getApiV3Key());
   }

   public Map<String, Object> getNotifyBody(String body, Map<String, String> headers) {
      //处理通知参数
//    String body = HttpUtils.readData(request);
      log.info("回调参数:{}", body);
      // 转换为Map
      Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
      });
      String notifyId = bodyMap.get("id").toString(); // 微信的通知ID(通知的唯一ID)
      WechatPayValidator wechatPayValidator = new WechatPayValidator(verifier, notifyId, body);// 验证签名信息
      if (!wechatPayValidator.validate(headers)) {
         throw new SecurityException("通知验签失败");
      }
      log.info("通知验签成功");
      return bodyMap;
   }
}


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值