1.登录微信支付的网址,获取商户号

微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式微信支付是腾讯公司的支付业务品牌,微信支付商户平台支持线下场所、公众号、小程序、PC网站、APP、企业微信等经营场景快速接入微信支付。微信支付全面打通O2O生活消费领域,提供专业的互联网+行业解决方案,微信支付支持微信红包和微信理财通,是移动支付的首选。https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F

2.申请一个微信小程序或者公众号,然后进入开发管理,获得appid(需要进行微信认证)

 在设置=》基本设置下有微信认证

 3.登录微信支付的商户号,点击产品中心=》APPID账号管理=》关联appid

然后登录小程序,进行关联同意

4.登录微信支付的商户号,点击产品中心=》开发配置=》设置回调链接

5.登录微信支付网址,点击账户中心=》api安全=》设置密钥

它是一串32位的随机数,生成代码的随机数如下,然后直接粘贴到密钥里即可

    /**

     * 生成随机数

     * @return

     */

    public static String getNonceStr(){

       return UUID.randomUUID().toString()

                .replaceAll("-", "")

                .substring(0, 32);

    }

    public static void main(String[] args) {

        System.out.println(getNonceStr());

    }

 6.登录微信支付网址,点击【账户中心】->【API安全】-> 申请证书

 在弹出窗口内点击“下载证书工具”按钮下载证书工具。

安装证书工具并打开,选择证书需要存储的路径后点击“申请证书”。

在证书工具中,将复制的商户信息粘贴并点击“下一步”

获取请求串

将请求串复制到商户平台

生成证书串

步骤1 在【商户平台】-“复制证书串”环节,点击“复制证书串”按钮后;
步骤2 在【证书工具】-“复制请求串”环节,点击“下一步”按钮进入“粘贴证书串”环节;
步骤3 在【证书工具】-“粘贴证书串”环节,点击“粘贴”按钮后;
步骤4 点击“下一步”按钮,进入【证书工具】-“生成证书”环节

粘贴证书串 

最后,申请证书

在【证书工具】-“生成证书”环节,已完成申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件。

点击账户中心=》API安全=》API证书管理,获取证书序列号

脚本:

 

编写代码环节:

将获得的参数写入yaml文件中,注意开发环境和生产环境的配置不一样:

dev环境:

server:
  port: 8002

spring:
  thymeleaf:
    prefix:  /templates/**
    suffix: .html
    cache: false
    resources:
      static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
  jackson:
    # 默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时,需要设置时区为我们自己的时区
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    locale: zh_CN
    generator:
      WRITE_NUMBERS_AS_STRINGS: true
      WRITE_BIGDECIMAL_AS_PLAIN: true

  freemarker:
    suffix: .html

  application:
    name: admin

  datasource:
    url: jdbc:mysql://8.136.84.238:3306/paymentdemo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
    #url: jdbc:mysql://localhost:3306/paymentdemo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
    username: paymentDemo
    password: 18081736467xr
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

########################微信支付参数#######################################
wechat:

  #微信商户号
  mchId: 1518984551

  #商户在微信公众平台申请服务号对应的APPID
  appId: wxfc5e38e47668b966

  #回调报文解密V3密钥key
  v3Key: 2526c992f36b59890ff7bdaea3308705

  #商家API证书序列号
  mchSerialNo: 14E4508CB8FABBABF48DAA1E1CB4C89F6637D8C3

  #微信获取平台证书列表地址,固定不变
  certificates:
    url: https://api.mch.weixin.qq.com/v3/certificates

  #微信统一下单Navtive的API地址,用于二维码支付
  unifiedOrder:
    url: https://api.mch.weixin.qq.com/v3/pay/transactions/native
    jsurl: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

#  #异步接收微信支付结果通知的回调地址
  callback: http://suqiqaq.cn/api/pay/callback

  #微信支付二维码logo
  logo: classpath:favicon.png
  #商户证书私钥路径
  key:
    path:  D:/1518984551_20211014_cert/apiclient_key.pem
###########################################################################

prod环境:

wechat:

  #微信商户号
  mchId: 1518984551

  #商户在微信公众平台申请服务号对应的APPID
  appId: wxfc5e38e47668b966

  #回调报文解密V3密钥key
  v3Key: 2526c992f36b59890ff7bdaea3308705

  #商家API证书序列号
  mchSerialNo: 14E4508CB8FABBABF48DAA1E1CB4C89F6637D8C3

  #微信获取平台证书列表地址,固定不变
  certificates:
    url: https://api.mch.weixin.qq.com/v3/certificates

  #微信统一下单Navtive的API地址,用于二维码支付
  unifiedOrder:
    url: https://api.mch.weixin.qq.com/v3/pay/transactions/native
    jsurl: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

#  #异步接收微信支付结果通知的回调地址
  callback: http://suqiqaq.cn/api/pay/callback

  #微信支付二维码logo
  logo: /home/favicon.png
  #商户证书私钥路径
  key:
    path: /home/1518984551_20211014_cert/apiclient_key.pem
###########################################################################

首先需要在项目启动的时候,把yaml中的参数赋值:

WechatPayConfig:
package com.suqi.payment.config;

import com.suqi.payment.common.KsdStaticParameter;
import com.suqi.payment.utils.WechatPayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;

@Configuration
//实现CommandLineRunner接口,进行数据的预加载
public class WechatPayConfig implements CommandLineRunner
{
    /**
     * 公众号appid
     */
    @Value("${wechat.appId}")
    private String wechatAppId;

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

    /**
     * 商户序列号
     */
    @Value("${wechat.mchSerialNo}")
    private String mchSerialNo;

    /**
     * 支付key
     */
    @Value("${wechat.v3Key}")
    private String wechatV3Key;

    /**
     * 微信支付回调url
     */
    @Value("${wechat.callback}")
    private String payCallbackUrl;

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

    /**
     * 平台证书列表地址
     */
    @Value("${wechat.certificates.url}")
    private String wechatCertificatesUrl;

    /**
     * 商户私钥路径
     */
    @Value("${wechat.key.path}")
    private String wechatKeyPath;

    /**
     * logo
     */
    @Value("${wechat.logo}")
    private String logo;

    /*springboot启动的时候回来加载run方法*/
    @Override
    public void run(String... args) throws Exception
    {
        //微信支付
        KsdStaticParameter.mchId = wechatMchId;
        KsdStaticParameter.appId = wechatAppId;
        KsdStaticParameter.v3Key = wechatV3Key;
        KsdStaticParameter.certificatesUrl = wechatCertificatesUrl;
        KsdStaticParameter.unifiedOrderUrl = wechatUnifiedOrderUrl;
        KsdStaticParameter.notifyUrl = payCallbackUrl;
        KsdStaticParameter.mchSerialNo = mchSerialNo;
        KsdStaticParameter.logo = logo;
        //加载商户私钥
        KsdStaticParameter.privateKey = WechatPayUtils.getPrivateKey(wechatKeyPath);
        //获取平台证书
        //KsdStaticParameter.certificateMap = WechatPayUtils.refreshCertificate();
    }
}

最终赋值给KsdStaticParameter类,通过KsdStaticParameter传递参数给controller
KsdStaticParameter:

package com.suqi.payment.common;

import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description 定义微信支付参数配置类
 * @Date 21:06 2021/5/10
 * @Param
 * @return
 **/
public class KsdStaticParameter {
    /**
     * 微信商户号
     */
    public static String mchId;
    /**
     * 商户在微信公众平台申请服务号对应的APPID
     */
    public static String appId;
    /**
     * 回调报文解密V3密钥key
     */
    public static String v3Key;
    /**
     * 微信获取平台证书列表地址
     */
    public static String certificatesUrl;
    /**
     * 微信APP下单URL
     */
    public static String unifiedOrderUrl;
    /**
     * 微信小程序的下单URL
     */
    public static String unifiedOrderUrlJS;
    /**
     * 异步接收微信支付结果通知的回调地址
     */
    public static String notifyUrl;
    /**
     * 微信证书私钥
     */
    public static PrivateKey privateKey;

    /**
     * logo
     */
    public static String logo;
    /**
     * 微信商家api序列号
     */
    public static String mchSerialNo;

    // 定义全局容器 保存微信平台证书公钥
    public static Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();

    public static void certificateMap(String serialNo) {
    }
}

编写WeixinNavtiveController:

1.前端传入courseid,后端通过coursesid查询是否有该课程,如果有,就将yaml中的参数封装成一个map,并且将一些附属参数存入到map中,方便微信回调成功的时候把数据存入数据库

 WeixinNavtiveController:

 /**
     * 付款订单Api,根据传入的订单号 生成付款二维码
     *
     * @param response
     */
    @RequestMapping("/weixinpay")
    @ResponseBody
    public byte[] weixinpay(String courseid, HttpServletResponse response) throws JsonProcessingException
    {
        if (StringUtils.isEmpty(courseid)) return null;
        KssCourses course = courseService.getById(courseid);
        if (Objects.isNull(course)) return null;
        //1封装请求参数
        Map<String, Object> map = new HashMap();
        map.put("mchid", KsdStaticParameter.mchId);
        map.put("appid", KsdStaticParameter.appId);

        //流水号,产生唯一的订单,与业务流水挂钩
        map.put("out_trade_no", new SnowflakeIdWorker(1, 1).nextId() + "");

        map.put("description", course.getTitle());

        //异步接收微信支付结果通知的回调地址
        map.put("notify_url", KsdStaticParameter.notifyUrl);

        Map<String, Object> amount = new HashMap();
        //订单金额 单位分
        amount.put("total", Integer.parseInt(getMoney(course.getPrice())));
        amount.put("currency", "CNY");
        map.put("amount", amount);

        // 附属参数,方便微信回调把数据存入数据库
        Map<String, Object> attachMap = new HashMap();
        attachMap.put("courseid", courseid);
        attachMap.put("userid", 1);
        attachMap.put("price", course.getPrice());
        attachMap.put("nickname", "飞哥");
        map.put("attach", JsonUtil.obj2String(attachMap));

        // 2:转换成json字符串,开始微信支付请求
        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(map);

        // 3:请求统一微信native下单接口
        Map<String, Object> stringObjectMap = HttpUtils.doPost(KsdStaticParameter.unifiedOrderUrl, body);

        String codeUrl = stringObjectMap.get("code_url").toString();
        //生成二维码配置
        try {
            // 7: 生成微信支付二维码
            ByteArrayOutputStream output = new ByteArrayOutputStream();

            //获取logo
            String logopath = ResourceUtils.getFile(KsdStaticParameter.logo).getAbsolutePath();

            //生成二维码
            BufferedImage buff = QRCodeUtil.encode(codeUrl, logopath, false);

            ImageOutputStream imageOut = ImageIO.createImageOutputStream(output);
            ImageIO.write(buff, "JPEG", imageOut);
            imageOut.close();
            ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
            return FileCopyUtils.copyToByteArray(input);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

因为微信必须以分为单位,而数据库一般存储以元为单位,所以需要进行转换:

/**
     * 元转换成分
     *
     * @param money
     * @return
     */
    private static String getMoney(String money) {
        if (money == null || money.equalsIgnoreCase("0")) {
            return "";
        }
        /*
        * 例如 0.12元        12元          1.2元
        *   index : 1       index :-1     index:1
        *   length : 4      length:2      length:3
        *   amLong : 12分   amLong:1200分  amLong:120
         * */
        // 金额转化为分为单位
        // 处理包含, ¥ 或者$的金额
        String currency = money.replaceAll("\\$|\\¥|\\,", "");
        int index = currency.indexOf(".");
        int length = currency.length();
        Long amLong = 0l;
        //没有小数
        if (index == -1) {
            amLong = Long.valueOf(currency + "00");
            // 传过来的数至少有两位小数
        } else if (length - index >= 3) {
            amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
            //传过来的数有一位小数
        } else if (length - index == 2)
        {
            String substring = currency.substring(0, index + 2);
            amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
            //小数后面没数
        } else {
            amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
        }
        return amLong.toString();
    }

2.HttpUtils向微信发送一个post请求,获取codeUrl

HttpUtils:

package com.suqi.payment.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class HttpUtils
{
    private static final ObjectMapper JSON=new ObjectMapper();

    /**
     * get方法
     * @param url
     * @return
     */
    public static JsonNode doGet(String url){
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        HttpGet httpget = new HttpGet(url);
        httpget.addHeader("Content-Type", "application/json;charset=UTF-8");
        httpget.addHeader("Accept", "application/json");
        try{
            String token = WechatPayUtils.getToken("GET", new URL(url), "");
            httpget.addHeader("Authorization", token);
            CloseableHttpResponse httpResponse = httpClient.execute(httpget);
            if(httpResponse.getStatusLine().getStatusCode() == 200){

                String jsonResult = EntityUtils.toString( httpResponse.getEntity());
                return JSON.readTree(jsonResult);
            }else{
                System.err.println(EntityUtils.toString( httpResponse.getEntity()));
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }



    /**
     * 封装post
     * @return
     */
    public static Map<String,Object> doPost(String url, String body)
    {
        CloseableHttpClient httpClient =  HttpClients.createDefault();
        HttpPost httpPost  = new HttpPost(url);
        httpPost.addHeader("Content-Type","application/json;chartset=utf-8");
        httpPost.addHeader("Accept", "application/json");
        try{
            // 发起微信支付的真正请求
            /*把请求地址URL和参数进行加密处理,并传到请求头中*/
            String token = WechatPayUtils.getToken("POST", new URL(url), body);
            httpPost.addHeader("Authorization", token);

            if(body==null){
                throw  new IllegalArgumentException("data参数不能为空");
            }
            //设置编码格式
            StringEntity stringEntity = new StringEntity(body,"utf-8");
            httpPost.setEntity(stringEntity);
            //向微信发送post请求
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            if(httpResponse.getStatusLine().getStatusCode() == 200)
            {
                String jsonResult = EntityUtils.toString(httpEntity);
                return JSON.readValue(jsonResult,HashMap.class);
            }else{
                System.err.println("微信支付错误信息"+EntityUtils.toString(httpEntity));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        return null;

    }

    /**
     * 封装post
     * @return
     */
    public static Map<String,Object> doPostWexin(String url, String body){
        CloseableHttpClient httpClient =  HttpClients.createDefault();
        HttpPost httpPost  = new HttpPost(url);
        httpPost.addHeader("Content-Type","application/json;chartset=utf-8");
        httpPost.addHeader("Accept", "application/json");
        try{
            String token = WechatPayUtils.getToken("POST", new URL(url), body);
            httpPost.addHeader("Authorization", token);

            if(body==null){
                throw  new IllegalArgumentException("data参数不能为空");
            }
            StringEntity stringEntity = new StringEntity(body,"utf-8");
            httpPost.setEntity(stringEntity);

            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            if(httpResponse.getStatusLine().getStatusCode() == 200){
                String jsonResult = EntityUtils.toString(httpEntity);
                return JSON.readValue(jsonResult, HashMap.class);
            }else{
                System.err.println("微信支付错误信息"+EntityUtils.toString(httpEntity));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        return null;

    }
}

       2.1首先需要把请求地址URL和参数进行加密处理,并传到请求头中

 /**
     * 生成token 把请求地址URL和参数进行加密处理
     * 为什么要签名:对请求参数和请求地址进行加密,不加密无法通过
     * @param method
     * @param url
     * @param body
     * @return
     * @throws Exception
     */
   public static String getToken(String method, URL url, String body) throws Exception
   {
        //生成随机数
        String nonceStr = getNonceStr();
        //生成时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        //生成签名串
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        //通过私钥生成签名
        String signature = sign(message.getBytes("utf-8"));

        return "WECHATPAY2-SHA256-RSA2048 "+"mchid=\"" + KsdStaticParameter.mchId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + KsdStaticParameter.mchSerialNo + "\","
                + "signature=\"" + signature + "\"";
    }

         加密处理:

                1.首先通过UUID生成随机数

/**
     * 生成随机数
     * @return
     */
    public static String getNonceStr(){
       return UUID.randomUUID().toString()
                .replaceAll("-", "")
                .substring(0, 32);
    }

                2.生成时间戳

long timestamp = System.currentTimeMillis() / 1000;

                3.生成签名串

/**
     * 生成签名串
     * @param method
     * @param url
     * @param timestamp
     * @param nonceStr
     * @param body
     * @return
     */
   public static String buildMessage(String method, URL url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.getPath();
        if (url.getQuery() != null) {
            canonicalUrl += "?" + url.getQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

                4.通过私钥生成签名

 /**
     * 通过私钥生成签名
     * @param message
     * @return
     * @throws Exception
     */
   public static String sign(byte[] message) throws Exception
   {
        Signature sign = Signature.getInstance("SHA256withRSA");
        // 商户私钥
        sign.initSign(KsdStaticParameter.privateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

                5.传到请求头中

 String token = WechatPayUtils.getToken("GET", new URL(url), "");
            httpget.addHeader("Authorization", token);

3.通过QRCodeUtil的encode方法将codeUrl生成二维码

package com.suqi.payment.utils;

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Hashtable;
/**
 * @Author xuke
 * @Description 二维码生成的类
 * @Date 14:50 2021/5/16
 * @Param
 * @return
**/
public class QRCodeUtil {
    private static final String CHARSET = "utf-8";
    private static final String FORMAT_NAME = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 300;
    // LOGO宽度
    private static final int WIDTH = 90;
    // LOGO高度
    private static final int HEIGHT = 90;

    private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception {
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
                hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        if (imgPath == null || "".equals(imgPath)) {
            return image;
        }
        // 插入图片
        QRCodeUtil.insertImage(image, imgPath, needCompress);
        return image;
    }

    private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
        File file = new File(imgPath);
        if (!file.exists()) {
            System.err.println("" + imgPath + "   该文件不存在!");
            return;
        }
        Image src = ImageIO.read(new File(imgPath));
        int width = src.getWidth(null);
        int height = src.getHeight(null);
        if (needCompress) { // 压缩LOGO
            if (width > WIDTH) {
                width = WIDTH;
            }
            if (height > HEIGHT) {
                height = HEIGHT;
            }
            Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(image, 0, 0, null); // 绘制缩小后的图
            g.dispose();
            src = image;
        }
        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (QRCODE_SIZE - width) / 2;
        int y = (QRCODE_SIZE - height) / 2;
        graph.drawImage(src, x, y, width, height, null);
        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
        mkdirs(destPath);
        ImageIO.write(image, FORMAT_NAME, new File(destPath));
    }

    public static BufferedImage encode(String content, String imgPath, boolean needCompress) throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
        return image;
    }

    public static void mkdirs(String destPath) {
        File file = new File(destPath);
        // 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    public static void encode(String content, String imgPath, String destPath) throws Exception {
        QRCodeUtil.encode(content, imgPath, destPath, false);
    }


    public static void encode(String content, String destPath) throws Exception {
        QRCodeUtil.encode(content, null, destPath, false);
    }

    public static void encode(String content, String imgPath, OutputStream output, boolean needCompress)
            throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
        ImageIO.write(image, FORMAT_NAME, output);
    }

    public static void encode(String content, OutputStream output) throws Exception {
        QRCodeUtil.encode(content, null, output, false);
    }

    public static String decode(File file) throws Exception {
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }
        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Result result;
        Hashtable hints = new Hashtable();
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        result = new MultiFormatReader().decode(bitmap, hints);
        String resultStr = result.getText();
        return resultStr;
    }

    public static String decode(String path) throws Exception {
        return QRCodeUtil.decode(new File(path));
    }

}

SnowflakeIdWorker:生成唯一的流水号

package com.suqi.payment.utils;

import org.springframework.stereotype.Component;

/**
 * Twitter_Snowflake<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
 * 000000000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T
 * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 */
@Component
public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /**
     * 开始时间截 (2015-01-01)
     */
    private final long twepoch = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private final long workerIdBits = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final long datacenterIdBits = 5L;

    /**
     * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据标识id,结果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 工作机器ID(0~31)
     */
    private long workerId;

    /**
     * 数据中心ID(0~31)
     */
    private long datacenterId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;

    // ==============================Constructors=====================================

    public SnowflakeIdWorker() {
        this(1L,2L);
    }

    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {

        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException( String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }

        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException( String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }

        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    // ==============================Test=============================================

    /**
     * 测试
     */
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            // System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }

    }
}

最后,将二维码以JPEG格式传递给前端

 //生成二维码
            BufferedImage buff = QRCodeUtil.encode(codeUrl, logopath, false);

            //通过JPEG格式传递给前端
            ImageOutputStream imageOut = ImageIO.createImageOutputStream(output);
            ImageIO.write(buff, "JPEG", imageOut);
            imageOut.close();
            ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
            return FileCopyUtils.copyToByteArray(input);

前端:

index首页:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="css/ksd.css"/>
</head>
<body>
<div id="app">
    <div id="courseListBox" class="course_bdleft course-block  Mtp25">
        <div class="text-center mt-5">
            <h1 class="fz32 fw">学相伴微信支付V3-Native系列</h1>
            <img :src="course.payimg" width="250" alt="">
            <h3>你购买的课程是:{{course.title}},价格是:¥{{course.price}}</h3>
        </div>
        <div class="course_stage_item">
            <h2 class="ksd-title-position-h2" index="1" id="ksd-title-position-1">
                <span class="cro_icon1">1</span>
                <span>第一阶段:JavaSE</span>
            </h2>
            <div class="path-course-r">
                <div class="row">
                    <div class="col-lg-3 col-md-4 col-sm-6 animated fadeInUp delay-1s" v-for="(course,index) in courses">
                        <div class="course-item">
                            <div class="course-img ">
                                <a :href="'coursedetail/'+course.courseid" target="_blank" :title="course.title"
                                   class="course__img">
                                    <img height="140" width="100%" :src="'https://www.kuangstudy.com'+course.img"
                                         data-original="/assert/course/c1/01.jpg" style="">
                                    <span class="stimer">课程价格:¥{{course.price}}</span>
                                </a>
                            </div>
                            <div class="course-content">
                                <h3 title="聊聊编程这条路" class="course__title">
                                    <a :href="'coursedetail/'+course.courseid" target="_blank" :title="course.title"
                                       class="course__img">
                                        {{course.title}}
                                    </a>
                                </h3>
                                <p class="course__author">{{course.intro}}</p>
                                <div class="course-price-wrap" @click="buyCourrse(index)">
                                    <span class="course__btn">点击购买</span>
                                </div>
                            </div>
                        </div>
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script>
    var vue = new Vue({
        el: "#app",
        data: {
            course:{
                payimg:""
            },
            courses: []
        },
        created: function () {
            // 初始化产品课程数据接口加载
            this.load();
        },
        methods: {
            // 1:加载课程的方法
            load: function () {
                var that = this;
                // 1: 调用服务段的接口
                axios.get("/loadcourse").then(function (res)
                {
                    console.log("res----》",res);
                    // 2: 解析和接受服务端返回的数据
                    that.courses = res.data.data.courses;
                })
            }
        }
    })
</script>
</body>
</html>

通过loadcourse接口获取课程数据

courseDetail:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="/css/ksd.css"/>
</head>
<body>
<div id="app">
    <div id="courseListBox" class="course_bdleft course-block  Mtp25">
        <div class="text-center mt-5">
            <h1 class="fz32 fw">学相伴微信支付V3-Native系列</h1>
            <input type="hidden" ref="course" value="${course.courseid}" id="courseid">
            <img src="/weixinpay?courseid=${course.courseid}" width="250" alt="">
            <h3>你购买的课程是:${course.title},价格是:¥${course.price}</h3>
            <p>已经等待了:{{count}}秒</p>
        </div>
    </div>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
    var vue = new Vue({
        el: "#app",
        data: {
            courseid: "",
            count:5
        },
        created()
        {
            this.courseid = document.getElementById("courseid").value;
            // 1: 轮询监听用户是否支付成功!
            this.intervalcallback();
        },

        methods:
        {
            // 1 : 用户支付成功回调监听
            listenerCallback()
            {
                var that = this;
                if(that.count<=0)
                {
                    alert("支付超时");
                    //刷新这个页面
                    window.location.href = window.location.href;
                    return;
                }
                that.count--;
                axios.get("/api/paySuccess?courseid=" + this.courseid).then(function (res)
                {
                    if(res.data.code == 200)
                    {
                        if (that.timer) clearInterval(that.timer);
                        alert("支付成功!!");
                    }
                });
            },
            intervalcallback()
            {
                var that = this;
                if (that.timer) clearInterval(that.timer);
                //每隔三秒查询
                that.timer = setInterval(this.listenerCallback, 3000);
            }
        }
    })

</script>
</body>
</html>

原因:商家证书的私钥路径不同,因为一个是linux环境一个是windows环境。
在部署linux环境的时候,可能会引发加密的的java异常。这个时候需要手动配置jdk和jdk-security安全相关的环境才可以校验通过。

去官网下载jdk8 无限制政策文件
JDK8的下载地址: JCE Unlimited Strength Jurisdiction Policy Files for JDK/JRE 8 Download 

使用 xftp ,把里面的两个jar包:local_policy.jar  和 US_export_policy.jar  

替换掉原来  Jdk  安装目录/usr/java/jdk1.8.0_121/jre/lib/security 下的两个jar 包就可以了。

Logo

更多推荐