微信支付(一)SpringBoot 实现微信扫码支付/Native支付

SpringBoot 实现微信扫码支付/Native支付

一、背景

在开发一个捐赠项目时须在pc端接入微信扫码支付(Native 扫码支付),在微信端接入微信公众号支付(Jsapi 支付)。后端使用的是Spring Boot框架,前台采用HTML+css+js 编写。

二、微信扫码支付流程

1、微信支付开发文档:微信支付开发文档
2、微信扫码支付业务流程

3、个人理解的支付流程
①用户在商户平台下单
②商户生成商户订单,并调起微信统一下单接口
③微信生成预支付订单,返回预支付的交易连接(code_url),我们根据code_url 生成二维码,提供给消费者扫码。值得注意的是:微信对于同一商户订单号且同一价格的商品可以生成多个预付单,我们可以设置预付单的有效时间(time_expire),预付单过期后,刷新二维码支付页面便会生成一个预付单,此时会发现二维码变了,即返回的预支付的交易连接(code_url)变了。
④用户扫描二维码,跳出支付页面
⑤用户确认支付,输入密码,支付成功
⑥交易成功后,微信返回支付成功页面,后台会调用回调接口通知给商户交易的支付状态,无论支付成功与否,商户都需要告知微信收到了通知,否则,微信将在半个小时内调起8次回调接口,若都没响应,则会退款给消费者。

三、所需依赖

生成二维码的依赖

在这里插入<!--生成二维码-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.3</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.3.2</version>
</dependency>

解析xml的依赖

<!--用于解析xml,因为微信接收和返回的数据是xml文档-->
<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom</artifactId>
    <version>1.1</version>
</dependency>
四、获取商品信息并生成二维码

1、引入 jquery.qrcode.min.js 生成二维码

public ModelAndView perOrder(String body) {
    // 商品描述
    body = body;
    // 商户订单号
    String out_trade_no = PayCommonUtil.getUniqueOrderId();
    // 订单总金额,单位为分
    String total_fee = "1";
    //获得生成支付二维码的字符串
    String result = PayCommonUtil.weiXinPay(total_fee, body, out_trade_no);
    //生成二维码result
    ModelAndView pay = new ModelAndView("pay");
    pay.addObject("qrCodeUrl",result);//前台js生成二维码
    return pay;

}

前端HTML

public ModelAndView perOrder(String body) {
    // 商品描述
    body = body;
    // 商户订单号
    String out_trade_no = PayCommonUtil.getUniqueOrderId();
    // 订单总金额,单位为分
    String total_fee = "1";
    //获得生成支付二维码的字符串
    String result = PayCommonUtil.weiXinPay(total_fee, body, out_trade_no);
    //生成二维码result
    ModelAndView pay = new ModelAndView("pay");
    pay.addObject("qrCodeUrl",result);//前台js生成二维码
    return pay;

}

2、ZXing 工具类生成二维码

public ModelAndView perOrder(String body) {
    // 商品描述
    body = body;
    // 商户订单号
    String out_trade_no = PayCommonUtil.getUniqueOrderId();
    // 订单总金额,单位为分
    String total_fee = "1";
    //获得生成支付二维码的字符串
    String result = PayCommonUtil.weiXinPay(total_fee, body, out_trade_no);
    //生成二维码result
    boolean b = ZXingUtil.encodeQRCodeImage(result, "UTF-8", "E:\\idea\\wxPayDemo\\src\\main\\resources\\static\\QR\\" + out_trade_no + ".jpg", 300, 300, null);
    if (b) {
        //将生成的二维码返回给前台   
    }
}
五、支付配置信息
public interface PayConfigUtil {
    String APP_ID="";//微信公众号id
    String MCH_ID="";//商户id
    String API_KEY="";//API密钥
    String UFDOOER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单地址
    String NOTIFY_URL="";//回调地址
    String CREATE_IP="";//发起ip
}
六、工具类

MD5

public class MD5Util {

    /**
     * @param b
     * @return java.lang.String
     * @author wxc
     * @date 2021/7/16/016 15:13
     * @description 将自己转换成可识别的字符串
     */
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0) {
            n += 256;
        }
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }


    /**
     * @param b
     * @return java.lang.String
     * @author wxc
     * @date 2021/7/16/016 15:09
     * @description 将字节数组转换成可识别字符串
     */
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }


    /**
     *
     * @author wxc
     * @date 2021/7/16/016 15:23
     * @param origin 被转换的内容
     * @param charsetname 字符集
     * @return java.lang.String
     * @description 获取指定内容的MD5值
     */
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if(charsetname==null||"".equals(charsetname)){
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            }else {
                resultString=byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            }

        } catch (Exception e) {
        }
        return resultString;
    }



    private static final String hexDigits[] ={"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};

    public static String UrlEncode(String src) throws UnsupportedEncodingException{
        return URLEncoder.encode(src,"UTF-8").replace("+","%20");
    }
}

HttpUtil

/**
 * @author wxc
 * @date 2021年07月16日 15:34
 * @description 用于与微信服务器进行交互
 */
public class WxHttpUtil {
    private final static int CONNECT_TIMEOUT=5000;
    private final static String DEFAULT_ENCODING="UTF-8";

    public static String postData(String urlStr,String data){
        return postData(urlStr,data,null);
    }

    public static String postData(String urlStr,String data,String contentType){
        BufferedReader reader=null;
        try{
            URL url = new URL(urlStr);
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(CONNECT_TIMEOUT);
            if(contentType!=null){
                conn.setRequestProperty("content-type",contentType);
            }
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
            if (data==null){
                data="";
            }
            writer.write(data);
            writer.flush();
            writer.close();

            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
            StringBuilder sb=new StringBuilder();
            String line=null;
            while ((line=reader.readLine())!=null){
                sb.append(line);
                sb.append("\r\n");
            }
            return sb.toString();

        }catch (Exception e){
            System.err.println("Error connecting to"+urlStr+":"+e.getMessage());
        }finally {
            try{
                if(reader!=null){
                    reader.close();
                }
            }catch (IOException e){

            }
        }
        return null;
    }
}

PayCommonUtil

/**
 * @author wxc
 * @date 2021年07月16日 16:29
 * @description
 */
public class PayCommonUtil implements PayConfigUtil{

    /**
     *
     * @author wxc
     * @date 2021/7/16/016 16:40
     * @param characterEncoding
     * @param packageParams
     * @return boolean
     * @description 验证签名 
     */
     /**
     * 支付成功后,微信返回支付成功的信息
     * 为了判断商户收到的信息是否是微信发来的而不是其他渠道发来的
     * 微信会把信息封装成一个签名传回来
     * 商户收到签名后,进行解析并与本地的比对,数据一样代表成功
     */
    public static boolean isTenpaySign(String characterEncoding, SortedMap<Object,Object> packageParams,String key){
        StringBuffer sb = new StringBuffer();
        Set es=packageParams.entrySet();
        Iterator it=es.iterator();
        while (it.hasNext()){
            Map.Entry entry=(Map.Entry)it.next();
            String k=(String)entry.getKey();
            String v=(String)entry.getValue();
            if(!"sign".equals(k)&&null!=v&&!"".equals(v)){
                sb.append(k+"="+v+"&");
            }
        }
        sb.append("key="+API_KEY);
        //算出摘要
        String mysign=MD5Util.MD5Encode(sb.toString(),characterEncoding).toLowerCase();
        String tenpaySign=((String)packageParams.get("sign")).toLowerCase();

        return tenpaySign.equals(mysign);
    }

    /**
     * 
     * @author wxc
     * @date 2021/7/29/029 16:47
     * @param characterEncoding
     * @param packageParams
     * @param key 
     * @return java.lang.String
     * @description 创建sign签名
     */
    public static String createSign(String characterEncoding,SortedMap<Object,Object> packageParams,String key){
        StringBuffer sb = new StringBuffer();
        Set es=packageParams.entrySet();
        Iterator it=es.iterator();
        while (it.hasNext()){
            Map.Entry entry=(Map.Entry)it.next();
            String k=(String)entry.getKey();
            String v=(String)entry.getValue();
            if(!"sign".equals(k)&&null!=v&&!"".equals(v)){
                sb.append(k+"="+v+"&");
            }
        }
        sb.append("key="+API_KEY);
        //算出摘要
        String sign=MD5Util.MD5Encode(sb.toString(),characterEncoding).toLowerCase();

        return sign;
    }


    /**
     *
     * @author wxc
     * @date 2021/7/16/016 17:06
     * @param parameters 请求数据
     * @return java.lang.String
     * @description 将请求参数转换为xml格式的字符串
     */
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es=parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry=(Map.Entry)it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            sb.append("<" + k + ">" + v + "</" + k + ">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    
    /**
     * 
     * @author wxc
     * @date 2021/7/16/016 20:40
     * @param return_code
     * @param return_msg 
     * @return java.lang.String
     * @description  通知微信已经收到消息,不要再给我发消息了
     */
    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code
                + "]]></return_code><return_msg><![CDATA[" + return_msg
                + "]]></return_msg></xml>";
    }

    /**
     *
     * @author wxc
     * @date 2021/7/16/016 17:14
     * @param length
     * @return int
     * @description 取出一个指定长度的随机正整数
     */
    public static int buildRandom(int length){
        int num=1;
        double random = Math.random();
        if(random<0.1){
            random=random+0.1;
        }
        for (int i=0;i<length;i++){
            num = num*10;
        }
        return (int)((random*num));
    }



    /**
     * 
     * @author wxc
     * @date 2021/7/16/016 20:40 
     * @return java.lang.String
     * @description 获取当前时间
     */
    public static String getCurrTime(){
        Date now=new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;

    }



    /**
     *
     * @author wxc
     * @date 2021/7/16/016 17:27
     * @param order_price 价格
     * @param body 商品信息
     * @param out_trade_no 订单号
     * @return java.lang.String
     * @description  获取生成二维码的字符串
     */
    public static String weiXinPay(String order_price,String body,String out_trade_no){

        String appid=PayConfigUtil.APP_ID; //账号信息
        String mch_id=PayConfigUtil.MCH_ID; //商家号
        String key=PayConfigUtil.API_KEY; //密钥

        String currTime=PayCommonUtil.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = PayCommonUtil.buildRandom(4) + "";
        String nonce_str=strTime+strRandom;
        String expireTime=PayCommonUtil.getOrderExpireTime(2*60*1000L);//二维码有效时间两分钟

        /*String order_price="1"; //价格 单位:分
        String body="guisdvgu"; //商品名称
        String out_trade_no="123456"; //订单号*/

        String spbill_url=PayConfigUtil.CREATE_IP;//获取发起电脑ip
        String notify_url=PayConfigUtil.NOTIFY_URL;//回调接口
        String trade_type="NATIVE";

        SortedMap<Object,Object> packageParams=new TreeMap<>();
        packageParams.put("appid",appid);
        packageParams.put("mch_id",mch_id);
        packageParams.put("nonce_str",nonce_str);
        packageParams.put("body",body);
        packageParams.put("out_trade_no",out_trade_no);
        packageParams.put("total_fee",order_price);
        packageParams.put("spbill_url",spbill_url);
        packageParams.put("notify_url",notify_url);
        packageParams.put("trade_type",trade_type);
        packageParams.put("time_expire",expireTime);

        String sign = PayCommonUtil.createSign("UTF-8",packageParams,key);
        packageParams.put("sign",sign);

        String requestXML = PayCommonUtil.getRequestXml(packageParams);
        System.out.println(requestXML);

        String resXml= WxHttpUtil.postData(PayConfigUtil.UFDOOER_URL,requestXML);
        System.out.println(resXml);
        Map map = XMLUtil.doXMLParse(resXml);
        String urlCode=(String) map.get("code_url");
        return urlCode;

    }



    /**
     *
     * @author wxc
     * @date 2021/7/17/017 9:08
     * @param request
     * @param response
     * @description 解析微信返回的结果
     */
    public static void weixin_notify(HttpServletRequest request, HttpServletResponse response) throws IOException {

        //读取参数
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        inputStream=request.getInputStream();
        String s;
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
        while ((s=in.readLine())!=null){
            sb.append(s);
        }
        in.close();
        inputStream.close();

        //解析xml成map
        Map<String,String> m=new HashMap<String,String>();
        m=XMLUtil.doXMLParse(sb.toString());

        //过滤空 设置TreeMap
        SortedMap<Object,Object> packageParams=new TreeMap<Object,Object>();
        Iterator it=m.keySet().iterator();
        while (it.hasNext()){
            String paramter = (String)it.next();
            String parameterValue=m.get(paramter);

            String v="";
            if(null!=parameterValue){
                v=parameterValue.trim();
            }
            packageParams.put(paramter,v);
        }

        //账号信息
        String key=PayConfigUtil.API_KEY;

        String out_trade_no=(String)packageParams.get("out_trade_no");
        //判断签名是否正确
        if(PayCommonUtil.isTenpaySign("UTF-8",packageParams,key)){
            //处理业务开始

            String resXml="";
            if("SUCCESS".equals((String)packageParams.get("result_code"))){
                //支付成功
                String mch_id=(String)packageParams.get("mch_id");
                System.out.println(mch_id);
                System.out.println("支付成功");

                resXml="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";

            }else {
                System.out.println("订单"+out_trade_no+"支付失败");

                resXml="<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";

            }

            //业务处理完毕

            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            out.close();
        }else{
            System.out.println("通知签名验证失败");

        }
    }


   /**
    *
    * @author wxc
    * @date 2021/7/17/017 11:31
    * @return java.lang.String
    * @description 获取唯一订单号
    */
    public static String getUniqueOrderId(){
        return String.valueOf(SnowflakeIdWorker.getSnowflakeIdWorker().nextId());
    }


    /**
     * 设置微信二维码失效时间,并返回具体失效的时间点
     * @param expire 二维码的有效时间,单位是毫秒
     * @return
     */
    public static String getOrderExpireTime(Long expire){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        Date now = new Date();
        Date afterDate = new Date(now .getTime() + expire);
        return sdf.format(afterDate );
    }
}

XmlUtil

/**
 *
 * @author wxc
 * @date 2021/7/16/016 18:04
 * @description 解析微信xml
 */
public class XMLUtil {

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Map doXMLParse(String strxml) {

        Map m = new HashMap();

        try {
            strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

            if(null == strxml || "".equals(strxml)) {
                return null;
            }

            InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
            SAXBuilder builder = new SAXBuilder();
            Document doc = builder.build(in);
            Element root = doc.getRootElement();
            List list = root.getChildren();
            Iterator it = list.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String k = e.getName();
                String v = "";
                List children = e.getChildren();
                if(children.isEmpty()) {
                    v = e.getTextNormalize();
                } else {
                    v = XMLUtil.getChildrenText(children);
                }

                m.put(k, v);
            }

            //关闭流
            in.close();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return m;
    }

    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    @SuppressWarnings("rawtypes")
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(XMLUtil.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }

    /**
     * 微信支付将请求参数转换为xml格式的String
     *
     * @param paramMap
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static String getRequestXmlQuery(SortedMap<String, String> paramMap) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set set = paramMap.entrySet();
        Iterator it = set.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 微信支付将请求参数转换为xml格式的String
     *
     * @param paramMap
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static String getRequestXml(SortedMap<String, String> paramMap) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set set = paramMap.entrySet();
        Iterator it = set.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
                sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
            } else {
                sb.append("<" + key + ">" + value + "</" + key + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值