微信扫码支付

本文档展示了如何使用微信支付APIv3接口进行扫码支付,包括证书实体类、解密数据对象、支付通知解密及工具类的详细实现。还提供了配置类以获取HTTP客户端,并演示了关闭订单和生成二维码的工具方法。同时,包含了验签、解密响应体等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

微信扫码支付用的是apiv3接口,点击查看微信扫码支付官方文档

  1. 编写微信支付封装实体类
/**
 * 微信平台证书VO
 * @author shenlu
 * @date 2020/12/21 16:14
 */
@Data
public class Certificate {
 
    /**
     * 平台证书序列号
     */
    private String serial_no;
 
    /**
     * 平台证书有效时间
     */
    private String effective_time;
 
    /**
     * 平台证书过期时间
     */
    private String expire_time;
 
    /**
     * 加密证书
     */
    private EncryptCertificate encrypt_certificate;
 
    /**
     * 加密证书
     */
    @Data
    public class EncryptCertificate {
 
        /**
         * 算法
         */
        private String algorithm;
 
        /**
         * 随机字符串
         */
        private String nonce;
 
        /**
         * 相关数据
         */
        private String associated_data;
 
        /**
         * 密文
         */
        private String ciphertext;
    }
}

/**
 * 微信通知数据解密后对象
 * @author shenlu
 * @date 2020/12/18 11:17
 */
@Data
public class NotifyResource {
 
    /**
     * 公众号ID
     */
    private String appid;
 
    /**
     * 直连商户号
     */
    private String mchid;
 
    /**
     * 商户订单号
     */
    private String out_trade_no;
 
    /**
     * 微信支付订单号
     */
    private String transaction_id;
 
    /**
     * 交易类型
     */
    private String trade_type;
 
    /**
     * 交易状态
     */
    private String trade_state;
 
    /**
     * 交易状态描述
     */
    private String trade_state_desc;
 
    /**
     * 付款银行
     */
    private String bank_type;
 
    /**
     * 支付完成时间
     */
    private String success_time;
 
    /**
     * 支付者
     */
    private Payer payer;
 
    /**
     * 订单金额
     */
    private Amount amount;
 
 
 
    /**
     * 支付者
     */
    @Data
    public class Payer{
        /**
         * 用户标识
         */
        private String openid;
    }
 
    /**
     * 订单金额
     */
    @Data
    public class Amount{
        /**
         * 总金额
         */
        private Integer total;
 
        /**
         * 用户支付金额
         */
        private Integer payer_total;
 
        /**
         * 货币类型
         */
        private String currency;
 
        /**
         * 用户支付币种
         */
        private String payer_currency;
 
    }
 
}

/**
 * 接收微信支付通知VO
 * @author shenlu
 * @date 2020/12/18 11:08
 */
@Data
public class PayNotify {
    /**
     * 通知的唯一ID
     */
    private String id;
 
    /**
     * 通知创建时间
     */
    private String create_time;
 
    /**
     * 通知类型 支付成功通知的类型为TRANSACTION.SUCCESS
     */
    private String event_type;
 
    /**
     * 通知数据类型 支付成功通知为encrypt-resource
     */
    private String resource_type;
 
    /**
     * 通知资源数据
     */
    private Resource resource;
 
    /**
     * 回调摘要
     */
    private String summary;
 
    /**
     * 通知资源数据
     */
    @Data
    public class Resource{
        /**
         * 加密算法类型
         */
        private String algorithm;
 
        /**
         * 数据密文
         */
        private String ciphertext;
 
        /**
         * 附加数据
         */
        private String associated_data;
 
        /**
         * 随机串
         */
        private String nonce;
    }
}
  1. 编写微信配置类,获取httpclient对象

@Configuration
public class WeChatConfig {
    private String mchId = "xxxxxxxxxx";//商户号
    private String mchSerialNo = "xxxxxxx";//证书序列号
    private String apiV3Key = "xxxxxxxxx";//apiv3密钥
    private String filePath = "/usr/local/wechat/1605438448_20210607_cert/apiclient_key.pem";//证书私钥地址



    @Bean
    public PrivateKey privateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        String content = new String(Files.readAllBytes(Paths.get(filePath)), "utf-8");
        String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey merchantPrivateKey = kf.generatePrivate(
                new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        return merchantPrivateKey;
    }


    @Bean
    public CloseableHttpClient closeableHttpClient() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, ParseException, CertificateException, URISyntaxException {
        //不需要传入微信支付证书了
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, privateKey())),
                apiV3Key.getBytes("utf-8"));


        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey())
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

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

        return httpClient;
    }

}

  1. 编写工具类,便于生成二维码和关闭订单

@Component
public class PayUtils {
    private static String apiV3Key = "xxxxxxxxxxx";
    private static String mchId = "xxxxxxxxxx";//商户号
    // 定义全局容器 保存微信平台证书公钥  注意线程安全
    public static Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();

    @Autowired
    private CloseableHttpClient httpClient;

    public String getQRCode(int price, String orderNumber){
        try {
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();

            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid", "xxxxxxxx")
                    .put("appid", "xxxxxxxxxxx")
                    .put("description", "\n" +
                            "xxxxxxxxxxxx")
                    .put("notify_url", "http://xxxxxxxxxxxxxx")
                    .put("out_trade_no", orderNumber);
            rootNode.putObject("amount")
                    .put("total", price)
                    .put("currency", "CNY");

            objectMapper.writeValue(bos, rootNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode==200){
                String var = EntityUtils.toString(response.getEntity());
                JSONObject json = JSONObject.parseObject(var);
                return json.getString("code_url");
            }else {
                System.out.println("生成二维码失败:"+statusCode+",错误信息为:"+EntityUtils.toString(response.getEntity()));
                return null;
            }
        } catch (JsonGenerationException e) {
            e.printStackTrace();
            return null;
        } catch (ClientProtocolException e) {
            e.printStackTrace();
            return null;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        } catch (JsonMappingException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public CloseableHttpResponse closeOrder(String orderNumber){
        try {
            //请求URL
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+orderNumber+"/close");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            //请求body参数
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();

            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid", mchId);

            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));

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

            return response;
        }catch (JsonGenerationException e) {
            e.printStackTrace();
            return null;
        } catch (ClientProtocolException e) {
            e.printStackTrace();
            return null;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        } catch (JsonMappingException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }


    public CloseableHttpResponse sendPost(String url,ByteArrayOutputStream bos) throws IOException {
        System.out.println(httpClient);
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        System.out.println(url);

        System.out.println(bos);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        System.out.println("设置参数成功");
        CloseableHttpResponse response = httpClient.execute(httpPost);
        System.out.println(response);
        return response;
    }


    /**
     * 用微信V3密钥解密响应体.
     *
     * @param associatedData  response.body.data[i].encrypt_certificate.associated_data
     * @param nonce          response.body.data[i].encrypt_certificate.nonce
     * @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext
     * @return the string
     * @throws GeneralSecurityException the general security exception
     */
    public String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.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 (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public CloseableHttpResponse sendGet(String url,ByteArrayOutputStream bos) throws IOException, URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);
        uriBuilder.setParameter("mchid", mchId);

        //完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);

        return response;
    }

    /**
     * 获取平台证书Map
     * @return
     * @throws ParseException
     * @throws IllegalAccessException
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws InstantiationException
     * @throws SignatureException
     * @throws InvalidKeyException
     * @throws CertificateException
     */
    public Map<String, X509Certificate> refreshCertificate() throws ParseException, CertificateException, IOException, URISyntaxException {
        //获取平台证书json
        CloseableHttpResponse response = sendGet("https://api.mch.weixin.qq.com/v3/certificates", null);
        JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
        List<Certificate> certificateList = JSON.parseArray(jsonObject.getString("data"), Certificate.class);
        //最新证书响应实体类
        Certificate newestCertificate = null;
        //最新时间
        Date newestTime = null;
        for (Certificate certificate:certificateList){
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            //如果最新时间是null
            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;
                }
            }
        }

        Certificate.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(StandardCharsets.UTF_8));
        X509Certificate certificate = null;
        try {
            certificate = (X509Certificate) cf.generateCertificate(inputStream);
        } catch (CertificateException e) {
            e.printStackTrace();
        }
        //保存微信平台证书公钥
        Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();
        // 清理HashMap
        certificateMap.clear();
        // 放入证书
        certificateMap.put(newestCertificate.getSerial_no(), certificate);
        return certificateMap;
    }


    /**
     * 验证微信签名
     * @param request
     * @param body
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ParseException
     */
    public boolean verifiedSign(HttpServletRequest request,String body) throws GeneralSecurityException, ParseException, IOException, URISyntaxException {

        //微信返回的证书序列号
        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 (certificateMap.isEmpty() || !certificateMap.containsKey(serialNo)) {
            certificateMap = refreshCertificate();
        }
        //根据序列号获取平台证书
        X509Certificate certificate = 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));
    }

}
  1. 发送请求
@RestController
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/payment")
public class PaymentController {
    @Autowired
    private IOrderService orderService;

    @Autowired
    private IVideoService videoService;

    @Autowired
    private PayUtils payUtils;

    @Autowired
    private CloseableHttpClient httpClient;

    @PostMapping("/closeOrder")
    //https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close
    public JSONObject closeOrder(@RequestBody String param) throws IOException {
        JSONObject json = JSONObject.parseObject(param);
        if (json == null) {
            json.put("success", 0);
            return json;
        }

        String orderNumber = json.getString("orderNumber");
        json.clear();
        if (orderNumber == null) {
            json.put("success", 0);
            return json;
        }

        CloseableHttpResponse response = payUtils.closeOrder(orderNumber);

        if (response == null) {
            System.out.println("response为空");
            json.put("success", 0);
            return json;
        }

        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode == 200) {
            System.out.println("关闭订单成功:" + EntityUtils.toString(response.getEntity()));
            QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
            orderQueryWrapper.eq("order_number", orderNumber);
            Order dbOrder = orderService.getOne(orderQueryWrapper);
            dbOrder.setIsPay(-1);
            orderService.updateById(dbOrder);
            json.put("success", 1);
            return json;
        } else if (statusCode == 204) {
            QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
            orderQueryWrapper.eq("order_number", orderNumber);
            Order dbOrder = orderService.getOne(orderQueryWrapper);
            dbOrder.setIsPay(-1);
            orderService.updateById(dbOrder);
            json.put("success", 1);
            return json;
        } else {
            json.put("success", 1);
            System.out.println("关闭订单失败,状态码:" + statusCode + "错误信息为:" + EntityUtils.toString(response.getEntity()));
            return json;
        }
    }

    public String[] queryIsHaveOrder(String memberId, Integer videoId) {
        QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
        orderQueryWrapper.eq("user_id", memberId).eq("video_id", videoId);
        Order dbOrder = orderService.getOne(orderQueryWrapper);
        if (dbOrder != null) {
            if (dbOrder.getIsPay() == -1) {
                String orderNumber = UUID.randomUUID().toString().replaceAll("-","");
                float price = dbOrder.getPrice();
                String code = payUtils.getQRCode((int) (price*100),orderNumber);

                String[] strs = new String[2];
                strs[0] = orderNumber;
                if (code == null) {
                    strs[1] = "error";
                } else {
                    dbOrder.setCreateTime(new Date());
                    dbOrder.setIsPay(0);
                    dbOrder.setOrderNumber(orderNumber);
                    orderService.updateById(dbOrder);
                    strs[1] = code;
                }
                return strs;
            } else {
                String orderNumber = dbOrder.getOrderNumber();
                float price = dbOrder.getPrice();
                String code = payUtils.getQRCode((int) (price * 100), orderNumber);
                String[] strs = new String[2];
                strs[0] = orderNumber;
                if (code == null) {
                    strs[1] = "error";
                } else {
                    strs[1] = code;
                }
                return strs;
            }
        }
        return null;
    }

    @PostMapping("/getQRCode")
    public JSONObject getQRCode(@RequestBody String param, HttpServletRequest request) throws IOException {
        JSONObject json = JSONObject.parseObject(param);
        Integer videoId = json.getInteger("videoId");
        json.clear();

        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        if (memberId == null || memberId.equals("")) {
            json.put("success", 0);
            return json;
        }

        String[] var = queryIsHaveOrder(memberId, videoId);
        if (var != null) {
            if (var[1].equals("error")) {
                json.put("success", 0);
                return json;
            } else {
                json.put("codeUrl", var[1]);
                json.put("orderNumber", var[0]);
                json.put("success", 1);
                return json;
            }
        }

        Video video = videoService.getById(videoId);
        if (video == null) {
            json.put("success", 0);
            return json;
        }

        String orderNumber = UUID.randomUUID().toString().replaceAll("-", "");
        float price = video.getPrice();
        String code = payUtils.getQRCode((int) (price * 100), orderNumber);
        if (code != null) {
            json.put("codeUrl", code);

            //写入数据库
            Order order = new Order();
            order.setUserId(memberId);
            order.setVideoId(videoId);
            order.setPrice(price);
            order.setOrderNumber(orderNumber);
            order.setIsPay(0);
            order.setCreateTime(new Date());
            boolean b = orderService.save(order);
            System.out.println("保存数据库:" + b);

            json.put("orderNumber", orderNumber);
            json.put("success", 1);
            return json;
        } else {
            json.put("success", 0);
        }
        return json;
    }

    //供微信调用的接口
    @PostMapping("/wechatNotify")
    public JSONObject wechatNotify(@RequestBody String param, HttpServletRequest request) throws ParseException, GeneralSecurityException, IOException, URISyntaxException {
        JSONObject json = new JSONObject();

        //如果验证签名序列号通过
        if (payUtils.verifiedSign(request, param)) {
            System.out.println("验证签名成功");
            //微信支付通知实体类
            PayNotify payNotify = JSONObject.parseObject(param, PayNotify.class);
            //如果支付成功
            if ("TRANSACTION.SUCCESS".equals(payNotify.getEvent_type())) {
                //通知资源数据
                PayNotify.Resource resource = payNotify.getResource();
                //解密后资源数据
                String notifyResourceStr = payUtils.decryptResponseBody(resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
                //通知资源数据对象
                NotifyResource notifyResource = JSONObject.parseObject(notifyResourceStr, NotifyResource.class);

                String orderNumber = notifyResource.getOut_trade_no();
                String trade_state = notifyResource.getTrade_state();
                String payTime = notifyResource.getSuccess_time();
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                Date date = formatter.parse(payTime);
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                date = sdf.parse(sdf.format(date));

                int payer_total = notifyResource.getAmount().getPayer_total();

                QueryWrapper<Order> accountQueryWrapper = new QueryWrapper<>();
                accountQueryWrapper.eq("order_number", orderNumber);
                Order order = orderService.getOne(accountQueryWrapper);

                if (order != null) {
                    if (payer_total == order.getPrice() * 100) {
                        //如果订单状态是提交状态
                        if (order.getIsPay() == 0) {
                            //如果付款成功
                            if ("SUCCESS".equals(trade_state)) {
                                order.setPayTime(date);
                                order.setIsPay(1);
                                orderService.updateById(order);
                            } else {
                                //修改失败参数
                            }
                        }
                    } else {
                        System.out.println("订单被修改!");
                    }
                } else {
                    System.out.println("微信返回支付错误摘要:" + payNotify.getSummary());
                }
                System.out.println(payNotify);
                System.out.println(notifyResource);
                //通知微信正常接收到消息,否则微信会轮询该接口
                json.put("code", "SUCCESS");
                json.put("message", "");
                return json;
            }
        }
        System.out.println("验证签名失败");
        return json;
    }

    //前端不断发请求查询订单是否成功
    @PostMapping("/getPayResult")
    public JSONObject getPayResult(@RequestBody String param) {
        JSONObject json = JSONObject.parseObject(param);
        if (json == null) {
            json.clear();
            json.put("success", 0);
            return json;
        }

        String orderNumber = json.getString("orderNumber");
        json.clear();

        if (orderNumber == null) {
            json.put("success", 0);
            return json;
        }

        QueryWrapper<Order> accountQueryWrapper = new QueryWrapper<>();
        accountQueryWrapper.eq("order_number", orderNumber);
        Order order = orderService.getOne(accountQueryWrapper);

        if (order.getIsPay() == 1) {
            json.put("success", 1);
            return json;
        }
        json.put("success", 0);
        return json;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值