微信小程序-微信支付退款

微信小程序-微信支付退款

官方接口文档及相关附件

申请退款

SDK

错误集锦

  • 调用该https://api.mch.weixin.qq.com/secapi/pay/refund接口需要应程序安装API证书才可以,否则会提示以下错误:

    <head><title>400 No required SSL certificate was sent</title></head>
    <body bgcolor="white">
    <center><h1>400 Bad Request</h1></center>
    <center>No required SSL certificate was sent</center>
    <hr><center>nginx</center>
    </body>
    </html>
    

    根据官方文档中介绍的证书下载及安装方式,由于本人是Windows环境下进行开发,所以按照它的介绍,下载下来apiclient_cert.p12证书之后,直接双击安装即可。但安装成功之后,请求还是报错。没办法,调试代码吧。具体调试过程如下:

    第一步:接受客户端发送的退款请求

    // 业务处理步骤省略……
    // 发起微信退款
    WXPay wxPay = new WXPay(wxPayConfig); // WXPay为微信官方提供的SDK所在类,官方SDK为com.github.wxpay.sdk,链接已在上面给出,需要的童鞋可以去下载。
    Map<String, String> res = wxPay.refund(redundData); // redundData为退款申请数据
    

    第二步:调试wxPay.refund方法,发现会走到这个方法requestWithCert

    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
            String url;
            if (this.useSandbox) {
                url = "/sandboxnew/secapi/pay/refund";
            } else {
                url = "/secapi/pay/refund";
            }
    
            String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
            return this.processResponseXml(respXml);
        }
    
    public String requestWithCert(String urlSuffix, Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
            String msgUUID = (String)reqData.get("nonce_str");
            String reqBody = WXPayUtil.mapToXml(reqData);
            String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
            return resp;
        }
    

    第三步:调试wxPayRequest.requestWithCert方法

    public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
            return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
        }
    
    

    上面方法又调用了自己的request方法

    private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
            Exception exception = null;
            long elapsedTimeMillis = 0L;
            long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
            boolean firstHasDnsErr = false;
            boolean firstHasConnectTimeout = false;
            boolean firstHasReadTimeout = false;
            DomainInfo domainInfo = this.config.getWXPayDomain().getDomain(this.config);
            if (domainInfo == null) {
                throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
            } else {
                try {
                    String result = this.requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
                    elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
                    this.config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, (Exception)null);
                    WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
                    return result;
                } catch (UnknownHostException var18) {
                    exception = var18;
                    firstHasDnsErr = true;
                    elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
                    WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
                    WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
                } catch (ConnectTimeoutException var19) {
                    exception = var19;
                    firstHasConnectTimeout = true;
                    elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
                    WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
                    WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
                } catch (SocketTimeoutException var20) {
                    exception = var20;
                    firstHasReadTimeout = true;
                    elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
                    WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
                    WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
                } catch (Exception var21) {
                    exception = var21;
                    elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
                    WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
                }
    
                this.config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, (Exception)exception);
                throw (Exception)exception;
            }
        }
    

    上面方法又调用了同类的requestOnce方法

    private String requestOnce(String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
            BasicHttpClientConnectionManager connManager;
            if (useCert) {
                char[] password = this.config.getMchID().toCharArray();
                InputStream certStream = this.config.getCertStream();
                KeyStore ks = KeyStore.getInstance("PKCS12");
                ks.load(certStream, password);
                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmf.init(ks, password);
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(kmf.getKeyManagers(), (TrustManager[])null, new SecureRandom());
                SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, (String[])null, new DefaultHostnameVerifier());
                connManager = new BasicHttpClientConnectionManager(RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build(), (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
            } else {
                connManager = new BasicHttpClientConnectionManager(RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(), (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
            }
    
            HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
            String url = "https://" + domain + urlSuffix;
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
            httpPost.setConfig(requestConfig);
            StringEntity postEntity = new StringEntity(data, "UTF-8");
            httpPost.addHeader("Content-Type", "text/xml");
            httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + this.config.getMchID());
            httpPost.setEntity(postEntity);
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            return EntityUtils.toString(httpEntity, "UTF-8");
        }
    

    可以发现,证书最终是通过该方法进行导入的。具体导入是由WXPayConfig类进行操作的,该类是在创建WXPay对象时传入的,一般我们都会根据自己的业务场景写一个该类的子类,由于在重写该类方法时,getCertStream方法直接返回了null,所以即使证书安装了,也会提示证书无法找到的错误。

    问题解决:

    1. 将下载好的证书放入工程目录下,注意该证书文件应放在有访问权限控制的目录中,防止被他人下载。

    2. 重写WXPayConfig的getCertStream方法

      private byte[] certData;
      
      @Override
      public InputStream getCertStream() {
          ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
          return null;
      }
      
      public MyWXPayConfig() throws Exception {
          URI uri = this.getClass().getClassLoader().getResource("cert/apiclient_cert.p12").toURI();
          String certPath =uri.getPath();
          File file = new File(certPath);
          InputStream certStream = new FileInputStream(file);
          this.certData = new byte[(int) file.length()];
          certStream.read(this.certData);
          certStream.close();
      }
      
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值