java实现微信小程序支付(springboot框架) - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 小程序相关 >

java实现微信小程序支付(springboot框架)

发表时间:2020-10-6

发布人:葵宇科技

浏览次数:53

java实现微信小程序支付

准备工作

请仔细阅读微信支付文档 微信支付文档地址
到微信公众平台开通微信小程序应用得到 appid 和 secret

支付代码

/**
     * @param orderNo    订单号
     * @param amount     订单总金额
     * @param deviceCode 设备编号
      * @DESCRIPTION: 订单支付
     * @return: ResponseData
     */
    public ResponseData payOrder(String openid, String orderNo, double amount,  HttpServletRequest request) {

        log.info("======================= 发起微信预支付请求,调用统一下单开始 =======================");
        log.info("准备请求数据...");
        try {
            //这里可以校验订单是否存在、是否支付
          
          //生成需要的参数集合
            Map<String, String> packageParams = this.createPackageParams(orderItemDto, openid, amount, request);

            //MD5运算生成签名,这里是第一次签名,用于调用统一下单接口(签名中所有字符大写toUpperCase())
            //WeChat.KEY:微信支付的商户密钥,需要在微信后台设置,包括后面用到的 KEY 都是这个值
            String mySign = this.sign(this.createLinkString(packageParams), WeChat.KEY, "utf-8").toUpperCase();
            packageParams.put("sign", mySign);
            log.info("==== 第一次签名:" + mySign + " ====\n\t");

            //拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
            String xml = this.GetMapToXML(packageParams);

            log.info("统一下单接口请求的拼接XML数据【" + xml + "】\n\t");

            //调用统一下单接口,并接受返回的结果
            String result = httpUtils .httpRequest(WeChat.PAY_URL, "POST", xml);

            if (ToolUtil.isEmpty(result)) {
                log.info("调用统一下单接口返回的XML数据为空,支付失败");
                return ResponseData.error("支付失败");
            }

            log.info("统一下单接口返回的XML数据【" + result + "】\n\t");
            // 将解析结果存储在HashMap中
            Map map = this.doXMLParse(result);

            //返回给小程序端需要的参数
            Map<String, Object> wxResponse = new HashMap<>();
            if (WeChat.SUCCESS.equals(map.get("return_code")) && WeChat.SUCCESS.equals(map.get("result_code"))) {

                //这里可以写你的业务逻辑,看具体情况,一般业务逻辑会写在支付回调函数里面
                
                Long timeStamp = System.currentTimeMillis() / 1000;
                wxResponse.put("nonceStr", packageParams.get("nonce_str"));
                wxResponse.put("package", "prepay_id=" + map.get("prepay_id"));
                //这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
                wxResponse.put("timeStamp", timeStamp + "");
                //拼接签名需要的参数
                String stringSignTemp = "appId=" + appid + "&nonceStr=" + packageParams.get("nonce_str") + "&package=prepay_id=" + map.get("prepay_id") + "&signType=MD5&timeStamp=" + timeStamp;

                //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = this.sign(stringSignTemp, WeChat.KEY, "utf-8").toUpperCase();
                log.info("==== 第二次签名:" + paySign + " ====");
                wxResponse.put("paySign", paySign);

            } else {
                log.info("调用统一下单接口返回异常 return_code【" + map.get("return_code") + "】" + " result_code【" + map.get("result_code") + "】 支付失败");
                return null;
            }

            wxResponse.put("appid", appid);
            wxResponse.put("orderNo", orderNo);
            log.info("======================= 发起微信支付请求,调用统一下单结束 =======================");
            return ResponseData.success(wxResponse);

        } catch (Exception e) {
            log.info("调用统一下单接口异常:" + e.getMessage());
            log.info("用户【" + openid + "】支付失败");
        }
        return ResponseData.error("支付失败");
    }

签名类

签名请参照微信官方签名算法 地址:签名算法

/**
     * 签名字符串
     *
     * @param text          需要签名的字符串
     * @param key           商户平台设置的密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + "&key=" + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

转换xml

public static String GetMapToXML(Map<String, String> param) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        for (Map.Entry<String, String> entry : param.entrySet()) {
            sb.append("<" + entry.getKey() + ">");
            sb.append(entry.getValue());
            sb.append("</" + entry.getKey() + ">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

解析xml

/**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     *
     * @param strxml
     * @return
     * @throws org.jdom2.JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws Exception {
        if (stringUtils.isEmpty(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = String2Inputstream(strxml);
        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 = getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }

排序并按规则拼接

/**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {
                // 拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }

参数集合

根据微信支付文档的参数来配置 微信支付文档地址

private Map<String, String> createPackageParams(OrderItemDto orderItemDto, String openid, double amount, HttpServletRequest request) {

        //支付金额,字符串类型,单位 分
        String total_fee = String.valueOf((int) (amount * 100));
        //生成的随机字符串
        String nonce_str = PayUtil.getRandomStringByLength(32);
        //设置商户订单号(年月日+六位酒店id,不够补零+随机4位 例:20190525000001a2sa)
        String out_trade_no = "";
        //获取客户端的ip地址
        String spbill_create_ip = PayUtil.getIpAddr(request);

        //组装参数,用户生成统一下单接口的签名
        Map<String, String> packageParams = new HashMap<>();
        packageParams.put("appid", appid);
        packageParams.put("mch_id", WeChat.MCH_ID);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", WeChat.PAY_ORDER_BODY);
        //商户订单号,自己的订单ID
        packageParams.put("out_trade_no", out_trade_no);
        //支付金额,这边需要转成字符串类型,否则后面的签名会失败
        packageParams.put("total_fee", total_fee);
        //客户端ip
        packageParams.put("spbill_create_ip", spbill_create_ip);
        //支付成功后的回调地址
        packageParams.put("notify_url", WeChat.NOTIFY_URL);
        //支付方式
        packageParams.put("trade_type", WeChat.TRADETYPE);
        //用户的openID,自己获取
        packageParams.put("openid", openid + "");

        return packageParams;
    }

http工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;

@Slf4j
public class httpUtils {

    // 连接超时时间,默认10秒
    private static int socketTimeout = 10000;
    // 传输超时时间,默认30秒
    private static int connectTimeout = 30000;
    // HTTP请求器
    private static CloseableHttpClient httpClient;
    // 请求器的配置
    private static RequestConfig requestConfig;

    /**
     * 加载证书
     */
    private static void initCert() throws Exception {
        // 证书密码,默认为商户ID
        String key = "";
        // 商户证书的路径
        //public static final String CERT_PATH = "E:/workspace/apiclient_cert.p12"; //本地路径
        String CERT_PATH = "/usr/local/dev/guns/cert/"; // 服务器路径
        String path = CERT_PATH + "qcdr_cert.p12";

        InputStream instream = null;
        try {
            // 读取本机存放的PKCS12证书文件
            instream = new FileInputStream(new File(path));

            /*ClassPathResource classPathResource = new ClassPathResource(path);
            //获取文件流
            instream = classPathResource.getInputStream();
            instream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);*/
        } catch (Exception e) {
            log.error("商户证书不正确-->>" + e);
        }
        try {
            // 指定读取证书格式为PKCS12
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            // 指定PKCS12的密码(商户ID)
            keyStore.load(instream, key.toCharArray());

            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
            // 指定TLS版本
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            // 设置httpclient的SSLSocketFactory
            httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (Exception e) {
            log.error("商户秘钥不正确-->>" + e);
        } finally {
            instream.close();
        }
    }

    /**
     * 通过Https往API post xml数据
     *
     * @param url    退款地址
     * @param xmlObj 要提交的XML数据对象
     * @return
     */
    public static String postData(String url, String xmlObj) {
        // 加载证书
        try {
            initCert();
        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        HttpPost httpPost = new HttpPost(url);
        // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        // 根据默认超时限制初始化requestConfig
        requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectTimeout)
                .build();
        // 设置请求器的配置
        httpPost.setConfig(requestConfig);
        try {
            HttpResponse response = null;
            try {
                response = httpClient.execute(httpPost);
            } catch (IOException e) {
                e.printStackTrace();
            }
            HttpEntity entity = response.getEntity();
            try {
                result = EntityUtils.toString(entity, "UTF-8");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } finally {
            httpPost.abort();
        }
        return result;
    }

    /**
     * @param requestUrl    请求地址
     * @param requestMethod 请求方法
     * @param outputStr     参数
     */
    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        // 创建SSLContext
        StringBuffer buffer = null;
        try {
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(requestMethod);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();
            //往服务器端写内容
            if (null != outputStr) {
                OutputStream os = conn.getOutputStream();
                os.write(outputStr.getBytes("utf-8"));
                os.close();
            }
            // 读取服务器端返回的内容
            InputStream is = conn.getInputStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            buffer = new StringBuffer();
            String line = null;
            while ((line = br.readLine()) != null) {
                buffer.append(line);
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }
}

下面是小程序支付回调函数

回调函数的路径需要放在上面支付的参数集合里,这样才能收到微信支付结果通知,通知url必须为外网可访问的url,不能携带参数
详细情况可以看 微信支付文档

回调函数代码

/**
     * 支付回调函数
     */
    public void payOrderCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("======================= 微信支付回调开始 =======================");

        //通知微信服务器已经支付是否成功
        String resXml = "<xml>";
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));

            String line;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            String notityXml = sb.toString();
            log.info("微信回调信息:" + notityXml);

            //解析回调xml
            Map map = this.doXMLParse(notityXml);

            //获得回调状态码
            String returnCode = (String) map.get("return_code");
            String resultCode = (String) map.get("result_code");
            log.info("微信回调状态码return_code【 " + returnCode + " 】");

            if ("SUCCESS".equals(returnCode) && returnCode.equals(resultCode)) {
                log.info("微信支付状态码result_code【 " + resultCode + " 】");

                //获取返回的金额与系统订单的金额进行比对
                String totalFee = map.get("total_fee").toString();
                //查询出订单金额
                QcOrder order = orderMapper.selectByOrderNo(map.get("out_trade_no").toString());
                log.info("微信返回订单金额【" + totalFee + "分】 系统订单金额【" + (int) (order.getAmount() * 100) + "分】");

				//根据自己需求校验,此处官方推荐校验金额
                //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
                if (PayUtil.verify(map, WeChat.KEY, "utf-8")) {
                    if (totalFee.equals(String.valueOf((int) (order.getAmount() * 100)))) {
					  //到这里就已经支付成功了,可以添加业务逻辑了
                       
                       //返回给微信支付成功的信息
                        resXml += "<return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>";
                    } else {
                        resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[支付金额错误]]></return_msg>";
                        log.info("微信回调失败,支付金额错误。" + "微信返回订单金额【" + totalFee + "分】 系统订单金额【" + (int) (order.getAmount() * 100) + "分】");
                    }
                } else {
                    resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[签名校验错误]]></return_msg>";
                    log.info("微信回调签名验证【 失败 】");
                }
            } else {
                resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[报文为空]]></return_msg>";
                log.info("微信回调【 失败 】");
            }
        } catch (Exception e) {
            resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[回调异常]]></return_msg>";
            log.info("微信支付回调异常:" + e.getMessage());
            e.printStackTrace();
        }

        resXml += "</xml> ";
        try {
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
        } catch (Exception e) {
            log.info("给微信返回状态时异常");
            e.printStackTrace();
        }
        log.info("支付回调通知微信的xml【" + resXml + "】");
        log.info("======================= 微信支付回调结束 =======================");
    }

验签

/**
     * 验证签名字符串
     *
     * @param map           需要签名的数据
     * @param key           微信商户支付密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static boolean verify(Map<String, String> map, String key, String input_charset) {

        //验证签名是否正确
        //回调验签时需要去除sign和空值参数
        Map<String, String> validParams = this.paraFilter(map);
        //把参数按规则排序拼接
        String prestr = this.createLinkString(validParams);

        String sign = map.get("sign");
        String mysign = this.sign(prestr, key, input_charset).toUpperCase();

        log.info("返回签名:" + sign + "  生成签名:" + mysign);

        if (mysign.equals(sign)) {
            log.info("支付回调返回数据签名验证通过!!!");
            return true;
        } else {
            return false;
        }
    }

去除无效值

/**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

至此整个微信小程序支付完成,如果有什么问题欢迎指出

微信小程序 退款 详细代码地址:微信退款代码地址

相关案例查看更多