微信v3支付和回调数据校验

导入微信官方推荐maven jar
    <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
    </dependency>
获取微信证书,并生成前端调起支付需要的参数(示例是合单支付)
    /**
     * 商户号
     * url https://pay.weixin.qq.com/index.php/core/account/info
     */
    private static final String merchantId = "xxxxx";

    /**
     * 商户证书序列号。
     * url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
     */
    private static final String merchantSerialNumber = "xxxxx";

    /**
     * API V3密钥
     * url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
     */
    private static final String apiV3Key = "xxxxx";

    /**
     * 商户API V3私钥证书地址(从微信商户平台下载,放到本地的地址)
     * url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
     */
    private static final String privateKeyPath = "xxxxx";
    private PrivateKey merchantPrivateKey;
    private ScheduledUpdateCertificatesVerifier verifier;
    private CloseableHttpClient httpClient;
  	@PostConstruct
  	//初始化微信证书
    private void initHttpClient() {
        log.info("--------微信证书v3初始化开始--------");
        try (FileInputStream inputStream = new FileInputStream(privateKeyPath)){
            merchantPrivateKey = PemUtil.loadPrivateKey(inputStream);
            verifier = new ScheduledUpdateCertificatesVerifier(
                    new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                    apiV3Key.getBytes(StandardCharsets.UTF_8));
            httpClient =  WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier))
                    .build();
            log.info("--------微信证书v3初始化成功--------");
        }catch (IOException e){
            log.error("微信证书v3初始化失败,证书地址:{}",privateKeyPath,e);
        }
    }
    /**
     * 微信合单支付
     * @return
     */
    public Map<String, String> combineTransactionsApp(){
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/combine-transactions/app");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        JSONObject requestBody = new JSONObject();
        requestBody.put("combine_appid","xxxxx");
        requestBody.put("combine_mchid", merchantId);
        requestBody.put("combine_out_trade_no", "xxxxx");

        JSONArray subOrders = new JSONArray();
        JSONObject subOrderJsonObject = new JSONObject();
        subOrderJsonObject.put("mchid",merchantId);
        subOrderJsonObject.put("attach","合单支付");

        JSONObject amountJsonObject = new JSONObject();
        amountJsonObject.put("total_amount",100);
        amountJsonObject.put("currency","CNY");
        subOrderJsonObject.put("amount",amountJsonObject);

        subOrderJsonObject.put("out_trade_no", "xxxxx");
        subOrderJsonObject.put("sub_mchid", "xxxxx");
        subOrderJsonObject.put("description","商品购买");
        subOrders.add(subOrderJsonObject);
        requestBody.put("sub_orders",subOrders);
        requestBody.put("notify_url", "xxxxx");
        String requestJsonString = JSONObject.toJSONString(requestBody);
        log.info("请求参数:{}",requestJsonString);
        httpPost.setEntity(new StringEntity(requestJsonString, "UTF-8"));
        try (CloseableHttpResponse response = httpClient.execute(httpPost)){
            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("response:{}",bodyAsString);
            JSONObject responseJson = JSONObject.parseObject(bodyAsString);
            String prepayId = Optional.ofNullable(responseJson.getString("prepay_id"))
                    .orElseThrow(() -> new BestPayException(responseJson.getString("message")));
            return this.buildSign(prepayId);
        } catch (IOException e) {
            log.error("微信合单支付V3接口请求失败",e);
        }
        return null;
    }
      /**
     * 生成返回给前端的数据
     * @param prepayId  预支付id
     * @return
     */
    public Map<String, String> buildSign(String prepayId) {
        Map<String, String> resultMap = new LinkedHashMap<>(8);
        resultMap.put("appid", "xxxxx");
        resultMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        resultMap.put("noncestr", "xxxxx");
        resultMap.put("prepayid", prepayId);
        resultMap.put("sign", this.signSHA256(resultMap,merchantPrivateKey));
        resultMap.put("partnerid", merchantId);
        resultMap.put("package", "Sign=WXPay");
        return resultMap;
    }
    /**
     * 微信v3支付签名
     */
    public String signSHA256(Map<String, String> params, PrivateKey privateKey) {
        StringBuilder content = new StringBuilder();
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (StringUtils.isNotEmpty(value)) {
                content.append(value).append("\n");
            }
        }
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(content.toString().getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            log.error("sha256_HMAC加密失败",e);
        }
        return null;
    }
回调数据校验
 	@PostMapping(value = "wxMergePayNotify")
    public Map<String, String> wxMergePayNotify(HttpServletRequest request) {
        //给微信的回应
        Map<String, String> result = new HashMap<>(2);
        //解密数据
        String data = this.payRequestDecryption(request);
        if (data == null){
            result.put("code","FAILED");
            result.put("message","失败");
            return result;
        }
        log.info("微信支付处理后的数据data={}", data);
        JSONObject response = JSONObject.parseObject(data);
        //TODO .......业务逻辑处理
        log.info("微信支付回调成功");
        result.put("code","SUCCESS");
        result.put("message","成功");
        return result;
    }
       /**
     * v3支付回调数据校验
     */
    public String payRequestDecryption(HttpServletRequest request){
        //校验签名
        String data = this.verifySign(request);
        if (data == null){
            return null;
        }
        JSONObject jsonObject = JSONObject.parseObject(data);
        //重复通知不处理,redis示例
        String id = jsonObject.getString("id");
        String key = "WXV3PayId:" + id;
        if (Objects.equals(redisUtil.get(key),id)){
            log.info("重复通知不处理:{}",data);
            return null;
        }
        //10分钟以内的请求不处理
        redisUtil.set(key,1,60*10);
        //非支付通知不处理
        String eventType = jsonObject.getString("event_type");
        String resourceType = jsonObject.getString("resource_type");
        if (!Objects.equals(eventType,"TRANSACTION.SUCCESS") && Objects.equals(resourceType,"encrypt-resource")){
            log.info("不是支付通知不处理:{}",data);
            return null;
        }
        //参数解密
        JSONObject resource = jsonObject.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String nonce = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        String result = null;
        try {
            result = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
        } catch (GeneralSecurityException e) {
            log.error("微信v3解密异常",e);
        }
        return result;
    }

	public static final String REQUEST_ID = "Request-ID";
    public static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
    public static final String WECHAT_PAY_SIGNATURE = "Wechatpay-Signature";
    public static final String WECHAT_PAY_TIMESTAMP = "Wechatpay-Timestamp";
    public static final String WECHAT_PAY_NONCE = "Wechatpay-Nonce";
    /**
     * 签名校验
     * url https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_13.shtml
     */
    public String verifySign(HttpServletRequest request){
        //检查header
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
        for (String headerName : headers) {
            if (request.getHeader(headerName) == null) {
                log.info("{} is null", headerName);
                return null;
            }
        }
        //检查时间
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
        if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
            log.info("超过应答时间");
            return null;
        }
        //获取微信返回的参数
        String data;
        try {
            data = request.getReader().lines().collect(Collectors.joining());
        } catch (IOException e) {
            log.error("获取微信V3回调参数失败",e);
            return null;
        }
        //校验签名
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        String message =  timestamp + "\n" + nonce + "\n" + data + "\n";
        String serial = request.getHeader(WECHAT_PAY_SERIAL);
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
            log.info("签名校验失败");
            return null;
        }
        return data;
    }
上一篇:HttpRunner3.x 学习2-名词解释(step/case/suite)


下一篇:ETCD 十 分布式事务