java微信退款unexpected end of file from server

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xb12369/article/details/51602153

首先抛出一个问题 unexpected end of file from server

相信很多人都会遇到这个问题,因为只要你做过微信支付然后再来做退款的时候就会习惯性地把支付的源码直接拿过来用,然后开始退款的时候就报错了~


问题的原因在于:支付的时候,你是把钱给微信,那么怎么简单怎么弄,现在你要拿出来,那得做点什么了~

支付不需要证书,退款需要双向证书!




ok,开始去商户平台下载证书java用这个 apiclient_cert.p12就好了,他需要几个jar包~
httpclient-4.3.4.jar,httpcore-4.3.2.jar

https://yunpan.cn/cRfFTTUKIWx3x  访问密码 774e (根据自己的需要开始下载)

然后就是关键代码了,普通的可能就是直接打开Http地址就好了,带上证书,你得这么操作;

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package com.base;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import codeGenerate.util.ConfProperties;

/**
 * This example demonstrates how to create secure connections with a custom SSL
 * context.
 */
public class ClientCustomSSL {

    public static String doRefund(String url,String data) throws Exception {
    	/**
    	 * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
    	 */
    	
        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        FileInputStream instream = new FileInputStream(new File(ConfProperties.getSSLPath()));//P12文件目录
        try {
        	/**
        	 * 此处要改
        	 * */
            keyStore.load(instream, "你的MCHID".toCharArray());//这里写密码..默认是你的MCHID
        } finally {
            instream.close();
        }

        // Trust own CA and all self-signed certs
        /**
    	 * 此处要改
    	 * */
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, "你的MCHID".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(url); // 设置响应头信息
        	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) ");
    		httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();

                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
               return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }

}

说明:1、修改你 证书的地址(这里微信说需要安装一下,其实装不装无所谓,因为Linux上你可能就不知道怎么安装,直接放到一个具有管理员权限的位置,并且不能呗下载到,避免被恶意操作)

2、修改 两处密码,填你们公司的mch_id

调用的代码:
//构造xml参数
							String xmlInfo = WeixinPay.xmlInfo(refund);
							String wxUrl = WeixinPay.getRefundURL;
							String weixinPost = ClientCustomSSL.doRefund(wxUrl, xmlInfo).toString();
							System.out.println(weixinPost);

							WXPayRefundResult refundResult = WeixinPay.getUnifiedorderResult(weixinPost);

							if("SUCCESS".equalsIgnoreCase(refundResult.getResult_code())){
								result = "200";
								System.out.println("==========处理退款成功==========");
							}else{
								result = refundResult.getReturn_msg();
							}


说明:之前做支付的时候是这么调用的:String weixinPost = HttpXmlUtils.httpsRequest(wxUrl, method, xmlInfo).toString();

现在换成String weixinPost = ClientCustomSSL.doRefund(wxUrl, xmlInfo).toString();


到这里差不多就能正常退款了!



然后新问题来了:一个订单1元一个,同时购买7个,退款的时候退5元,我擦
又报错了:同一个out_refund_no退款金额要一致

那是因为out_refund_no这个参数我用的是商品的id,因为如果你不做处理,微信认为你还可能下一次再来退款,那么已经用当前的id退款,再来的话,微信可能也不好处理,这个时候追加一个随机数就好了~相比支付的时候,不需要回调这里就简单多了~

String out_refund_no = applyReturn.getItemId()+CommonUtils.getRandByNum(6);
/**
	 * 产生num位的随机数
	 * @return
	 */
	public static String getRandByNum(int num){
		String length = "1";
		for(int i=0;i<num;i++){
			length += "0";
		}

		Random rad=new Random();

		String result  = rad.nextInt(Integer.parseInt(length)) +"";

		if(result.length()!=num){
			return getRandByNum(num);
		}
		return result;
	} 

上几张图片吧:



源码:
//微信

						if(trans.getListSplitTrans()!=null&&trans.getListSplitTrans().size()>0){
							Item item = this.itemService.findItemById(applyReturn.getItemId());

							if(item!=null){
								//这里是下单后并未直接支付,而是后面拆单后一个一个地支付
								for (SplitTrans split : trans.getListSplitTrans()) {
									if(split.getSellerId().equals(item.getUserId())){
										if(split.getPm()==applyReturn.getPm()){
											outCode = split.getOutCode();
											finalPay = split.getFinalpay().intValue();
										}else{
											result = "提现中的支付方式跟交易流水中的支付方式不一致!!";
										}
										break;
									}
								}
							}else{
								result = "退款中的商品不存在~";
							}
						}else if(!StringUtils.isEmpty(trans.getOutCode())){
							if(trans.getPm()==applyReturn.getPm()){
								outCode = trans.getOutCode();
								finalPay = trans.getFinalpay().intValue();

							}else{
								result = "提现中的支付方式跟交易流水中的支付方式不一致!!";
							}
						}

						if(StringUtils.isEmpty(result)){
							String appid = WeixinPay.appid;
							String mch_id = WeixinPay.mch_id;
							String nonce_str = WeixinPay.getRandomString(32);
							String transaction_id = outCode;
							String out_refund_no = applyReturn.getItemId()+CommonUtils.getRandByNum(6);
							int total_fee = finalPay*100;//单位为分
							int refund_fee = (int)applyReturn.getMoney()*100;
							String op_user_id = WeixinPay.mch_id;//操作员帐号, 默认为商户号

							SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
							parameters.put("appid", appid);
							parameters.put("mch_id", mch_id);
							parameters.put("nonce_str", nonce_str);
							parameters.put("transaction_id", transaction_id);
							parameters.put("out_refund_no", out_refund_no);
							parameters.put("total_fee", total_fee);
							parameters.put("refund_fee", refund_fee);
							parameters.put("op_user_id", op_user_id);

							String sign = WeixinPay.createSign("UTF-8", parameters);
							System.out.println("签名是:"+sign);

							WXPayRefund refund  = new WXPayRefund();
							refund.setAppid(appid);
							refund.setMch_id(mch_id);
							refund.setNonce_str(nonce_str);
							refund.setOp_user_id(op_user_id);
							refund.setOut_refund_no(out_refund_no);
							refund.setRefund_fee(refund_fee);
							refund.setSign(sign);
							refund.setTotal_fee(total_fee);
							refund.setTransaction_id(transaction_id);

							//构造xml参数
							String xmlInfo = WeixinPay.xmlInfo(refund);
							String wxUrl = WeixinPay.getRefundURL;
							String weixinPost = ClientCustomSSL.doRefund(wxUrl, xmlInfo).toString();
							System.out.println(weixinPost);

							WXPayRefundResult refundResult = WeixinPay.getUnifiedorderResult(weixinPost);

							if("SUCCESS".equalsIgnoreCase(refundResult.getResult_code())){
								result = "200";
								System.out.println("==========处理退款成功==========");
							}else{
								result = refundResult.getReturn_msg();
							}
						}

核心类:WeixinPay
package com.base;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;

import javax.net.ssl.HttpsURLConnection;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.xml.sax.InputSource;

import codeGenerate.util.MD5Util;

import com.base.entity.WXPayRefund;
import com.base.entity.WXPayRefundResult;

/**
 * 商户微信支付配置
 * @author iYjrg_xiebin
 * @date 2016年5月24日下午2:52:25
 */
public class WeixinPay {
	//http://mch.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3
	//商户Key:改成公司申请的即可
	//32位密码设置地址:http://www.sexauth.com/  12341hvufnm1sdcb0e81t36k0d0f15nc
	private static String Key = "32位私钥";//改成你们的私钥(密码)


	/**
	 * 配置参数
	 */
	public static String appid = "你们的appId";
	public static String mch_id = "你们的mch_id";
	public static String getRefundURL = "https://api.mch.weixin.qq.com/secapi/pay/refund";

	/**
	 * 随即字符串
	 * @param length
	 * @return
	 */
	public static String getRandomString(int length) { //length表示生成字符串的长度
		String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";   
		Random random = new Random();   
		StringBuffer sb = new StringBuffer();
		int number = 0;
		for (int i = 0; i < length; i++) {   
			number = random.nextInt(base.length());   
			sb.append(base.charAt(number));   
		}   
		return sb.toString();   
	} 
	
	/**
	 * 微信支付签名算法sign
	 * @param characterEncoding
	 * @param parameters
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
		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);
		System.out.println("字符串拼接后是:"+sb.toString());
		String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
		return sign;
	}
	
	/**
	 * 开始退款
	 * 构造xml参数
	 * @param xml
	 * @return
	 */
	public static String xmlInfo(WXPayRefund refund){
		//构造xml参数的时候,至少又是个必传参数
		/*
		 * <xml>
			   <appid>wx2421b1c4370ec43b</appid>
			   <mch_id>10000100</mch_id>
			   <nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str>
			   <op_user_id>10000100</op_user_id>
			   <out_refund_no>1415701182</out_refund_no>
			   <out_trade_no>1415757673</out_trade_no>
			   <refund_fee>1</refund_fee>
			   <total_fee>1</total_fee>
			   <transaction_id></transaction_id>
			   <sign>FE56DD4AA85C0EECA82C35595A69E153</sign>
			</xml>
		 */

		if(refund!=null){
			StringBuffer bf = new StringBuffer();
			bf.append("<xml>");

			bf.append("<appid><![CDATA[");
			bf.append(refund.getAppid());
			bf.append("]]></appid>");

			bf.append("<mch_id><![CDATA[");
			bf.append(refund.getMch_id());
			bf.append("]]></mch_id>");

			bf.append("<nonce_str><![CDATA[");
			bf.append(refund.getNonce_str());
			bf.append("]]></nonce_str>");

			bf.append("<sign><![CDATA[");
			bf.append(refund.getSign());
			bf.append("]]></sign>");

			bf.append("<op_user_id><![CDATA[");
			bf.append(refund.getOp_user_id());
			bf.append("]]></op_user_id>");

			bf.append("<transaction_id><![CDATA[");
			bf.append(refund.getTransaction_id());
			bf.append("]]></transaction_id>");

			bf.append("<out_refund_no><![CDATA[");
			bf.append(refund.getOut_refund_no());
			bf.append("]]></out_refund_no>");

			bf.append("<total_fee><![CDATA[");
			bf.append(refund.getTotal_fee());
			bf.append("]]></total_fee>");

			bf.append("<refund_fee><![CDATA[");
			bf.append(refund.getRefund_fee());
			bf.append("]]></refund_fee>");

			bf.append("</xml>");
			return bf.toString();
		}

		return "";
	}
	
	/**
	 * post请求并得到返回结果
	 * @param requestUrl
	 * @param requestMethod
	 * @param output
	 * @return
	 */
	public static String httpsRequest(String requestUrl, String requestMethod, String output) {
		try{
			URL url = new URL(requestUrl);
			HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			connection.setUseCaches(false);
			connection.setRequestMethod(requestMethod);
			if (null != output) {
				OutputStream outputStream = connection.getOutputStream();
				outputStream.write(output.getBytes("UTF-8"));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = connection.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;
			connection.disconnect();
			return buffer.toString();
		}catch(Exception ex){
			ex.printStackTrace();
		}

		return "";
	}
	
	/**
	 * 解析退款申请
	 * 解析的时候自动去掉CDMA
	 * @param xml
	 */
	@SuppressWarnings("unchecked")
	public static WXPayRefundResult getUnifiedorderResult(String xml){
		WXPayRefundResult unifieorderResult = new WXPayRefundResult();
		try { 
			StringReader read = new StringReader(xml);
			// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
			InputSource source = new InputSource(read);
			// 创建一个新的SAXBuilder
			SAXBuilder sb = new SAXBuilder();
			// 通过输入源构造一个Document
			Document doc;
			doc = (Document) sb.build(source);

			Element root = doc.getRootElement();// 指向根节点
			List<Element> list = root.getChildren();

			if(list!=null&&list.size()>0){
				for (Element element : list) {
					System.out.println("key是:"+element.getName()+",值是:"+element.getText());
					if("return_code".equals(element.getName())){
						unifieorderResult.setReturn_code(element.getText());
					}
					
					if("return_msg".equals(element.getName())){
						unifieorderResult.setReturn_msg(element.getText());
					}
					
					if("result_code".equals(element.getName())){
						unifieorderResult.setResult_code(element.getText());
					}
				}
			}

		} catch (JDOMException e) {
			e.printStackTrace();
		}  catch (IOException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		return unifieorderResult;
	}

}

============================无私的世界,啥都共享==================================================
老司机开车完毕~~



说明:其它的方法,签名什么的我之前已经提供过了,这里就不重复了~

good luck

来了:
到这里差不多就能正常退款了!

猜你喜欢

转载自blog.csdn.net/xb12369/article/details/51602153