SpringBoot整合微信支付V3版本

1,155 阅读2分钟

微信支付相关参数

  1. 商户号
  2. 公众号appid
  3. 商户证书序列号
  4. APIv3秘钥
    具体申请方式,参考微信支付开发者文档

项目工程搭建

在resources目录下新建cert目录存放证书中的私钥文件

image.png

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!--项目中添加 spring-boot-starter-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--单元测试-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>

    <!-- 微信支付API v3的Apache HttpClient扩展,实现了请求签名的生成和应答签名的验证-->
     <dependency>
         <groupId>com.github.wechatpay-apiv3</groupId>
         <artifactId>wechatpay-apache-httpclient</artifactId>
         <version>0.3.0</version>
     </dependency>
 </dependencies>

如何在程序中加载私钥

image.png

@Slf4j
@Configuration
@RequiredArgsConstructor
public class CertificateConfig {

    private final WechatPayConfig wechatPayConfig;

    public PrivateKey getPrivateKey() throws IOException {

        InputStream inputStream = new ClassPathResource(wechatPayConfig.getPrivateKeyPath()
                .replace("classpath:", ""))
                .getInputStream();

        String content = new BufferedReader(new InputStreamReader(inputStream))
                .lines().collect(Collectors.joining(System.lineSeparator()));
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

}

测试私钥证书读取

@Autowired
private CertificateConfig certificateConfig;

@Test
public void testLoadPrivateKey() throws IOException {
    log.info(certificateConfig.getPrivateKey().getAlgorithm());
}

自动更新微信平台证书

/**
 * 定时获取微信签名验证器,自动获取微信平台证书(证书里面包括微信平台公钥)
 * @return
 * @throws IOException
 */
@Bean
public ScheduledUpdateCertificatesVerifier getCertificatesVerifier() throws IOException {
    //使用定时更新的签名验证器,不需要传入证书
    ScheduledUpdateCertificatesVerifier getPrivateKey = new ScheduledUpdateCertificatesVerifier(
            new WechatPay2Credentials(wechatPayConfig.getMchId(),new PrivateKeySigner(wechatPayConfig.getMchSerialNo(),getPrivateKey())),
    wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    return getPrivateKey;
}

封装httpclient

/**
 * 获取http请求对象,会自动的处理签名和验签,并进行证书自动更新
 * @param verifier
 * @return
 */
@Bean("wechatPayClient")
public CloseableHttpClient getWechatPayClient(ScheduledUpdateCertificatesVerifier verifier) throws IOException {
    return WechatPayHttpClientBuilder.create()
            .withMerchant(wechatPayConfig.getMchId(), wechatPayConfig.getMchSerialNo(), getPrivateKey())
            .withValidator(new WechatPay2Validator(verifier))
            .build();
}

Navtive下单API接口调用

image.png 构造请求参数中的必填选项

@Test
public void testNativeOrder(){
    ObjectMapper objectMapper = new ObjectMapper();
    //构建下单请求参数
    ObjectNode nativeOrder = objectMapper.createObjectNode();
    nativeOrder.put("mchid",wechatPayConfig.getMchId());
    //订单号,根据业务规则生成即可
    nativeOrder.put("out_trade_no","123456789abcdefghijk");
    nativeOrder.put("appid",wechatPayConfig.getWxPayAppid());
    nativeOrder.put("description","测试微信支付");
    nativeOrder.put("notify_url",wechatPayConfig.getCallbackUrl());
    //微信支付 支付信息结点
    ObjectNode payNode = objectMapper.createObjectNode();
    //订单支付的总金额 单位为分
    payNode.put("total",100);
    payNode.put("currency","CNY");
    nativeOrder.set("amount",payNode);

    String requestBody = nativeOrder.toString();
    log.info("微信支付请求报文为{}",requestBody);
    StringEntity entity = new StringEntity(requestBody, StandardCharsets.UTF_8);
    entity.setContentType("application/json");

    //调用native API
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
    httpPost.setHeader("Accept","application/json");
    httpPost.setEntity(entity);
    //使用封装的httpClient自动进行参数签名
    try(CloseableHttpResponse response = wechatHttpClient.execute(httpPost)) {
        //响应码
        int statusCode = response.getStatusLine().getStatusCode();
        log.info("native下单接口响应码为:{}",statusCode);
        if (HttpStatus.SC_OK == statusCode){
            String responseBody = EntityUtils.toString(response.getEntity());
            log.info("native下单接口返回内容为:{}",responseBody);
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

查询订单API接口调用

image.png

@Test
public void testNativeQuery(){
    //上一步中的商户订单号
    String outTradeNo = "123456789abcdefghijk";
    String queryUrl = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s", outTradeNo,wechatPayConfig.getMchId());
    HttpGet httpGet = new HttpGet(queryUrl);
    httpGet.setHeader("Accept","application/json");
    try (CloseableHttpResponse response = wechatHttpClient.execute(httpGet)){
        int statusCode = response.getStatusLine().getStatusCode();
        log.info("native query下单接口响应码为:{}",statusCode);
        if (HttpStatus.SC_OK == statusCode){
            String responseBody = EntityUtils.toString(response.getEntity());
            log.info("native query下单接口返回内容为:{}",responseBody);
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}