java版微信公众号支付

前言

今天是2020-4-1愚人节,好久没写博客了,今天准备写一篇微信公众号支付,刚好公司给了我账户,让我有参数测试,由于以前对于支付是小白,所以把这个功能打通花了2天,一天8小时,首先看官网文档,其次是其他朋友写的博客,总算流程走通了,以前这种代码稍微多点的功能,我都是直接把代码往git上放的,因为组织语言能力极差,所以很少写大篇幅的博客,今天可能是愚人节的缘故吧【由于工作原因,有可能一次写不完,我尽量写详细】

环境准备

首先得准备一个域名地址,外网能够访问你本地,我是在natapp上买的【这块不懂的朋友去百度】,假设我申请的域名地址是http://nat200.top

微信官网

这里的环境准备就是4个参数 appId(公众号id),appSecret(公众号Secret),mchId(商户id),mchKey(api秘钥)
以及支付授权目录和网页授权域名设置

商户中心

登录地址 https://pay.weixin.qq.com

mchId: 这个直接可以获取,不多说
mchKey:这个是设置一个秘钥: 账户中心--账户设置---api秘钥设置
			提供一种获取方式: https://suijimimashengcheng.51240.com/
支付授权目录添加

产品中心--> 开发配置 --> JSAPI支付
如何设置呢?我刚开始看文档的时候,以为是 微信内H5调起支付 页面的上一级,但是设置了不生效,后面换的是后端接口的地址
http://nat200.top/wxJsapi/
前面的是我域名,wxJsapi是我controller类上面的路径,提交订单的全路径是 http://nat200.top/wxJsapi/pay 在pay方法里处理完的返回的页面就是 微信内H5调起支付的所在页面

公众号中心

微信公众已认证的服务号,并且需要开通微信支付功能,必须企业才有资格申请

登录 https://mp.weixin.qq.com

这里获取2个【appId(公众号id),appSecret(公众号Secret)】参数值不多说,公司哪个申请的,问他就可以了

网页授权域名设置
公众号设置-->功能设置-->网页授权域名

这个地址如何写呢?就2点
1、下载 MP_verify_nsZ8KYsVMz282Qgq.txt 点击设置的时候,在弹出页面的那里 可以下载
2、假设我这里填写的域名地址是 http://nat200.top,那么我访问 http://nat200.top/MP_verify_nsZ8KYsVMz282Qgq.txt 可以调通就可以,所以你自己根据自己的情况去设置

环境大致就这些了,注意不要有权限验证

openID

官网说明
为什么获取openID还单独开个标题去说,因为简单点我们微信支付就是对接统一下单这个接口,这个接口的其他参数都好获取,就这个openID稍微困难点
这个一般是用户在注册的时候,就会获取到openID,保存在用户信息表里,每次调用支付接口时,后端可以根据发起的用户查询数据库获取到openID,但这里为了演示,我把获取openID的代码大概写下,具体项目怎么嵌入,自己根据实际情况去

我们看了文档应该知道,我们首先要获取code,在用code获取openID

首先获取code

我刚开始看的时候,就是不知道这些流程怎么一起整进来,我这里获取openID和支付是分开的,因为是为了走流程,所以先获取一个openID,就可以用这个openID测试支付了

我是直接在后台代码的controller直接定义了一个接口,你只要请求该接口,就会重定向获取code的url地址,这里也可以前端获取,真实情况我们是在用户注册的时候就会把这些做完,直接看接口代码

 /**
     * 前期测试获取code时使用
     */
    @GetMapping(value = "/getCode")
    public void getCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // redirect_uri 回调地址,该域名需要公众号验证
        String backUrl = "http://nat200.top/wxJsapi/getOpenID";
        // 拼接url
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appID"
                + "&redirect_uri=" + URLEncoder.encode(backUrl)
                + "&response_type=code"
                + "&scope=snsapi_base"
                + "&state=STATE#wechat_redirect";
        response.sendRedirect(url);
        // snsapi_base(静默授权,用户没有感知)  snsapi_userinfo
        /*
	    	1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
			2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
        */
    }

这里要强调的是redirect_uri对应的地址 1、首先要URLEncoder.encode对链接编码 2、此链接获取到code,之后,就会回调至你写的这个地址,我是把他回调到了/getOpenID这个接口中

下面看/getOpenID这个接口代码你就明白了

 	@GetMapping("/getOpenID")
    public  String getOpenID(HttpServletRequest request, ModelMap model) throws IOException {
        String code = request.getParameter("code");
        Map<String,Object> map = accessToken(code);
        String openId= map.get("openid").toString();
        log.info("获取用户的openId: {}", openId);
        model.put("openId",openId);
        // 我这里返回的是index.html页面,页面在通过 ${openId}获取到值,在传到后端,你可以不需要管,我只是为了一次性测试流程,你只要知// 道这里openID你就拿到了
        return "index";
    }

    public Map<String,Object> accessToken(String code) throws IOException {
        List<Object> list = new ArrayList<Object>();
        String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=你的APPID"
                + "&secret=你的公众号secret"
                + "&code="+code
                + "&grant_type=authorization_code";
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost(url);
        HttpResponse res = client.execute(post);
        Map<String,Object> map = null;
        if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            HttpEntity entity = res.getEntity();
            String str = EntityUtils.toString(entity, "utf-8");
            ObjectMapper mapper=new ObjectMapper();
            map = mapper.readValue(str, Map.class);
            log.info("获取openID返回结果:{}", JSON.toJSONString(map));
        }
        return map;
    }

获取openID返回的数据格式如下

	{ 
	  "access_token":"ACCESS_TOKEN",
	  "expires_in":7200,
	  "refresh_token":"REFRESH_TOKEN",
	  "openid":"OPENID",
	  "scope":"SCOPE" 
	}

在这里为止,openID已经有了,下面我开始走支付,我会在下面说下,我测试的整个流程是怎么走的,实际怎么嵌入要看项目的

统一下单

我测试的流程如下,再次强调 ,完全是走流程,没有结合业务

1、 首先调用 /getCode 这个接口 
2、 会跳转到 /getOpenID 这个接口 
3、 处理完会跳转到 index.html页面
4、 页面输入价格,点击提交 
5、 会进入后端 /pay接口 --> 
6、 处理完,会跳转至 微信内H5调起支付 所在页面
7、 付款支付,根据状态做出不同操作 --> 完

下面主要是代码
首先把官网的 Java版SDK下载,只用里面的 WXPayXmlUtil.java和WXPayUtil.java 2个工具类,其他的看得太累

忘记贴maven依赖了

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

往WXPayUtil.java 加入一个方法【支付回调触发时解析用的】,代码如下,这里直接拷贝网上的

public static String inputStream2String(InputStream inStream, String encoding){
        String result = null;
        ByteArrayOutputStream outStream = null;
        try {
            if(inStream != null){
                outStream = new ByteArrayOutputStream();
                byte[] tempBytes = new byte[1024];
                int count = 0;
                while((count = inStream.read(tempBytes)) != -1){
                    outStream.write(tempBytes, 0, count);
                }
                tempBytes = null;
                outStream.flush();
                result = new String(outStream.toByteArray(), encoding);
                outStream.close();
            }
        } catch (Exception e) {
            result = null;
        }
        return result;
    }

下面看 index.html 的代码

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf8">
    <title>微信支付</title>
</head>
<body>
<h2>微信订单支付</h2>
<form action="http://nat200.top/wxJsapi/pay" method="post">
    <div>
        <label>商品价格</label>
        <input name="amount" value=""/>
    </div>
    <div>
        <label>openId</label>
        <input name="openId" th:value="${openId}"/>
    </div>
    <input type="submit"/>
</form>
</body>
</html>

看了这个,就该看 接口 /pay 的代码了

 	@PostMapping(value = "/pay")
    public String wxPluginSubmit(@RequestParam("amount") Integer amount,@RequestParam("openId")String openId,
                                 HttpServletRequest request, Model model) {
        // 1 获取此时的用户信息,从用户信息里面获取到 openId [用户注册的时候直接绑定]
        // 2 保存订单信息
        String orderNo = System.currentTimeMillis() + "";
        // 3 传入订单编号,拼装参数发送请求
        // 测试,先写成固定的
        try {
            model.addAttribute("parameterMap",wxPayService.getParameterMap(orderNo, request,openId,amount));
        }catch (BusinessException be){
            log.error(be.getMsg());
            // 返回error页面
        }catch (Exception e){
            // 返回error页面
        }
        return "wxpay";
    }

接下来看 wxPayService.getParameterMap 接口实现的代码

 	@Override
    public Map<String, String> getParameterMap(String orderNo, HttpServletRequest request,String openId,Integer amount) {
        // 2 生成ip
        String ip = IpUtil.getIp(request);
        // 4 定义一个集合
        Map<String, String> packageParams = new HashMap<>();
        packageParams.put("appid", wechatAccountConfig.getAppId());
        packageParams.put("mch_id", wechatAccountConfig.getMchId());
        packageParams.put("nonce_str", WXPayUtil.generateNonceStr());
        packageParams.put("body", Constants.BODY);
        packageParams.put("out_trade_no", orderNo);
        packageParams.put("notify_url", wechatAccountConfig.getNotifyUrl());// 回调地址
        packageParams.put("openid", openId);
        packageParams.put("spbill_create_ip", ip);
        packageParams.put("sign_type", "MD5");
        packageParams.put("total_fee",amount.toString());
        packageParams.put("trade_type", "JSAPI"); // 交易类型 JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付

        // 生成签名sign
        String sign;
        try {
            sign = WXPayUtil.generateSignature(packageParams, wechatAccountConfig.getMchKey());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("生成签名失败: {}" + JSON.toJSONString(packageParams));
            throw new BusinessException(ResultEntity.FAIL_CODE,"生成签名失败");
        }

        packageParams.put("sign", sign);
        String prepayXml;
        try {
             prepayXml = WXPayUtil.mapToXml(packageParams);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("map生成字符串出错: {}" + JSON.toJSONString(packageParams));
            throw new BusinessException(ResultEntity.FAIL_CODE,"map转化成字符串出错");
        }
        // 调用接口获取 预支付id
        log.info("发送统一请求接口参数,{}",JSON.toJSONString(packageParams));
        String xmlStr = HttpUtil.sendPost(Constants.WXPAY_UNIFIEDORDER_GATEWAY, prepayXml);//发送post请求"统一下单接口
        if (null == xmlStr || xmlStr.indexOf(Constants.FAIL) != -1) {
            log.error("统一下单返回报文:{}" ,xmlStr);
            throw new BusinessException(ResultEntity.FAIL_CODE,"发送统一下单接口异常");
        }
        Map<String, String> map = null;
        try {
            map = WXPayUtil.xmlToMap(xmlStr);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ResultEntity.FAIL_CODE,"map转化成字符串出错");
        }
        // 获取预支付交易会话标识
        String prepay_id = map.get("prepay_id");
        /**
         *  以下内容是返回前端页面的json数据
          */
        Map<String, String> payMap = new HashMap<>();
        payMap.put("appId", wechatAccountConfig.getAppId());
        payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
        payMap.put("nonceStr",  WXPayUtil.generateNonceStr());
        payMap.put("signType", "MD5");
        payMap.put("package", "prepay_id=" + prepay_id);
        String paySign;
        try {
            paySign = WXPayUtil.generateSignature(payMap, wechatAccountConfig.getMchKey());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("封装前台数据签名时出错: {}", JSON.toJSONString(payMap));
            throw new BusinessException(ResultEntity.FAIL_CODE,"封装前台数据签名时出错");
        }
        payMap.put("paySign", paySign);
        // package在jsp属于关键字【但是参与签名的名字必须是 package ,切记切记】,所以改名为packageValue,不过我后续改为 HTML了
        payMap.put("packageValue", "prepay_id=" + prepay_id);
        // 返回订单号
        payMap.put("no", orderNo);
        log.info("返回前端的支付数据: {}" , JSON.toJSONString(payMap));
        return payMap;
    }

这个方法一出,工具类就多了,下面依次贴出来

public class IpUtil {
    /**
     * 获取ip
     */
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }
}

常量配置类

public class Constants {

    /**
     *  商品描述
     */
    public static String BODY = "";

    /**
     * 签名类型
     */
    public enum SignType {
        MD5, HMACSHA256
    }

    /**
     * 签名字段名称
     */
    public static final String FIELD_SIGN = "sign";

    /**
     * 统一下单
     */
    public static final String WXPAY_UNIFIEDORDER_GATEWAY = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 查询地址
     */
    public static final String WXPAY_ORDERQUERY_GATEWAY = "https://api.mch.weixin.qq.com/pay/orderquery";
    /**
     * 关闭订单
     */
    public static final String WXPAY_CLOSEORDER_GATEWAY = " https://api.mch.weixin.qq.com/pay/closeorder";

    /**
     * 回复微信的消息
     */
    public static final String NOTIFY_RESPONSE_BODY = "<xml>\n" +
            "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
            "  <return_msg><![CDATA[OK]]></return_msg>\n" +
            "</xml>";

    /**
     * 失败
     */
    public static final String FAIL = "FAIL";
    /**
     * 成功
     */
    public static final String SUCCESS = "SUCCESS";

}

发送请求的,都是网上拷贝的,拷来拷去,也是为了快点实现流程,也没有管是哪个朋友写的了

@Slf4j
public class HttpUtil {

    /**
     * 向指定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;
            System.out.println(urlNameString);
            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;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
           log.error("发送 POST 请求出现异常",e);
            e.printStackTrace();
            return  null;
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }
}

几个重要的参数类

@Data
@ConfigurationProperties(prefix = "wechat")
@Component
public class WechatAccountConfig {
	
	// 这些值要到配置文件里面定义,是springboot就知道的

    /**
     * 公众账号ID
     */
    private String appId;

    /**
     * 公众号Secret
     */
    private String appSecret;

    /**
     * 商户号
     */
    private String mchId;

    /**
     * 商户密钥
     */
    private String mchKey;

    /**
     * 微信公众号支付异步通知地址
     */
    private String notifyUrl;

}

/pay接口用到的都拷贝完了,这个接口处理完会跳转到wxpay.html里面,下面看这里的代码,这里的获取后端的值只是一种笨的方式,当时急于测试通过就没管了,后面想改,环境又变了,就算了,这不是重点,前面用的是jsp,但是同时兼容HTML和jsp还加入了一些配置类,就直接换掉了

<!DOCTYPE html>
<html>
  <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>微信网页支付</title>
            <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
      </head>
  <body>
<div hidden>
    <span th:text="${parameterMap.appId}" id="appId"></span>
    <span th:text="${parameterMap.timeStamp}" id="timeStamp"></span>
    <span th:text="${parameterMap.nonceStr}" id="nonceStr"></span>
    <span th:text="${parameterMap.packageValue}" id="packageValue"></span>
    <span th:text="${parameterMap.paySign}" id="paySign"></span>
    <span th:text="${parameterMap.no}" id="no"></span>
</div>
  <script type="text/javascript">
    function cancel(order) {
         $.ajax({
             type: "POST",
             url: "http://nat200.to/wxJsapi/wxPayClose",
             data: {
                 "orderNo": order
             },
             success: function (data) {
                 if(data.code === '200'){
                     alert("关闭成功");
                 }
             }
         });
    }
    //支付接口
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                "appId" :$("#appId").text(),
                "timeStamp" : $("#timeStamp").text(),
                "nonceStr" : $("#nonceStr").text(),
                "package" : $("#packageValue").text(),
                "signType" : "MD5",
                "paySign" : $("#paySign").text()
            },
            function(res){
                //使用以下方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回  ok,但并不保证它绝对可靠。
                if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                    alert("您已经支付成功");
                } else if(res.err_msg == "get_brand_wcpay_request:cancel") {
                    alert("您已经放弃了本次支付");
                    // 用户点击关闭,调用后端接口
                    cancel($("#no").text())
                } else if(res.err_msg == "get_brand_wcpay_request:fail") {
                    alert("支付失败");
                }
            }
        );
    }
    if (typeof(WeixinJSBridge) == "undefined"){
        if( document.addEventListener ){
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        }else if (document.attachEvent){
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    }else{
        onBridgeReady();
    }
</script>
  </body>
</html>

顺便说一下,我这里springboot返回到HTML页面的配置,maven加入

		 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

yml配置文件加入

spring:
  thymeleaf:
    prefix: classpath:/static/ # html页面文件
    suffix: .html
    cache: false #关闭缓存

html页面放在 main/webapp/static

回调接口

 @RequestMapping("/notify")
    public void notify(HttpServletRequest request, HttpServletResponse response){
          InputStream is = null;
        try {
            is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
            String xml = WXPayUtil.inputStream2String(is, "UTF-8");
            Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
            log.info("支付回调返回的数据:{}", JSON.toJSONString(notifyMap));
            // 验签返回的数据
            if (WXPayUtil.generateSignature(notifyMap, wechatAccountConfig.getMchKey()).equals(notifyMap.get("sign")) && wechatAccountConfig.getMchId().equals(notifyMap.get("mch_id"))
                    && ("JSAPI".equals(notifyMap.get("trade_type")))) {
                if(notifyMap.get("return_code").equals(Constants.SUCCESS)){
                    log.info("支付回调结果校验成功");
                    // 如果有必要可以判断金额是否匹配
                    String orderNo = notifyMap.get("out_trade_no"); //商户订单号
                    String amount = notifyMap.get("total_fee"); //实际支付的订单金额:单位 
                    // TODO 处理项目逻辑,比如发送消息修改订单状态
                    //orderStatusSend.delaySend("",2000);
                }else {
                    log.info("支付失败:{}",JSON.toJSONString(notifyMap));
                }
                 response.getWriter().write(Constants.NOTIFY_RESPONSE_BODY);
                /*BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(Constants.NOTIFY_RESPONSE_BODY.getBytes());
                out.flush();
                out.close();*/
            }else{
                log.info("支付回调结果校验失败===============================");
             
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
     
    }

对微信支付也是个小白,所以将就着看吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值