微信支付

@啊湛详细记录app微信支付教程

使用前说明

主要技术:前端uni-app,后端spring-boot
1.
微信开放平台
微信开放平台是商户APP接入微信支付开放接口的申请入口,通过此平台可申请微信APP支付。
从开放平台中可以获取AppID和设置AppSecret
平台入口:open.weixin.qq.com
2.
微信商户平台
微信商户平台是微信支付相关的商户功能集合,包括参数配置、支付数据查询与统计、在线退款、代金券或立减优惠运营等功能。
从商户平台中可以获取证书,api密钥,商户ID(提现功能才会用到证书)
平台入口:pay.weixin.qq.com

3.(记录一个坑)
微信支付的金额单位为分,支付宝为元(充值0.01元,要先将元转为100分,若需要存入数据库,再*100转为0.01元)

准备工作可根据微信支付开发文档操作

一.
先导入依赖:

        <dependency>
            <groupId>cn.springboot</groupId>
            <artifactId>best-pay-sdk</artifactId>
            <version>1.3.3</version>
        </dependency>

二.
废话不多说,直接上代码

2.1
controller

/**
     *
     * 功能描述: 微信APP支付
     * @Return: java.lang.String
     * @Author: wangjvzhan
     * @Date: 2021/3/1
     */
    @RequestMapping("/WechatPay")
    public  String createSignAgain(@RequestBody WebDTO webDTO,HttpServletRequest request,String sn)throws Exception{
        
        //调用微信预支付接口方法
        Map<String, String> map = weixinPrePay(webDTO.getUserAccount(),webDTO.getMemberMoney(),webDTO.getTitleInfo(),webDTO.getPrepaidTime());
        JSONObject jsonObject = new JSONObject();
        SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
        Object prepay_id = map.get("prepay_id");
        //应用ID
        parameterMap.put("appid", PayCommonUtil.APPID);
        //商户号
		parameterMap.put("partnerid", PayCommonUtil.MCH_ID);
		//预支付交易会话ID
		parameterMap.put("prepayid",prepay_id );
		//扩展字段
		parameterMap.put("package", "Sign=WXPay");
        //随机字符串
		parameterMap.put("noncestr", PayCommonUtil.getRandomString(32));
		//时间戳
		parameterMap.put("timestamp", System.currentTimeMillis());
        //签名
		String sign = PayCommonUtil.createSign("UTF-8", parameterMap);
		parameterMap.put("sign", sign);
		jsonObject.put("parameterMap",parameterMap);
		return jsonObject.toString();
    }

2.2
由于这里我是将开通会员以及充值 余额写在一起的(通过titleInfo 区分),所以会有两个回调接口

/**
     *
     * @param userAccount 用户账号
     * @param totalAmount 价格
     * @param titleInfo 微信支付开通会员
     * @param prepaidTime 开通时长
     * @return
     * @throws Exception
     */
    public  Map<String, String> weixinPrePay(String userAccount,BigDecimal totalAmount,String titleInfo,String prepaidTime) throws Exception {
        SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
        //应用ID
        parameterMap.put("appid", PayCommonUtil.APPID);
        //商户号
        parameterMap.put("mch_id", PayCommonUtil.MCH_ID);
        //随机字符串
        parameterMap.put("nonce_str", PayCommonUtil.getRandomString(32));
        Map<String, Object> attachMap = new HashMap<>();
        attachMap.put("userAccount", userAccount);
        //商品订单号
        parameterMap.put("out_trade_no", PayCommonUtil.getUUID32());
        //货币类型
        parameterMap.put("fee_type", "CNY");
        //总金额,分为单位
        BigDecimal total = totalAmount.multiply(new BigDecimal(100));
        java.text.DecimalFormat df = new java.text.DecimalFormat("0");
        parameterMap.put("total_fee", df.format(total));
        //终端IP
        parameterMap.put("spbill_create_ip", "127.0.0.1");
        //回调地址
        if(titleInfo.equals("1")){
            //商品描述
            parameterMap.put("body","微信支付-开通会员");
            //充值时长
            attachMap.put("prepaidTime", prepaidTime);
            parameterMap.put("notify_url", "http://www.xyzhuanqian.com:8081/releasetask/pay/wxNotify");
        }else{
            parameterMap.put("body","微信支付-充值余额");
            parameterMap.put("notify_url", "http://www.xyzhuanqian.com:8081/releasetask/pay/wxNotify2");
        }
        String attachString = JSON.toJSONString(attachMap);
        //额外参数
        parameterMap.put("attach", attachString);
        //交易类型
        parameterMap.put("trade_type", "APP");
        //签名
        String sign = PayCommonUtil.createSign("UTF-8",parameterMap);
        parameterMap.put("sign", sign);
        String requestXML = PayCommonUtil.getRequestXml(parameterMap);
        System.out.println(requestXML);
        //post请求,接口路径
        String result = PayCommonUtil.httpsRequest(
                "https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
                requestXML);
        Map<String, String> map = null;
        try {
            map = PayCommonUtil.doXMLParse(result);
        } catch (JDOMException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return map;
    }

2.3
开通会员的回调就不上传了,差不多的,改下2.2的回调路径就好了

/**
     *
     * 功能描述: 充值微信回调
     * @Return: java.lang.String
     * @Author: wangjvzhan
     * @Date: 2021/3/2
     */
    @RequestMapping("/wxNotify2")
    public String wxNotify2(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.out.println("支付回调");
            return payService.callBack2(request, response);
        } catch (Exception e) {
            response.setHeader("Content-type", "application/xml");
            return "<xml>\n" +
                    "  <return_code><![CDATA[FAIL]]></return_code>\n" +
                    "  <return_msg><![CDATA[]]></return_msg>\n" +
                    "</xml>";
        }
    }

2.4
service实现层

@Override
    public String callBack2(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 预先设定返回的 response 类型为 xml
        response.setHeader("Content-type", "application/xml");
        // 读取参数,解析Xml为map
        Map<String, String> map = wxUtils.transferXmlToMap(wxUtils.readRequest(request));
        System.out.println(map);
        // 转换为有序 map,判断签名是否正确
        System.out.println("支付回调 转换有序map");
        String result_code=map.get("result_code");
        if (result_code.equals("SUCCESS")) {//充值成功
                 //以下为充值成功的业务操作
                return success();
            }else{
                return fail();
            }
        }else {
            // 签名校验失败(可能不是微信服务器发出的数据)
            return fail();
        }

    }

2.5
service中返回的成功或者失败方法(返回给微信的格式必须为xml)

public String fail() {
        return "<xml>\n" +
                "  <return_code><![CDATA[FAIL]]></return_code>\n" +
                "  <return_msg><![CDATA[]]></return_msg>\n" +
                "</xml>";
    }

    public String success() {
        return "<xml>\n" +
                "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                "</xml>";
    }

三.
工具类

public class PayCommonUtil{
                //这里就填写自己申请的参数
        //商户秘钥(APPsecret)
        public static String MCH_KEY="";
        //api秘钥
        public static String API_KEY="";
        //apiId
        public static String APPID="";
        //商户id
        public static String MCH_ID="";
        //证书路径(写存放证书的服务器路径)
        public static String CERT_PATH="";
        //随机字符串生成
        public static String getRandomString(int length) { //length表示生成字符串的长度
            String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            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();
        }
        //请求xml组装
        public static String getRequestXml(SortedMap<String,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 key = entry.getKey().toString();
                String value = entry.getValue().toString();
                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();
        }
        //生成签名
        public static String createSign(String characterEncoding, SortedMap<String, Object> parameters){
            StringBuffer sb = new StringBuffer();
            Set es = parameters.entrySet();
            Iterator it = es.iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String k = (String)entry.getKey();
                Object v = entry.getValue();
                if(null != v && !"".equals(v)
                        && !"sign".equals(k) && !"key".equals(k)) {
                    sb.append(k + "=" + v + "&");
                }
            }
            sb.append("key=" + API_KEY);
            String sign = MD5Utils.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
            return sign;
        }
        //请求方法
        public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
            try {

                URL url = new URL(requestUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.setUseCaches(false);
                // 设置请求方式(GET/POST)
                conn.setRequestMethod(requestMethod);
                conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
                // 当outputStr不为null时向输出流写数据
                if (null != outputStr) {
                    OutputStream outputStream = conn.getOutputStream();
                    // 注意编码格式
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }
                // 从输入流读取返回内容
                InputStream inputStream = conn.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                String str = null;
                StringBuffer buffer = new StringBuffer();
                while ((str = bufferedReader.readLine()) != null) {
                    buffer.append(str);
                }
                // 释放资源
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                inputStream = null;
                conn.disconnect();
                return buffer.toString();
            } catch (ConnectException ce) {
                System.out.println("连接超时:{}"+ ce);
            } catch (Exception e) {
                System.out.println("https请求异常:{}"+ e);
            }
            return null;
        }
        //退款的请求方法
        public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception {
            KeyStore keyStore  = KeyStore.getInstance("PKCS12");
            StringBuilder res = new StringBuilder("");
            FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12"));
            try {
                keyStore.load(instream, "".toCharArray());
            } finally {
                instream.close();
            }

            // Trust own CA and all self-signed certs
            SSLContext sslcontext = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, "1313329201".toCharArray())
                    .build();
            // Allow TLSv1 protocol only
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslcontext,
                    new String[] { "TLSv1" },
                    null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpclient = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .build();
            try {

                HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
                httpost.addHeader("Connection", "keep-alive");
                httpost.addHeader("Accept", "*/*");
                httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; 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) ");
                StringEntity entity2 = new StringEntity(outputStr , Consts.UTF_8);
                httpost.setEntity(entity2);
                System.out.println("executing request" + httpost.getRequestLine());

                CloseableHttpResponse response = httpclient.execute(httpost);

                try {
                    HttpEntity entity = response.getEntity();

                    System.out.println("----------------------------------------");
                    System.out.println(response.getStatusLine());
                    String text=null;
                    if (entity != null) {
                        System.out.println("Response content length: " + entity.getContentLength());
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
                        res.append(text);
                        while ((text = bufferedReader.readLine()) != null) {
                            res.append(text);
                            System.out.println(text);
                        }

                    }
                    EntityUtils.consume(entity);
                } finally {
                    response.close();
                }
            } finally {
                httpclient.close();
            }
            return res.toString();

        }
        //xml解析
        public static Map doXMLParse(String strxml) throws JDOMException, IOException {
            strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

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

            Map m = new HashMap();

            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 = getChildrenText(children);
                }

                m.put(k, v);
            }

            //关闭流
            in.close();

            return m;
        }

        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(getChildrenText(list));
                    }
                    sb.append(value);
                    sb.append("</" + name + ">");
                }
            }

            return sb.toString();
        }




    /**
     * 生成uuid32位,商品订单号
     * @return
     */
    public static String getUUID32(){
        return UUID.randomUUID().toString().replace("-", "").toLowerCase();
    }

}
@Component
public class WxUtils {

    /**
     * 执行 POST 方法的 HTTP 请求
     *
     * @param url
     * @param parameters
     * @return
     * @throws
     */
    public String executeHttpPost(String url, SortedMap<String, Object> parameters) throws IOException {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost request = new HttpPost(url);
        request.setHeader("Content-type", "application/xml");
        request.setHeader("Accept", "application/xml");
        request.setEntity(new StringEntity(transferMapToXml(parameters), "UTF-8"));
        HttpResponse response = client.execute(request);
        return readResponse(response);
    }


    /**
     * 第一次签名
     *
     * @param parameters 数据为服务器生成,下单时必须的字段排序签名
     * @param key
     * @return
     */
    public String createSign(SortedMap<String, Object> parameters, String key) throws NoSuchAlgorithmException {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        return encodeMD5(sb.toString());
    }

    /**
     * 第二次签名
     *
     * @param result 数据为微信返回给服务器的数据(XML 的 String),再次签名后传回给客户端(APP)使用
     * @param key    密钥
     * @return
     * @throws IOException
     */
    public Map createSign2(String result, String key) throws IOException, JDOMException, NoSuchAlgorithmException {
        SortedMap<String, Object> map = new TreeMap<>(transferXmlToMap(result));
        Map app = new HashMap();
        app.put("appid", map.get("appid"));
        app.put("partnerid", map.get("mch_id"));
        app.put("prepayid", map.get("prepay_id"));
        app.put("package", "Sign=WXPay");                   // 固定字段,保留,不可修改
        app.put("noncestr", map.get("nonce_str"));
        app.put("timestamp", new Date().getTime() / 1000);  // 时间为秒,JDK 生成的是毫秒,故除以 1000
        app.put("sign", createSign(new TreeMap<>(app), key));
        return app;
    }

    /**
     * 验证签名是否正确
     *
     * @return boolean
     * @throws Exception
     */
    public boolean checkSign(SortedMap<String, Object> parameters, String key) throws Exception {
        String signWx = parameters.get("sign").toString();
        System.out.println("+++++++++++"+signWx);
        if (signWx == null) return false;
        parameters.remove("sign"); // 需要去掉原 map 中包含的 sign 字段再进行签名
        String signMe = createSign(parameters, key);
        System.out.println("-----------"+signMe);
        return signWx.equals(signMe);
    }

    /**
     * 读取 request body 内容作为字符串
     *
     * @param request
     * @return
     * @throws IOException
     */
    public String readRequest(HttpServletRequest request) throws IOException {
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        inputStream = request.getInputStream();
        String str;
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        while ((str = in.readLine()) != null) {
            sb.append(str);
        }
        in.close();
        inputStream.close();
        return sb.toString();
    }

    /**
     * 读取 response body 内容为字符串
     */
    public String readResponse(HttpResponse response) throws IOException {
        BufferedReader in = new BufferedReader(
                new InputStreamReader(response.getEntity().getContent()));
        String result = new String();
        String line;
        while ((line = in.readLine()) != null) {
            result += line;
        }
        return result;
    }

    /**
     * 将 Map 转化为 XML
     *
     * @param map
     * @return
     */
    public String transferMapToXml(SortedMap<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        for (String key : map.keySet()) {
            sb.append("<").append(key).append(">")
                    .append(map.get(key))
                    .append("</").append(key).append(">");
        }
        return sb.append("</xml>").toString();
    }

    /**
     * 将 XML 转化为 map
     *
     * @param strxml
     * @return
     * @throws
     * @throws IOException
     */
    public Map transferXmlToMap(String strxml) throws IOException, JDOMException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = null;
        try {
            doc = builder.build(in);
        } catch (JDOMException e) {
            throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出
        }
        // 解析 DOM
        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;
    }

    // 辅助 transferXmlToMap 方法递归提取子节点数据
    private String getChildrenText(List<Element> children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator<Element> it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List<Element> list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }


    /**
     * 生成 32 位随机字符串,包含:数字、字母大小写
     *
     * @return
     */
    public String gen32RandomString() {
        char[] dict = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < 32; i++) {
            sb.append(String.valueOf(dict[(int) (Math.random() * 36)]));
        }
        return sb.toString();
    }

    /**
     * MD5 签名
     *
     * @param str
     * @return 签名后的字符串信息
     */
    public String encodeMD5(String str) throws NoSuchAlgorithmException {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] inputByteArray = (str).getBytes();
            messageDigest.update(inputByteArray);
            byte[] resultByteArray = messageDigest.digest();
            return byteArrayToHex(resultByteArray);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    // 辅助 encodeMD5 方法实现
    private String byteArrayToHex(byte[] byteArray) {
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] resultCharArray = new char[byteArray.length * 2];
        int index = 0;
        for (byte b : byteArray) {
            resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
            resultCharArray[index++] = hexDigits[b & 0xf];
        }
        // 字符数组组合成字符串返回
        return new String(resultCharArray);
    }
}

四:以上就是本人实现微信支付的代码与操作,后续会持续更新支付宝支付,支付宝提现,微信提现,微信授权登录等功能!

如有帮助,不妨点个赞再走吧…

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值