java APP微信支付API V3

因项目需要,有用到微信支付,这里对java微信支付的开发流程和注意事项到做一次记录,以遍后面有需要的时候翻阅,方便回顾。

快速开发一个功能,需要了解整个开发过程以及开发过程中需要注意的点,开发的功能才能足够安全高效

如下是微信支付官方给出的微信支付V3流程图:

java APP微信支付API V3_第1张图片

微信支付V3开发流程阅读下面代码时,需要对照当前流程):

1、首先是接入前准备

  • 选择接入模式
  • 参数申请
  • 配置API key
  • 下载并配置商户证书
  • App支付页面规范

具体参考微信支付V3接入前准备:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml

2、开发准备

搭建和配置开发环境,根据自身开发语言,选择对应的开发库构建项目,笔者这里使用的是Java,所以使用的是wechatpay-apache-httpclient,只要将对应的maven依赖引入项目即可

        
            com.github.wechatpay-apiv3
            wechatpay-apache-httpclient
            0.2.1
        

,最新依赖版本可参考https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

3、API接口列表

需要注意的是接口的适用接入模式,请求参数,接口地址,返回参数一定要跟微信官方文档的一致。

4、接口规则

  • 签名生成:微信支付Api v3 key要求商户对请求进行签名,微信支付会在收到请求后进行签名的验证。如果验证不通过,微信支付Api v3会拒绝处理请求。返回状态401
  • 签名验证:微信支付会在回调的Http头部中包括回调报文的签名,以确保回调是由微信支付发送
  • 证书和回调报文加密解密:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密
  • 平台证书更新:由于平台证书存在有效期,需要定时更换平台证书以确保交易安全

微信支付V3java 代码:

配置信息

wechat:
  appId: ###
  mchId: ###
  aesKey: ###
  unifiedOrder:
    url: https://api.mch.weixin.qq.com/v3/pay/transactions/app
  notify:
    url: ###
  queryOrder:
    url: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s
  certificates:
    url: https://api.mch.weixin.qq.com/v3/certificates
package com.hjy.ft.config;

import com.hjy.pay.WxpayV3Util;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.*;
import java.security.cert.X509Certificate;

/**
 * @version 1.0
 * @className WeChatStartUpRunner
 * @description 程序启动后加载
 * @since 2021/4/19 14:47
 */
@Component
@Order(value = 1)
public class WeChatStartUpRunner implements CommandLineRunner {

    /**
     * 微信商户号
     */
    @Value("${wechat.mchId}")
    private String wechatMchId;

    /**
     * 应用id
     */
    @Value("${wechat.appId}")
    private String wechatAppId;

    /**
     * API v3密钥
     */
    @Value("${wechat.aesKey}")
    private String wechatAesKey;

    /**
     * 统一下单地址
     */
    @Value("${wechat.unifiedOrder.url}")
    private String unifiedOrderUrl;

    /**
     * 异步接收微信支付 回调地址
     */
    @Value("${wechat.notify.url}")
    private String notifyUrl;

    /**
     * 微信订单查询地址
     */
    @Value("${wechat.queryOrder.url}")
    private String queryOrderUrl;

    /**
     * 获取平台证书列表
     */
    @Value("${wechat.certificates.url}")
    private String certificatesUrl;

    @Override
    public void run(String... args) throws Exception {
        WxpayV3Util.wechatMchId = wechatMchId;
        WxpayV3Util.wechatAppId = wechatAppId;
        WxpayV3Util.wechatAesKey = wechatAesKey;
        WxpayV3Util.unifiedOrderUrl = unifiedOrderUrl;
        WxpayV3Util.notifyUrl = notifyUrl;
        WxpayV3Util.queryOrderUrl = queryOrderUrl;
        X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(readPayPath("apiclient_cert.pem")));
        WxpayV3Util.wechatCertificatePath = certificate;
        //获取证书序列号
        WxpayV3Util.serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
        WxpayV3Util.wechatKeyPath = PemUtil.loadPrivateKey(new FileInputStream(readPayPath("apiclient_key.pem")));
        WxpayV3Util.certificatesUrl = certificatesUrl;
        //获取平台证书(由于微信证书存在有限期限制,微信支付会不定期更换平台证书以确保交易安全)
        WxpayV3Util.certificateMap = WxpayV3Util.refreshCertificate();
    }

    /**
     * 读取文件地址,适用发布环境
     * @param fileName (文件路径)
     * @return 临时文件路径
     */
    public String readPayPath(String fileName) {
        String url = null;
        //返回读取指定资源的输入流
        InputStream is = this.getClass().getResourceAsStream("/wxpay/"+fileName);
        //InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/alipay/"+fileName);
        String path = System.getProperty("user.dir");
        //create folder
        String dirPath = path + File.separator + "uploadWxPayFiles";
        File dir = new File(dirPath);
        dir.mkdirs();
        //create file
        String filePath = dirPath + File.separator + fileName;
        File file = new File(filePath);
        //判断文件是否存在
        if (!file.exists()) {
            try {
                file.createNewFile();
                //文件不存,创建流输入数据到新文件
                inputStreamToFile(is, file);
            } catch (IOException e) {
                e.printStackTrace();
            }
            url = filePath;
        }else{
            url = filePath;
        }
        return url;
    }

    public void inputStreamToFile(InputStream ins, File file) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[1024];
            while ((bytesRead = ins.read(buffer, 0, 1024)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

工具类

package com.hjy.pay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @version 1.0
 * @className WxpayV3Util
 * @description TODO
 */
public class WxpayV3Util {

    private static Logger logger = LoggerFactory.getLogger(WxpayV3Util.class);

    //应用id
    public static String wechatAppId;
    //商户id
    public static String wechatMchId;
    //API v3密钥
    public static String wechatAesKey;
    //统一下单地址
    public static String unifiedOrderUrl;
    //微信回调地址
    public static String notifyUrl;
    //订单查询地址
    public static String queryOrderUrl;
    //证书序列号
    public static String serialNo;
    //微信证书地址
    public static X509Certificate wechatCertificatePath;
    //微信证书私钥地址
    public static PrivateKey wechatKeyPath;
    //平台证书更换
    public static String certificatesUrl;

    public static Map certificateMap = new ConcurrentHashMap<>();

    /**
     * 获取请求文体
     * @param request
     * @return
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            reader.close();
        }
        return sb.toString();
    }

    /**
     * 回调数据解密
     * @param associatedData
     * @param nonce
     * @param ciphertext
     * @return
     */
    public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(wechatAesKey.getBytes("UTF-8"), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8"));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes("UTF-8"));

            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 证书更新
     * @return
     */
    public static Map refreshCertificate(){
        try {
            String url = WxpayV3Util.certificatesUrl;
            //设置参数
            CloseableHttpClient httpClient = HttpClients.createDefault();
            //创建实例方法
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", getToken("GET",url,""));
            HttpResponse response = httpClient.execute(httpGet);
            String result = "";
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity(),"UTF-8");
            }
            JSONObject jsonObject = JSONObject.parseObject(result);
            List certificateList = JSON.parseArray(jsonObject.getString("data"),CertificateVO.class);
            //JSONObject jsonObject = PayRequestUtils.wechatHttpGet(StaticParameter.wechatCertificatesUrl,"", JSONObject.class);
            Date newestTime = null;
            CertificateVO newestCertificate = null;
            for (CertificateVO certificate:certificateList) {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                if (newestTime == null){
                    newestCertificate = certificate;
                    newestTime = formatter.parse(certificate.getEffective_time());
                }else{
                    Date effectiveTime = formatter.parse(certificate.getEffective_time());
                    //证书启用时间大于最新时间
                    if(effectiveTime.getTime() > newestTime.getTime()){
                        //更换证书
                        newestCertificate = certificate;
                    }
                }
            }
            CertificateVO.EncryptCertificate encryptCertificate = newestCertificate.getEncrypt_certificate();
            String publicKey = decryptResponseBody(encryptCertificate.getAssociated_data(),encryptCertificate.getNonce(),encryptCertificate.getCiphertext());
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            //获取证书
            ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes("UTF-8"));
            X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
            //保存平台证书及序列号
            Map certificateMap = new ConcurrentHashMap<>();
            // 清理HashMap
            certificateMap.clear();
            // 放入证书
            certificateMap.put(newestCertificate.getSerial_no(), certificate);
            return certificateMap;
        }catch (Exception e){
            logger.error("微信平台证书更新报错:",e);
        }
        return null;
    }

    /**
     * 签名串
     * @param method 请求方式post | get
     * @param url app 统一下单API
     * @param body 请求内容
     */
    public static String getToken(String method, String url,String body){
        //时间戳
        Long timestamp = System.currentTimeMillis()/1000;
        //随机串
        String nonceStr = UUID.randomUUID().toString().replace("-","");
        //签名值
        String signature = getSign(method,url,body,timestamp,nonceStr);
        final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        // 生成token
        return String.format(TOKEN_PATTERN,
                WxpayV3Util.wechatMchId,
                nonceStr, timestamp, WxpayV3Util.serialNo, signature);
    }

    /**
     * 对签名串进行SHA256 with RSA签名,并进行Base64编码得到签名值
     * @param method 请求方法  GET  POST 等
     * @param canonicalUrl 请求地址
     * @param body 请求体 GET 为 "" POST 为JSON
     * @param timestamp 时间戳
     * @param nonceStr 随机串
     */
    public static String getSign(String method, String canonicalUrl, String body, long timestamp, String nonceStr){
        try{
            URL url = new URL(canonicalUrl);
            String signUrl;
            if ("GET".equals(method)&&url.getQuery()!=null) {
                signUrl = url.getPath() + "?" + url.getQuery();
            }else{
                signUrl = url.getPath();
            }
            //有序切割
            String signatureStr = Stream.of(method, signUrl, String.valueOf(timestamp), nonceStr, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(WxpayV3Util.wechatKeyPath);
            sign.update(signatureStr.getBytes("UTF-8"));
            return Base64Utils.encodeToString(sign.sign());
        }catch(Exception ex){
            logger.error("签名值出错:",ex);
        }
        return null;
    }

}

生成订单信息

     @ApiOperation(value = "生成微信支付订单(直连模式)", notes = "生成微信支付订单(直连模式)")
     @PostMapping("/get_wechat_pay_v3_order_info")
     public JsonResponse wechatPayV3OrderInfo(WechatModel wechatModel) throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //请求URL
        HttpPost httpPost = new HttpPost(WxpayV3Util.unifiedOrderUrl);
        //订单号
        Long orderNumber = 0L;
        //订单总额(单位:分)
        Integer total = new BigDecimal(wechatModel.getTotalAmount()).multiply(new BigDecimal(100)).intValue();
        //业务逻辑处理
        //下单对象
        Transaction transaction = new Transaction(WxpayV3Util.wechatAppId,WxpayV3Util.wechatMchId,wechatModel.getDescription(), String.valueOf(orderNumber),WxpayV3Util.notifyUrl,total,"CNY");
        String jsonStr = JSON.toJSONString(transaction);
        httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
        httpPost.setHeader("Accept", "application/json");
        //Authorization 头:认证类型与签名信息组成 | 请求数据做加密,防止被拦截后改动
        String token = WxpayV3Util.getToken("POST",WxpayV3Util.unifiedOrderUrl,jsonStr);
        httpPost.setHeader("Authorization", token);
        if(null != jsonStr){
            StringEntity entity = new StringEntity(jsonStr,"UTF-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
        }
        //完成签名并执行请求
        HttpResponse response = httpClient.execute(httpPost);
        //获取返回状态
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //处理成功
            String result = EntityUtils.toString(response.getEntity(),"UTF-8");
            JSONObject object = JSONObject.parseObject(result);
            //获取预付单
            String prepayId = object.getString("prepay_id");
            //生成签名
            Long timestamp = System.currentTimeMillis()/1000;
            String nonceStr = UUID.randomUUID().toString().replace("-","");
            String paySign = appPaySign(timestamp,nonceStr,prepayId);
            Map payMap = new HashMap();
            payMap.put("timestamp",timestamp);
            payMap.put("nonceStr",nonceStr);
            payMap.put("prepayId",prepayId);
            payMap.put("paySign",paySign);
            return new JsonResponse(1,"success",payMap);
        } else if (statusCode == 204) { //处理成功,无返回Body
            return new JsonResponse(1,"success",null);
        } else {
            return new JsonResponse(1,"error",null);
        }

    }
    /**
     * 签名串
     * @param method 请求方式post | get
     * @param url app 统一下单API
     * @param body 请求内容
     */
    public static String getToken(String method, String url,String body){
        //时间戳
        Long timestamp = System.currentTimeMillis()/1000;
        //随机串
        String nonceStr = UUID.randomUUID().toString().replace("-","");
        //签名值
        String signature = getSign(method,url,body,timestamp,nonceStr);
        final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        // 生成token
        return String.format(TOKEN_PATTERN,
                WxpayV3Util.wechatMchId,
                nonceStr, timestamp, WxpayV3Util.serialNo, signature);
    }
    /**
     * 对签名串进行SHA256 with RSA签名,并进行Base64编码得到签名值
     * @param method 请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 请求地址
     * @param body 请求体 GET 为 "" POST 为JSON
     * @param timestamp 时间戳
     * @param nonceStr 随机串
     */
    public static String getSign(String method, String canonicalUrl, String body, long timestamp, String nonceStr){
        try{
            URL url = new URL(canonicalUrl);
            String signUrl;
            if ("GET".equals(method)&&url.getQuery()!=null) {
                signUrl = url.getPath() + "?" + url.getQuery();
            }else{
                signUrl = url.getPath();
            }
            //有序切割
            String signatureStr = Stream.of(method, signUrl, String.valueOf(timestamp), nonceStr, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(WxpayV3Util.wechatKeyPath);
            sign.update(signatureStr.getBytes("UTF-8"));
            return Base64Utils.encodeToString(sign.sign());
        }catch(Exception ex){
            logger.error("签名值出错:",ex);
        }
        return null;
    }

    /**
     * 生成带签名支付信息
     * @param timestamp 时间戳
     * @param nonceStr 随机数
     * @param prepayId 预付单
     * @return 支付信息
     * @throws Exception
     */
    public String appPaySign(long timestamp, String nonceStr, String prepayId) throws Exception {
        String signatureStr = Stream.of(WxpayV3Util.wechatAppId, String.valueOf(timestamp), nonceStr, prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(WxpayV3Util.wechatKeyPath);
        sign.update(signatureStr.getBytes("UTF-8"));
        return Base64Utils.encodeToString(sign.sign());
    }

微信异步通知

    @ApiOperation(value = "微信异步通知", notes = "微信异步通知")
    @PostMapping(value = "/wechat_callback")
    public Map wechatCallback(HttpServletRequest request){
        Map map = new HashMap<>(2);
        try {
            //微信返回的请求体
            String body = WxpayV3Util.getRequestBody(request);
            //如果验证签名序列号通过
            if (verifiedSign(request,body)){
                //微信支付通知实体类
                PayNotify payNotify = JSONObject.parseObject(body,PayNotify.class);
                //如果支付成功
                if ("TRANSACTION.SUCCESS".equals(payNotify.getEvent_type())){
                    //通知资源数据
                    PayNotify.Resource resource = payNotify.getResource();
                    //解密后资源数据
                    String notifyResourceStr = WxpayV3Util.decryptResponseBody(resource.getAssociated_data(),resource.getNonce(),resource.getCiphertext());
                    //通知资源数据对象
                    NotifyResource notifyResourceVO = JSONObject.parseObject(notifyResourceStr,NotifyResource.class);
                    String outTradeNo = notifyResourceVO.getOut_trade_no();
                    //逻辑实现
                    //do something

                }else{
                    logger.info("微信返回支付错误摘要:{}",payNotify.getSummary());
                    //updateStatus(outTradeNo,RechargeUtil.FAIL_PAY);
                }
                //通知微信正常接收到消息,否则微信会轮询该接口
                map.put("code","SUCCESS");
                map.put("message","成功");
                return map;
            }
        } catch (IOException e) {
            logger.error("微信回调错误:"+e);
        }
        return map;
    }
    /**
     * 获取请求报文
     * @param request
     * @return
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            reader.close();
        }
        return sb.toString();
    }
    /**
     * 验签
     * @param request request请求
     * @param body 微信返回请求体
     * @return true,false
     */
    public boolean verifiedSign(HttpServletRequest request,String body){
        try{
            //微信返回的证书序列号
            String serialNo = request.getHeader("Wechatpay-Serial");
            //微信返回的随机字符串
            String nonceStr = request.getHeader("Wechatpay-Nonce");
            //微信返回的时间戳
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            //微信返回的签名
            String wechatSign = request.getHeader("Wechatpay-Signature");
            //组装签名字符串
            String signStr = Stream.of(timestamp, nonceStr, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            //判断证书序列号是否相同
            if(WxpayV3Util.certificateMap.isEmpty() || !WxpayV3Util.certificateMap.containsKey(serialNo)){
                //刷新证书
                WxpayV3Util.refreshCertificate();
            }
            X509Certificate certificate = WxpayV3Util.certificateMap.get(serialNo);
            //获取失败 验证失败
            if (certificate == null){
                return false;
            }
            //SHA256withRSA签名
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(certificate);
            signature.update(signStr.getBytes());
            //返回验签结果
            return signature.verify(Base64Utils.decodeFromString(wechatSign));
        }catch (Exception ex){
            logger.error("验签报错:",ex);
        }
        return false;
    }
    /**
     * 解密
     * @param associatedData 附加数据包
     * @param nonce 加密使用的随机串
     * @param ciphertext Base64编码后的密文
     * @return
     */
    public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(wechatAesKey.getBytes("UTF-8"), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8"));
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes("UTF-8"));
            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

订单查询:

    @ApiOperation(value = "商户订单号查询", notes = "商户订单号查询")
    @GetMapping(value = "/query_transactions_out_trade_no")
    public JsonResponse queryTransactionsOutTradeNo(String outTradeNo){
        String result = "";
        try{
            String url = String.format(WxpayV3Util.queryOrderUrl,outTradeNo,WxpayV3Util.wechatMchId);
            //设置参数
            CloseableHttpClient httpClient = HttpClients.createDefault();
            //创建实例方法
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", WxpayV3Util.getToken("GET",url,""));
            HttpResponse response = httpClient.execute(httpGet);
            //状态码为200,为正常返回
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity(),"UTF-8");
            }
            if(StringUtils.isNotEmpty(result)){
                JSONObject jsonObject = JSONObject.parseObject(result);
                String tradeState = jsonObject.getString("trade_state");
                String tradeStateDesc = jsonObject.getString("trade_state_desc");
                return new JsonResponse(1,tradeState,tradeStateDesc);
            }
        }catch(Exception ex){
            logger.error("商户订单号查询错误",ex);
        }
        return new JsonResponse(1,"NOTFOUND","没有找到该订单");
    }

证书更新:

    /**
     * 证书更新
     * @return
     */
    public static Map refreshCertificate(){
        try {
            String url = WxpayV3Util.certificatesUrl;
            //设置参数
            CloseableHttpClient httpClient = HttpClients.createDefault();
            //创建实例方法
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", getToken("GET",url,""));
            HttpResponse response = httpClient.execute(httpGet);
            String result = "";
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity(),"UTF-8");
            }
            JSONObject jsonObject = JSONObject.parseObject(result);
            List certificateList = JSON.parseArray(jsonObject.getString("data"),CertificateVO.class);
            //JSONObject jsonObject = PayRequestUtils.wechatHttpGet(StaticParameter.wechatCertificatesUrl,"", JSONObject.class);
            Date newestTime = null;
            CertificateVO newestCertificate = null;
            for (CertificateVO certificate:certificateList) {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                if (newestTime == null){
                    newestCertificate = certificate;
                    newestTime = formatter.parse(certificate.getEffective_time());
                }else{
                    Date effectiveTime = formatter.parse(certificate.getEffective_time());
                    //证书启用时间大于最新时间
                    if(effectiveTime.getTime() > newestTime.getTime()){
                        //更换证书
                        newestCertificate = certificate;
                    }
                }
            }
            CertificateVO.EncryptCertificate encryptCertificate = newestCertificate.getEncrypt_certificate();
            String publicKey = decryptResponseBody(encryptCertificate.getAssociated_data(),encryptCertificate.getNonce(),encryptCertificate.getCiphertext());
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            //获取证书
            ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes("UTF-8"));
            X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
            //保存平台证书及序列号
            Map certificateMap = new ConcurrentHashMap<>();
            // 清理HashMap
            certificateMap.clear();
            // 放入证书
            certificateMap.put(newestCertificate.getSerial_no(), certificate);
            return certificateMap;
        }catch (Exception e){
            logger.error("微信平台证书更新报错:",e);
        }
        return null;
    }

遇到的问题:

前端使用的是uniapp,经调试,支付成功或者失败,微信支付返回的只有状态信息,没有包含支付信息,那就不能验证微信支付Api v3流程图中19步骤的流程,只能直接跳转到支付成功或者支付失败页面。

解决方法:后端定时查询商户待支付订单记录,验证支付订单状态

参考地址:https://blog.csdn.net/qq_39706128/article/details/111558994

你可能感兴趣的:(java,spring,后端)