微信支付 v3

一、构造httpClient

常规操作和接口说明看微信文档就好,这里记录一下构造httpClient的参数说明,由于我是半路接手的,有些参数的来源是推测来的,实际用的时候需要甄别,还有微信文档中对于同一个变量的称呼不是完全统一,这都给对接的时候挖了坑,需要注意

参数 说明 获取方式
mchId 商户号  应该是开通微信支付以后,在管理端的商户信息 / 账户信息中可以查询微信支付商户号
mchSerialNo 商户证书序列号 在管理端的API安全中有申请API证书,一顿操作之后可以得到证书序列号
privateKey 商户私钥 / 商户API私钥 这部分我拿到的是前辈留下的证书文件,直接拿来用了,推测数据应该是上一个参数申请API证书的时候得到的,具体的操作就查看微信文档吧
certificate 微信支付平台证书 这个参数有两种获取方式,一种是接口,一种是通过官方提供的一个jar包,首次下载证书,可以使用微信支付提供的证书下载工具。同时这个参数是需要更新的,但目前我还没做更新逻辑,后续补充

有了这几个参数,就可以构造httpClient了

二、微信支付平台证书

参考这个项目:https://github.com/wechatpay-apiv3/CertificateDownloader

三、示例代码

package com.genesysinfo.umss.core.modules.wxpay.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.genesysinfo.umss.core.common.constant.CommonConstant;
import com.genesysinfo.umss.core.modules.wxpay.dto.NativeOrderDTO;
import com.genesysinfo.umss.core.modules.wxpay.dto.SUCVDTO;
import com.genesysinfo.umss.core.modules.wxpay.dto.WXPlatCertificate;
import com.genesysinfo.umss.core.modules.wxpay.service.WxPayService;
import com.genesysinfo.umss.core.modules.wxpay.utils.AesUtil;
import com.genesysinfo.umss.core.modules.wxpay.utils.QRCodeUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * 微信支付平台证书更新指引 https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/zsgxguide.shtml
 * @author coco
 * @date 2021/12/29
 */
@Api(tags = "微信支付")
@RestController
@RequestMapping("/wxpay")
public class WxPayController {

    @Autowired
    private WxPayService wxPayService;

    // 1.创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
    @GetMapping("/setup")
    public ResponseEntity setup() throws IOException, URISyntaxException {
        // 你的商户私钥
        String privateKey = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("static/apiclient_key.pem").toURI())), "utf-8");
        // 商户API证书
        //String certificate = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("static/apiclient_cert.pem").toURI())), "utf-8");
        // 微信支付平台证书
        String certificate = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("static/wechatpay_66666666666666.pem").toURI())), "utf-8");

        Map map = new HashMap<>();
        // 商户号
        map.put("mchId","000000000000000000000");
        // 商户证书序列号
        map.put("mchSerialNo","1111111111111111111111111111111111");
        map.put("privateKey",privateKey);
        map.put("certificate",certificate);

        return new ResponseEntity<>(map, HttpStatus.OK);
    }


    /**
     * 生成二维码
     * @param content
     * @param response
     */
    @ApiOperation(value = "生成二维码")
    @GetMapping("/genQrcode")
    public void genQrcode(@RequestParam(name = "content") String content, HttpServletResponse response){
        try {
            QRCodeUtil.createCodeToOutputStream(content, response.getOutputStream());
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取平台证书列表
     * @param param
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    @ApiOperation(value = "获取平台证书列表", notes = "https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml")
    @PostMapping("/getPlatCertList")
    public ResponseEntity getPlatCertList(@Validated @RequestBody SUCVDTO param) throws IOException, URISyntaxException {
         CloseableHttpClient httpClient;
         ScheduledUpdateCertificatesVerifier verifier;

        // 商户号
        String mchId = param.getMchId();
        // 商户证书序列号
        String mchSerialNo = param.getMchSerialNo();
        // API V3密钥
        String apiV3Key = param.getApiV3Key();
        // 你的商户私钥
        String privateKey = param.getPrivateKey();

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);

        // 使用定时更新的签名验证器,不需要传入证书
        verifier = new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        URIBuilder uriBuilder = new URIBuilder(CommonConstant.URL_WX_CERTIFICATES);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
            return new ResponseEntity<>(JSONObject.parseObject(EntityUtils.toString(entity)), HttpStatus.OK);
        } finally {
            response.close();
            httpClient.close();
        }
    }


    /**
     * 解密平台证书
     * @param wxPlatCertificate
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    @ApiOperation(value = "解密平台证书", notes = "https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml")
    @PostMapping("/decryptPlatCert")
    public ResponseEntity getCertList(@Validated @RequestBody WXPlatCertificate wxPlatCertificate) throws IOException {

        // API V3密钥
        String apiV3Key = wxPlatCertificate.getApiV3Key();

        try {
            // 解密返回数据,得到证书
            AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
            String publicKey =  aesUtil.decryptToString(wxPlatCertificate.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8),
                    wxPlatCertificate.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8),
                    wxPlatCertificate.getEncrypt_certificate().getCiphertext());

            return new ResponseEntity<>(publicKey, HttpStatus.OK);
        } catch (GeneralSecurityException e) {
            return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_GATEWAY);
        }
    }


    /**
     * 获取解密的启用时间最晚的平台证书
     * @param param
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    @ApiOperation(value = "获取解密的启用时间最晚的平台证书")
    @PostMapping("/getDecryptedPlatCert")
    public ResponseEntity getDecryptedPlatCert(@Validated @RequestBody SUCVDTO param) throws IOException, URISyntaxException {
        CloseableHttpClient httpClient;
        ScheduledUpdateCertificatesVerifier verifier;

        // 商户号
        String mchId = param.getMchId();
        // 商户证书序列号
        String mchSerialNo = param.getMchSerialNo();
        // API V3密钥
        String apiV3Key = param.getApiV3Key();
        // 你的商户私钥
        String privateKey = param.getPrivateKey();

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);

        // 使用定时更新的签名验证器,不需要传入证书
        verifier = new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        URIBuilder uriBuilder = new URIBuilder(CommonConstant.URL_WX_CERTIFICATES);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
            JSONObject js = JSONObject.parseObject(EntityUtils.toString(entity));
            List respCertList = JSONArray.parseArray(js.get("data").toString(),WXPlatCertificate.class);

            // 选用证书启用时间最晚的证书
            List wx = respCertList.stream()
                    .sorted(Comparator.comparing((WXPlatCertificate w) -> w.getEffective_time()))
                    .collect(Collectors.toList());
            WXPlatCertificate wxPlatCertificate = wx.get(0);
            // 解密返回数据,得到证书
            AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
            String publicKey =  aesUtil.decryptToString(wxPlatCertificate.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8),
                    wxPlatCertificate.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8),
                    wxPlatCertificate.getEncrypt_certificate().getCiphertext());

            return new ResponseEntity<>(publicKey, HttpStatus.OK);
        } catch (GeneralSecurityException e) {
            int statusCode = response.getStatusLine().getStatusCode();
            MultiValueMap params = new LinkedMultiValueMap<>();
            for (Header header : response.getAllHeaders()) {
                params.put(header.getName(), Collections.singletonList(header.getValue()));
            }
            return new ResponseEntity<>(e.getMessage(), params, statusCode);
        } finally {
            response.close();
            httpClient.close();
        }
    }


    /**
     * 发起native下单
     * @param dto
     * @return
     * @throws Exception
     */
    @ApiOperation(value = "发起native下单", notes = "https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml")
    @PostMapping("/createNativeOrder")
    public ResponseEntity createNativeOrder(@Validated @RequestBody NativeOrderDTO dto) throws Exception{
        CloseableHttpClient httpClient;
        if (StringUtils.isNotEmpty(dto.getWxPayBase().getApiV3Key())){
            httpClient = createHttpClientWithVerifier(dto.getWxPayBase().getMchId(),
                    dto.getWxPayBase().getMchSerialNo(),
                    dto.getWxPayBase().getPrivateKey(),
                    dto.getWxPayBase().getApiV3Key());
        }else{
            httpClient = createHttpClientWithCertificate(dto.getWxPayBase().getMchId(),
                    dto.getWxPayBase().getMchSerialNo(),
                    dto.getWxPayBase().getPrivateKey(),
                    dto.getWxPayBase().getCertificate());
        }
        String bodyStr = filterJavaBean(dto);
        HttpPost httpPost = new HttpPost(CommonConstant.URL_WX_PAY_NATIVE);
        //转两次过滤掉一些为null的参数
        StringEntity entity = new StringEntity(JSONObject.toJSONString(JSONObject.parseObject(bodyStr)),"utf-8");
        entity.setContentType(CommonConstant.JSON);
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", CommonConstant.JSON);

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

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            //处理成功
            if (statusCode == 200) {
                return new ResponseEntity<>(JSONObject.parseObject(EntityUtils.toString(response.getEntity())), HttpStatus.OK);
                //处理成功,无返回Body
            } else if (statusCode == 204) {
                return new ResponseEntity<>(new JSONObject(), HttpStatus.NO_CONTENT);
            } else {
                MultiValueMap params = new LinkedMultiValueMap<>();
                for (Header header : response.getAllHeaders()) {
                    params.put(header.getName(), Collections.singletonList(header.getValue()));
                }
                return new ResponseEntity<>(JSONObject.parseObject(EntityUtils.toString(response.getEntity())), params, statusCode);
            }
        } finally {
            response.close();
            httpClient.close();
        }
    }

    /**
     * 过滤参数
     * @param dto
     * @param 
     * @return
     * @throws JsonProcessingException
     */
    private  String filterJavaBean(T dto) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode jsonNodes = objectMapper.valueToTree(dto);
        jsonNodes.remove("wxPayBase");
        return objectMapper.writeValueAsString(jsonNodes);
    }


    /**
     * 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
     * @param mchId
     * @param mchSerialNo
     * @param privateKey
     * @param certificate
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    private CloseableHttpClient createHttpClientWithCertificate(String mchId, String mchSerialNo,String privateKey, String certificate) {
        CloseableHttpClient httpClient;

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
        X509Certificate wechatPayCertificate = PemUtil.loadCertificate(
                new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8)));

        ArrayList listCertificates = new ArrayList<>();
        listCertificates.add(wechatPayCertificate);

        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withWechatPay(listCertificates)
                .build();

        return httpClient;
    }

    /**
     * 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
     * 使用定时更新的签名验证器,不需要传入证书
     * @param mchId
     * @param mchSerialNo
     * @param privateKey
     * @param apiV3Key
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    private CloseableHttpClient createHttpClientWithVerifier(String mchId, String mchSerialNo,String privateKey, String apiV3Key) {
        CloseableHttpClient httpClient;
        ScheduledUpdateCertificatesVerifier verifier;

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
        // 使用定时更新的签名验证器,不需要传入证书
        verifier = new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        return httpClient;
    }



}

问题记录:

记一次“微信支付开发API V3”接口调用,解决“应答的微信支付签名验证失败”_fyh60318647的博客-CSDN博客_应答的微信支付签名验证失败https://blog.csdn.net/fyh60318647/article/details/112689022

你可能感兴趣的:(微信,微信)