SpringBoot微信小程序支付/回调

SpringBoot微信小程序支付/回调

一、引入依赖
<dependency>
   <groupId>org.jdom</groupId>
   <artifactId>jdom</artifactId>
   <version>1.1.3</version>
</dependency>
<dependency>
   <groupId>com.github.binarywang</groupId>
   <artifactId>weixin-java-pay</artifactId>
    <version>4.3.9.B</version>
</dependency>		
二、配置文件
2.1、WxDataConfigure
@Component
@Slf4j
public class WxDataConfigure {

    /**小程序appId*/
    public static String appId = "";

    /**小程序的密码,此处不能写名称*/
    public static String 密码 = "";

    /**商户号*/
    public static String mch_id = "";

    /**商户支付秘钥V2*/
    public static String key = "";
    /**商户支付秘钥V3*/
    public static String keyV3 = "";

    /**退款用到*/
    public static String certUrl = "";

    /**商家转账到零钱*/
    public static String pemUrl = "";
    public static String privateKeyPath = "";
    public static String privateCertPath = "";
    public static String sn = "";

    /**商户证书序列号*/
    public static String serial_no = "";

    /**回调通知地址(需内网穿透测试)*/
    public static String notify_url = "";

    /**交易类型*/
    public static  String trade_type = "JSAPI";

    /**统一下单API接口链接*/
    public static String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**查询订单API接口链接*/
    public static String query_url = "https://api.mch.weixin.qq.com/pay/orderquery";

    /**退款接口*/
    public static String refund_url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    /**退款回调接口*/
    public static String refund_notify_url = "";

    /**商家转账到零钱*/
    public static String batches_url = "https://api.mch.weixin.qq.com/v3/transfer/batches";

    /**
     * 预支付
     * @return
     */
    public static WxPayService unifiedOrderWxPayService() {
        log.info("======================初始化微信支付接口服务开始======================");
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mch_id);
        payConfig.setMchKey(key);
        payConfig.setKeyPath(certUrl);
        payConfig.setTradeType(trade_type);

        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);

        log.info("======================初始化微信支付接口服务完成======================");
        return wxPayService;
    }

    /**
     * 退款
     * @return
     */
    public static WxPayService wxPayService() {
        //logger.info("======================初始化微信支付接口服务开始======================");
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mch_id);
        payConfig.setPrivateKeyPath(privateKeyPath);
        payConfig.setNotifyUrl(refund_notify_url);
        payConfig.setApiV3Key(keyV3);
        payConfig.setPrivateCertPath(privateCertPath);
        payConfig.setCertSerialNo(serial_no);

        payConfig.setMchKey(key);
        payConfig.setKeyPath(certUrl);
        payConfig.setTradeType(trade_type);

        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);

        //logger.info("======================初始化微信支付接口服务完成======================");
        return wxPayService;
    }
}
三、utils工具类
3.1、RandomStringGenerator生成随机数
public class RandomStringGenerator {

    /**
     * 获取一定长度的随机字符串
     *
     * @param length 指定字符串长度
     * @return 一定长度的字符串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

}
3.2、Signature 微信签名
public class Signature {

    /**
     * 签名算法
     *
     * @param o 要参与签名的数据对象
     * @return 签名
     * @throws IllegalAccessException
     */
    public static String getSign(Object o) throws IllegalAccessException {
        ArrayList<String> list = new ArrayList<String>();
        Class cls = o.getClass();
        Field[] fields = cls.getDeclaredFields();
        for (Field f : fields) {
            f.setAccessible(true);
            if (f.get(o) != null && f.get(o) != "") {
                String name = f.getName();
                XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
                if (anno != null) {
                    name = anno.value();
                }
                list.add(name + "=" + f.get(o) + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + Configure.key;
        System.out.println("签名数据:" + result);
        result = MD5.MD5Encode(result, "utf-8").toUpperCase();
        return result;
    }

    public static String getSign(Map<String, Object> map) {
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() != "") {
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + Configure.key;
        //Util.log("Sign Before MD5:" + result);
        result = MD5.MD5Encode(result, "utf-8").toUpperCase();
        //Util.log("Sign Result:" + result);
        return result;
    }
}
3.3、HttpRequest请求
public class HttpRequest {
    //连接超时时间,默认10秒
    private static final int socketTimeout = 10000;

    //传输超时时间,默认30秒
    private static final int connectTimeout = 30000;


    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url   发送请求的URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }


    /**
     * post请求
     *
     * @throws IOException
     * @throws ClientProtocolException
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws KeyManagementException
     * @throws UnrecoverableKeyException
     */
    public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException {


        HttpPost httpPost = new HttpPost(url);
        //解决XStream对出现双下划线的bug
        XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
        xStreamForRequestPostData.alias("xml", xmlObj.getClass());
        //将要提交给API的数据对象转换成XML格式数据Post给API
        String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
        System.out.println(postDataXML);
        //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);

        //设置请求器的配置
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
        httpPost.setConfig(requestConfig);

        HttpClient httpClient = HttpClients.createDefault();
        HttpResponse response = httpClient.execute(httpPost);
        HttpEntity entity = response.getEntity();
        String result = EntityUtils.toString(entity, "UTF-8");
        return result;
    }

    /**
     * http POST 请求
     *
     * @param url:请求地址
     * @param body:     body实体字符串
     * @param certPath: 证书路径
     * @param password: 证书密码
     * @return
     */
    public static String httpPostReflect(String url, String body, InputStream certPath, String password) {
        String xmlRes = "{}";
        HttpClient client = createSSLClientCert(certPath, password);
        HttpPost httpost = new HttpPost(url);
        try {
            //所有请求的body都需采用UTF-8编码
//            StringEntity entity = new StringEntity(body,"UTF-8");
//            httpost.setEntity(entity);
            //支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/json; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            //httpost.addHeader("Authorization",Configure.sn + " " + data);
            httpost.setEntity(new StringEntity(body, "UTF-8"));
            HttpResponse response = client.execute(httpost);
            //所有响应也采用UTF-8编码
            String result = EntityUtils.toString(response.getEntity(), "UTF-8");
            xmlRes = result;
        } catch (ClientProtocolException e) {
            System.out.println(e);
        } catch (UnknownHostException e) {
            System.out.println(e);
        } catch (IOException e) {
            System.out.println(e);
        }
        return xmlRes;
    }

    /**
     * 创建带证书的实例
     *
     * @param certPath
     * @return
     */
    public static CloseableHttpClient createSSLClientCert(InputStream certPath, String password) {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(certPath, password.toCharArray());
            certPath.close();
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                //信任所有
                public boolean isTrusted(X509Certificate[] chain,
                                         String authType) throws CertificateException {
                    return true;
                }
            }).loadKeyMaterial(keyStore, password.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (KeyManagementException e) {
            System.out.println(e);
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e);
        } catch (KeyStoreException e) {
            System.out.println(e);
        } catch (FileNotFoundException e) {
            System.out.println(e);
        } catch (Exception e) {
            System.out.println(e);
        }
        return HttpClients.createDefault();
    }

    /**
     * 自定义证书管理器,信任所有证书
     *
     * @author pc
     */
    public static class MyX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] arg0, String arg1)
                throws CertificateException {

        }

        @Override
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] arg0, String arg1)
                throws CertificateException {

        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }
}
三、实体类
3.1、OrderReturnInfo 订单返回实体类
@Data
public class OrderReturnInfo {

    private String return_code;

    private String return_msg;

    private String result_code;

    private String appid;

    private String mch_id;

    private String nonce_str;

    private String sign;

    private String prepay_id;

    private String trade_type;
}
四、后台方法
4.1、统一下单
	/***
     * 测试微信支付,回调等需要内网穿透测试
     * @param orderForm
     * @return
     */
    @ApiOperation(value = "统一下单", tags = "统一下单")
    @PostMapping(value = "/order")
    public Map<String,String> order(@RequestBody OrderForm orderForm){
        log.info("进入统一下单 START...");
        String id = UUIDGenerator.generate();

        try {
            OrderInfo order = new OrderInfo();
            order.setAppid(WxDataConfigure.appId);
            order.setMch_id(WxDataConfigure.mch_id);
            order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32));
            //避坑 body如果传中文,签名编码改为utf-8, 不然本地测通,线上提示签名错误
            order.setBody("");
            order.setOut_trade_no(id);
            //单位分
            order.setTotal_fee(orderForm.getSumPrice().multiply(new BigDecimal(100)).intValue());
            order.setSpbill_create_ip("127.0.0.1");
            order.setNotify_url(WxDataConfigure.notify_url);
            order.setTrade_type(WxDataConfigure.trade_type);
            order.setSign_type("MD5");
            //这里直接使用当前用户的openid
            order.setOpenid(orderForm.getOpenId());
            //生成签名
            String sign = Signature.getSign(order);
            order.setSign(sign);

            String result = HttpRequest.sendPost(WxDataConfigure.url, order);
            System.out.println(result);
            XStream xStream = new XStream();
            xStream.alias("xml", OrderReturnInfo.class);

            OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result);
            // 二次签名
            if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
                SignInfo signInfo = new SignInfo();
                signInfo.setAppId(WxDataConfigure.appId);
                long time = System.currentTimeMillis() / 1000;
                signInfo.setTimeStamp(String.valueOf(time));
                signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32));
                signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
                signInfo.setSignType("MD5");
                //生成签名
                String sign1 = Signature.getSign(signInfo);
                Map<String, String> payInfo = new HashMap<>();
                payInfo.put("timeStamp", signInfo.getTimeStamp());
                payInfo.put("nonceStr", signInfo.getNonceStr());
                payInfo.put("package", signInfo.getRepay_id());
                payInfo.put("signType", signInfo.getSignType());
                payInfo.put("paySign", sign1);
                payInfo.put("id",id);

                //订单信息放入redis, 回调再新增
                //todo
                log.info("统一下单成功 END");
                return payInfo;
            }
            log.info("统一下单失败 END");
            HashMap<String, String> map = new HashMap<>();
            map.put("code","201");
            map.put("msg","统一下单失败!");
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
4.2、回调方法(需内网穿透测试)

配置在WxDataConfigure中notify_url(回调通知地址)里

@PostMapping(value = "/notify")
    public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("进入微信支付回调.....");
        BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        //sb为微信返回的xml
        String notityXml = sb.toString();
        String resXml = "";
        System.out.println("接收到的报文:" + notityXml);

        Map map = PayUtil.doXMLParse(notityXml);

        String returnCode = (String) map.get("return_code");
        if ("SUCCESS".equals(returnCode)) {
            //验证签名是否正确
            //回调验签时需要去除sign和空值参数
            Map<String, String> validParams = PayUtil.paraFilter(map);
            //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
            String validStr = PayUtil.createLinkString(validParams);
            //拼装生成服务器端验证的签名
            String sign = PayUtil.sign(validStr, WxDataConfigure.key, "utf-8").toUpperCase();
            // 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了
            //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
            if (sign.equals(map.get("sign"))) {
                /** 业务相关逻辑  **/
                String id = map.get("out_trade_no").toString();
                // TODO 处理业务逻辑

                //通知微信服务器已经支付成功
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                System.out.println("微信支付回调失败!签名不一致");
            }
        } else {
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        System.out.println(resXml);
        System.out.println("微信支付回调数据结束");

        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }

一个在学习的开发者,勿喷,欢迎交流

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值