微信支付功能做了太多,今天又做了支付、退款、查询、提现等等,顺便把支付和退款代码贴出来,希望对初学者有点帮助。

首先调用微信支付退款 API 地址 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

案例代码

支付退款工具类

用于支付参数中的 nonce_str 字段赋值

/*** 生产32位随机数**/public static String getUUID(){UUID uuid = UUID.randomUUID();return uuid.toString().replace("-","");}

用于支付参数中的 spbill_create_ip 字段赋值

 /*** 获取服务器ip**/public static String getHostIp(){try{Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();while (allNetInterfaces.hasMoreElements()){NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();Enumeration<InetAddress> addresses = netInterface.getInetAddresses();while (addresses.hasMoreElements()){InetAddress ip = (InetAddress) addresses.nextElement();if (ip != null&& ip instanceof Inet4Address&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255&& ip.getHostAddress().indexOf(":")==-1){return ip.getHostAddress();}}}}catch(Exception e){e.printStackTrace();}return null;}

用于生成支付签名 sign字段赋值

private  String getSign(SortedMap<Object, Object> map,String appKey,String type){String signPayTmp = Common.Map2String(map) + "key=" + appKey;log.info("微信" + type +"接口签名参数:sign:" + signPayTmp);String signPay = MD5Util.MD5Encode(signPayTmp, "UTF-8").toUpperCase();return signPay;}/*** Map 转 String**/public static String Map2String(Map<Object,Object> map){StringBuffer sb = new StringBuffer();Iterator<Map.Entry<Object, Object>> it = map.entrySet().iterator();while(it.hasNext()) {Map.Entry entry = (Map.Entry)it.next();String k = (String)entry.getKey();String v = (String)entry.getValue();if(null != v && !"".equals(v)&& !"sign".equals(k) && !"key".equals(k)) {sb.append(k + "=" + v + "&");}}return sb.toString();}

用于支付退款金额转换成整数(微信要求)

public static String getMoney(String amount) {if(amount==null){return "";}// 金额转化为分为单位// 处理包含, ¥ 或者$的金额String currency =  amount.replaceAll("\\$|\\¥|\\,", "");int index = currency.indexOf(".");int length = currency.length();Long amLong = 0l;if(index == -1){amLong = Long.valueOf(currency+"00");}else if(length - index >= 3){amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));}else if(length - index == 2){amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);}else{amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");}return amLong.toString();}

创建一个MD5Util 类:

public class MD5Util {public static String encryptMd5(String string) throws UnsupportedEncodingException {return encryptMd5(string, "UTF-8");}public static String encryptMd5(String string, String charSet) throws UnsupportedEncodingException {return DigestUtils.md5Hex(string.getBytes(charSet));}private static String byteArrayToHexString(byte b[]) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++)resultSb.append(byteToHexString(b[i]));return resultSb.toString();}private static String byteToHexString(byte b) {int n = b;if (n < 0)n += 256;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}public static String MD5Encode(String origin, String charsetname) {String resultString = null;try {resultString = new String(origin);MessageDigest md = MessageDigest.getInstance("MD5");if (charsetname == null || "".equals(charsetname))resultString = byteArrayToHexString(md.digest(resultString.getBytes()));elseresultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));} catch (Exception exception) {}return resultString;}private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}

创建一个 SnowflakeIdUtil 类(雪花算法):

public class SnowflakeIdUtil {// ==============================Fields===========================================/** 开始时间截 (2015-01-01) */private static final long twepoch = 1420041600000L;/** 机器id所占的位数 */private static final long workerIdBits = 5L;/** 数据标识id所占的位数 */private static final long datacenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);/** 序列在id中占的位数 */private static final long sequenceBits = 12L;/** 机器ID向左移12位 */private static final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private static final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private static final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~31) */private static long workerId;/** 数据中心ID(0~31) */private static long datacenterId;/** 毫秒内序列(0~4095) */private static long sequence = 0L;/** 上次生成ID的时间截 */private static long lastTimestamp = -1L;private static SnowflakeIdUtil idWorker;/*** 以SnowFlake算法,获取唯一有序id* @return*/public static long getSnowflakeId() {if(idWorker == null) {synchronized (SnowflakeIdUtil.class) {if(idWorker == null) {idWorker = new SnowflakeIdUtil(0, 0);}}}return idWorker.nextId();}// ==============================Methods==========================================private SnowflakeIdUtil() {}//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdUtil(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/public static long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/public static long timeGen() {return System.currentTimeMillis();}/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public static synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}
}

创建一个 XMLUtil 类:

public class XMLUtil {private static Logger log = LoggerFactory.getLogger(XMLUtil.class);private static final String PREFIX_XML = "<xml>";private static final String SUFFIX_XML = "</xml>";private static final String PREFIX_CDATA = "<![CDATA[";private static final String SUFFIX_CDATA = "]]>";/*** 转化成xml, 单层无嵌套* @param parm* @param isAddCDATA* @return*/public static String mapToXml(Map<Object, Object> parm, boolean isAddCDATA) {StringBuffer strbuff = new StringBuffer(PREFIX_XML);if (null != parm) {for (Entry<Object, Object> entry : parm.entrySet()) {strbuff.append("<").append(entry.getKey()).append(">");if (isAddCDATA) {if(!"sign".equals(entry.getKey())) {strbuff.append(PREFIX_CDATA);if (null != entry.getValue()) {strbuff.append(entry.getValue());}strbuff.append(SUFFIX_CDATA);}else {if (null != entry.getValue()) {strbuff.append(entry.getValue());}}} else {if (null != entry.getValue()) {strbuff.append(entry.getValue());}}strbuff.append("</").append(entry.getKey()).append(">");}}return strbuff.append(SUFFIX_XML).toString();}/*** xml转化成map* @param xmlStr* @return*/public static Map<String,String> XmlToMap(String xmlStr){Map<String,String> map = XmlUtil.of(xmlStr).toMap();return map;}/*** json 字符串转化成xml* @param params* @return*/public static String JsonToMap(String params){Map maps = (Map)JSON.parse(params);Map<Object, Object> sortMap = new TreeMap<Object, Object>();sortMap.putAll(maps);return XMLUtil.mapToXml(sortMap,true);}/*** 获取xml节点文本内容* @param nodeName* @return*/public static String getXmlNodeValue(String xmlStr, String nodeName) {Map<String,String> map = XmlToMap(xmlStr);if(map != null && map.size() > 0){return (String)map.get(nodeName);}else{return null;}}public static String parseXml2Str(HttpServletRequest request) {DataInputStream in;String wxNotifyXml = "";try {InputStream stream = request.getInputStream();in = new DataInputStream(stream);String str = IOUtils.toString(stream, "UTF-8");log.info("******************str="+str + "======contentLength="+request.getContentLength());byte[] dataOrigin = new byte[request.getContentLength()];in.readFully(dataOrigin); // 根据长度,将消息实体的内容读入字节数组dataOrigin中if(null != in) in.close(); // 关闭数据流wxNotifyXml = new String(dataOrigin); // 从字节数组中得到表示实体的字符串} catch (IOException e) {e.printStackTrace();}return wxNotifyXml;}/*** 解析微信发来的请求(XML)** @param request* @return* @throws Exception*/@SuppressWarnings("unchecked")public static Map<String, String> parseXml4Map(HttpServletRequest request) throws Exception {// 将解析结果存储在HashMap中Map<String, String> map = new HashMap<String, String>();// 从request中取得输入流InputStream inputStream = request.getInputStream();
//      String str = IOUtils.toString(inputStream, "utf-8");
//      log.info("******************str=" + str);// 读取输入流SAXReader reader = new SAXReader();Document document = reader.read(inputStream);// 得到xml根元素Element root = document.getRootElement();// 得到根元素的所有子节点List<Element> elementList = root.elements();// 遍历所有子节点for (Element e : elementList)map.put(e.getName(), e.getText());// 释放资源inputStream.close();inputStream = null;return map;}
}

创建一个 HttpUtil 类:

public class HttpUtil {private static Logger log = LoggerFactory.getLogger(HttpUtil.class);/*** http POST 请求* @param  url:请求地址* @param  body: body实体字符串* @param  sign:签名* @param  sn: 序列号* @return*/public static String httpPost(String url, String body,String sign,String sn) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {String xmlRes = "{}";HttpClient client = createSSLClientDefault();HttpPost httpost = new HttpPost(url);try {log.debug("Request string: " + body);//所有请求的body都需采用UTF-8编码StringEntity entity = new StringEntity(body,"UTF-8");entity.setContentType("application/json");httpost.setEntity(entity);//支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/jsonhttpost.addHeader("Content-Type","application/json");//支付平台所有的API调用都需要签名验证,签名首部: Authorization: sn + " " + signhttpost.addHeader("Authorization",sn + " " + sign);HttpResponse response = client.execute(httpost);//所有响应也采用UTF-8编码String result = EntityUtils.toString(response.getEntity(), "UTF-8");xmlRes = result;log.debug("Response string: " + xmlRes);} catch (ClientProtocolException e) {log.error("",e);}catch (UnknownHostException e){log.error("",e);}catch (IOException e) {log.error("",e);}return xmlRes;}public static CloseableHttpClient createSSLClientDefault() {try {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {//信任所有public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);return HttpClients.custom().setSSLSocketFactory(sslsf).build();} catch (KeyManagementException e) {log.error("", e);} catch (NoSuchAlgorithmException e) {log.error("", e);} catch (KeyStoreException e) {log.error("", e);}return HttpClients.createDefault();}/*** 创建带证书的实例* @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) {log.error("", e);} catch (NoSuchAlgorithmException e) {log.error("", e);} catch (KeyStoreException e) {log.error("", e);}catch (FileNotFoundException e){log.error("", e);}catch (Exception e){log.error("", e);}return HttpClients.createDefault();}public static String doGet(String url,String parameter){String uriAPI =url+"?"+parameter ;String result= "";HttpClient client = createSSLClientDefault();HttpGet httpRequst = new HttpGet(uriAPI);try {HttpResponse httpResponse = client.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类if(httpResponse.getStatusLine().getStatusCode() == 200){HttpEntity httpEntity = httpResponse.getEntity();result = EntityUtils.toString(httpEntity);//取出应答字符串// 一般来说都要删除多余的字符result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格}elsehttpRequst.abort();} catch (ClientProtocolException e) {// TODO Auto-generated catch blocke.printStackTrace();result = e.getMessage().toString();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();result = e.getMessage().toString();}return result;}public static String doGet(String url){String result= "";HttpClient client = createSSLClientDefault();HttpGet httpRequst = new HttpGet(url);try {HttpResponse httpResponse = client.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类if(httpResponse.getStatusLine().getStatusCode() == 200){HttpEntity httpEntity = httpResponse.getEntity();result = EntityUtils.toString(httpEntity);//取出应答字符串// 一般来说都要删除多余的字符result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格}elsehttpRequst.abort();} catch (ClientProtocolException e) {// TODO Auto-generated catch blocke.printStackTrace();result = e.getMessage().toString();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();result = e.getMessage().toString();}return result;}/*** http POST 请求* @param  url:请求地址* @param  body: body实体字符串* @return*/public static String httpPost(String url, String body){String xmlRes = "{}";HttpClient client = createSSLClientDefault();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/jsonhttpost.addHeader("Content-Type","text/xml");HttpResponse response = client.execute(httpost);//所有响应也采用UTF-8编码String result = EntityUtils.toString(response.getEntity(), "UTF-8");xmlRes = result;} catch (ClientProtocolException e) {log.error("",e);}catch (UnknownHostException e){log.error("",e);}catch (IOException e) {log.error("",e);}return xmlRes;}/*** 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/jsonhttpost.addHeader("Content-Type","text/xml");HttpResponse response = client.execute(httpost);//所有响应也采用UTF-8编码String result = EntityUtils.toString(response.getEntity(), "UTF-8");xmlRes = result;} catch (ClientProtocolException e) {log.error("",e);}catch (UnknownHostException e){log.error("",e);}catch (IOException e) {log.error("",e);}return xmlRes;}
}

创建一个 XMLUtil 类:

public class XMLUtil {private static Logger log = LoggerFactory.getLogger(XMLUtil.class);private static final String PREFIX_XML = "<xml>";private static final String SUFFIX_XML = "</xml>";private static final String PREFIX_CDATA = "<![CDATA[";private static final String SUFFIX_CDATA = "]]>";/*** 转化成xml, 单层无嵌套* @param parm* @param isAddCDATA* @return*/public static String mapToXml(Map<Object, Object> parm, boolean isAddCDATA) {StringBuffer strbuff = new StringBuffer(PREFIX_XML);if (null != parm) {for (Entry<Object, Object> entry : parm.entrySet()) {strbuff.append("<").append(entry.getKey()).append(">");if (isAddCDATA) {if(!"sign".equals(entry.getKey())) {strbuff.append(PREFIX_CDATA);if (null != entry.getValue()) {strbuff.append(entry.getValue());}strbuff.append(SUFFIX_CDATA);}else {if (null != entry.getValue()) {strbuff.append(entry.getValue());}}} else {if (null != entry.getValue()) {strbuff.append(entry.getValue());}}strbuff.append("</").append(entry.getKey()).append(">");}}return strbuff.append(SUFFIX_XML).toString();}/*** xml转化成map* @param xmlStr* @return*/public static Map<String,String> XmlToMap(String xmlStr){Map<String,String> map = XmlUtil.of(xmlStr).toMap();return map;}/*** json 字符串转化成xml* @param params* @return*/public static String JsonToMap(String params){Map maps = (Map)JSON.parse(params);Map<Object, Object> sortMap = new TreeMap<Object, Object>();sortMap.putAll(maps);return XMLUtil.mapToXml(sortMap,true);}/*** 获取xml节点文本内容* @param nodeName* @return*/public static String getXmlNodeValue(String xmlStr, String nodeName) {Map<String,String> map = XmlToMap(xmlStr);if(map != null && map.size() > 0){return (String)map.get(nodeName);}else{return null;}}public static String parseXml2Str(HttpServletRequest request) {DataInputStream in;String wxNotifyXml = "";try {InputStream stream = request.getInputStream();in = new DataInputStream(stream);String str = IOUtils.toString(stream, "UTF-8");log.info("******************str="+str + "======contentLength="+request.getContentLength());byte[] dataOrigin = new byte[request.getContentLength()];in.readFully(dataOrigin); // 根据长度,将消息实体的内容读入字节数组dataOrigin中if(null != in) in.close(); // 关闭数据流wxNotifyXml = new String(dataOrigin); // 从字节数组中得到表示实体的字符串} catch (IOException e) {e.printStackTrace();}return wxNotifyXml;}/*** 解析微信发来的请求(XML)** @param request* @return* @throws Exception*/@SuppressWarnings("unchecked")public static Map<String, String> parseXml4Map(HttpServletRequest request) throws Exception {// 将解析结果存储在HashMap中Map<String, String> map = new HashMap<String, String>();// 从request中取得输入流InputStream inputStream = request.getInputStream();
//      String str = IOUtils.toString(inputStream, "utf-8");
//      log.info("******************str=" + str);// 读取输入流SAXReader reader = new SAXReader();Document document = reader.read(inputStream);// 得到xml根元素Element root = document.getRootElement();// 得到根元素的所有子节点List<Element> elementList = root.elements();// 遍历所有子节点for (Element e : elementList)map.put(e.getName(), e.getText());// 释放资源inputStream.close();inputStream = null;return map;}
}

微信统一下单接口返回结果对象:

public class WxPayParam {/*** appID*/@ApiModelProperty(value = "appID")private String appId;/*** 时间戳*/@ApiModelProperty(value = "时间戳")private String timeStamp;/*** 随机字符串*/@ApiModelProperty(value = "随机字符串")private String nonceStr;/*** 统一下单接口返回的 prepay_id 参数值*/@ApiModelProperty(value = "统一下单接口返回的 prepay_id 参数值")private String prepayID;/*** 支付签名*/@ApiModelProperty(value = "支付签名")private String paySign;/*** 签名类型,默认为MD5*/@ApiModelProperty(value = "签名类型,默认为MD5")private String signType;/*** 请求响应编码*/@ApiModelProperty(value = "请求响应编码")private String resultStr;/*** 请求响应编码*/@ApiModelProperty(value = "用于标注openId是否为空")private String message;/*** 请求响应编码*/@ApiModelProperty(value = "用于存储支付前的流水id")private String thirdPayId;/*** 请求响应编码*/@ApiModelProperty(value = "用于存储平台业务流水号(订单流水)")private String serialNo;private String packageValue;
}

支付下单代码

public R pay(){//计算sign签名并用MD5加密生成支付签名String nonceStr = Common.getUUID();SortedMap<Object,Object> map = new TreeMap<Object,Object>();map.put("appid",appId);//公众账号ID//map.put("attach",bladeTenantId);map.put("attach",flag);//附加数据map.put("body",description);//商品描述map.put("mch_id",mchId);//商户号map.put("nonce_str",nonceStr);//随机字符串map.put("notify_url",notifyUrl);//通知地址map.put("openid",openId);//用户标识map.put("out_trade_no",32yu34b34n); //商户订单号map.put("spbill_create_ip",Common.getHostIp());//终端IPmap.put("total_fee",totalFee);//标价金额map.put("trade_type","JSAPI");//交易类型,比如JSAPI -JSAPI支付,NATIVE -Native支付,APP -APP支付map.put("sign", getSign(map,appKey,"支付统一下单"));//签名//调用微信通知下单接口String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";String xmlStr = XMLUtil.mapToXml(map,true);String resultStr = HttpUtil.httpPost(url,xmlStr);log.info("微信支付统一下单接口返回参数:resultStr:" + resultStr);Map<String,String> mapResult= XmlUtil.of(resultStr).toMap();log.info("解析微信响应参数成Map格式:{}",mapResult);if(mapResult !=null){if("FAIL".equals(mapResult.get("return_code")))return R.fail(mapResult.get("return_msg"));if("FAIL".equals(mapResult.get("result_code")))return R.fail(mapResult.get("err_code_des"));}//设置支付参数返回给前端String timestamp = Common.getTimestamp();WxPayParam param = new WxPayParam();param.setAppId(appId);param.setTimeStamp(timestamp);param.setNonceStr(nonceStr);param.setSignType("MD5");String prepayid = XMLUtil.getXmlNodeValue(resultStr,"prepay_id");//获取prepayIdif(StringUtils.isNotBlank(prepayid)){String prepayID = "prepay_id=" + prepayid;param.setPrepayID(prepayID);//生成前台支付签名,采用MD5算法!SortedMap<Object, Object> signParams = new TreeMap<Object,Object>();signParams.put("appId", param.getAppId());signParams.put("timeStamp", param.getTimeStamp());signParams.put("nonceStr", param.getNonceStr());signParams.put("signType", param.getSignType());signParams.put("package", param.getPrepayID());param.setPaySign(getSign(signParams,appKey,"支付统一下单支付"));param.setPackageValue(prepayID);}return R.data(param);}

退款代码

        //订单idString orderId ="xm-" + SnowflakeIdUtil.nextId();//计算sign签名并用MD5加密生成支付签名String nonceStr = Common.getUUID();String refundFee = CommonUtils.getMoney("0.03");String totalFee = CommonUtils.getMoney("0.05");//配置请求微信退款的参数SortedMap<Object, Object> map = new TreeMap<Object, Object>();map.put("appid", appId);map.put("mch_id", mchId);map.put("nonce_str", nonceStr);map.put("transaction_id", doctMzRecord.getThirdorderNo());map.put("out_refund_no", orderId);map.put("out_trade_no", doctMzRecord.getOrderId());map.put("total_fee", totalFee);//这里必须是支付时候的下单金额,特别是分批次退款的时候注意这里,不然微信提示:订单金额或退款金额与之前请求不一致,请核实后再试map.put("refund_fee", refundFee);//这里是需要退款的金额map.put("sign", WxUtil.getSign(map, appKey, "订单退款"));logger.info("map的参数:{}",map);String xmlStr = XMLUtil.mapToXml(map, true);logger.info("封装xml格式参数:{}",xmlStr);//证书路径//本地环境路径定义//InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("cert/apiclient_cert.p12");//正式环境路径定义File file = new File("/srv/apiclient_cert.p12");InputStream input = new FileInputStream(file);String wxUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";String resultStr = HttpUtil.httpPostReflect(wxUrl , xmlStr, input, mchId);logger.info("退款请求接口返回参数:{}" , resultStr);Map<String, String> mapResult = XmlUtil.of(resultStr).toMap();logger.info("解析微信响应参数成Map格式:{}",mapResult);if (mapResult != null) {if ("FAIL".equals(mapResult.get("return_code")))return R.fail(mapResult.get("return_msg"));if ("FAIL".equals(mapResult.get("result_code")))return R.fail(mapResult.get("err_code_des"));}}

退款注意点:

1、退款需要证书:apiclient_cert.p12,如果做退款功能需要申请证书。

2、total_fee:必须是订单的支付金额,单位:分;
3、refund_fee:少于订单支付金额,单位:分;
4、同一个订单可以发起多次退款,商户退款单号(out_refund_no)可以不同;
4、使用一个商户退款单号(out_refund_no)进行退款,因为某种原因退款失败了,不影响下次使用其他的商户退款单号进行退款 。

更多信息可以参考微信支付开发文档

java 中实现微信支付退款功能案例相关推荐

  1. Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java 中的微信支付(1):API V3 版本签名详解一文中胖哥讲解了微信支付 V3 版本 API 的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API 证书对参数 ...

  2. Java中的微信支付(1):API V3版本签名详解

    1. 前言 最近在折腾微信支付,证书还是比较烦人的,所以有必要分享一些经验,减少你在开发微信支付时的踩坑.目前微信支付的 API 已经发展到V3版本,采用了流行的 Restful 风格. 微信支付V2 ...

  3. php 微信支付退款接口开发,微信支付退款功能开发

    第一次做微信的退款处理,特此标记一下 准备 appId 微信分配的公众账号ID(企业号corpid即为此appId) mch_id 微信支付分配的商户号 pay_secret 商户平台设置的秘钥:微信 ...

  4. Java中的微信支付: 微信API-V3签名生成工具类

    微信支付API v3简介 微信官方文档地址 为了在 保证支付 安全的前提下,带给商户 简单.一致且易用的开发体验,我们推出了全新的微信支付API v3. 相较于之前的微信支付API,主要区别是: 遵循 ...

  5. Java中的微信支付:API V3对微信服务器响应进行签名验证3

    前言 牢记一句话:公钥加密,私钥解密:私钥加签,公钥验签. 微信支付V3版本前两篇分别讲了如何对请求做签名和如何获取并刷新微信平台公钥,本篇将继续展开如何对微信支付响应结果的验签. 2. 为什么要对响 ...

  6. java读取微信证书_Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java中的微信支付(1):API V3版本签名详解一文中胖哥讲解了微信支付V3版本API的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API证书对参数进行加签,微信 ...

  7. java 微信https 证书_Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java中的微信支付(1):API V3版本签名详解一文中胖哥讲解了微信支付V3版本API的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API证书对参数进行加签,微信 ...

  8. ansible 建 kubernetes 证书签名请求_Java中的微信支付(2):API V3 微信平台证书的获取与刷新...

    1. 前言 在Java 中的微信支付(1):API V3 版本签名详解一文中胖哥讲解了微信支付 V3 版本 API 的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API 证书对参数 ...

  9. aes加密 java_Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java 中的微信支付(1):API V3 版本签名详解一文中胖哥讲解了微信支付 V3 版本 API 的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API 证书对参数 ...

最新文章

  1. ups的空开、电缆及电池的配置计算
  2. 年后来广州第一天,写篇水文
  3. 老人机彻底不能用了?联通逐渐关闭2G、3G网络?回应:手机制式不支持
  4. 05 - Django应用第二步
  5. 实验一:命令解释程序
  6. OO第四单元UML作业总结暨OO课程总结
  7. JAVA中的I/O流
  8. 视觉SLAM十四讲中的修改代码总结
  9. 树莓派3B的摄像头模块
  10. 小米笔记本linux系统下载软件,小米笔记本用什么系统 小米笔记本电脑将预装Linux系统...
  11. 小学计算机课评课稿,有关于小学信息技术评课稿范文
  12. 测试睡眠质量软件,测睡眠质量的app排行榜:推荐10款有趣又有效的睡眠APP
  13. ENSEMBLE DISTILLATION APPROACHES FOR GRAMMATICAL ERROR CORRECTION翻译
  14. html如何读取pdf,html页面读取PDF小案例
  15. 今天19:30 | 复旦大学青年副研究员许嘉蓉—《基于图数据的鲁棒机器学习 》
  16. Python读取Excel日期列读出来是数字的处理
  17. Python安装包(3.6和3.8)及Pycharm安装及汉化包
  18. 公司服务器文件拷贝痕迹,服务器文件拷贝监控
  19. MySQL级联优缺点_【Mysql】外键级联与级联的劣势_MySQL
  20. 【 天梯赛L2-028 秀恩爱分得快】

热门文章

  1. Arduino基础入门之八 调速小风扇
  2. 将Roaring Bitmap序列化为JSON
  3. 东莞理工专插本计算机专业,东莞理工学院2021专插本专业,必看!专插本公办最容易录取的专业!是那些专业?...
  4. Google Android 7.0 GMS测试常见fail项以及分析解决方法
  5. js循环nodelist_js中的动态集合-nodeList,HTMLCollection,nameNodeMap
  6. 融入国家重大战略部署 5G频率规划使用需深耕细作
  7. 测试成功,修改能运行代码--待优化
  8. mysql存储过程中文乱码_mysql存储过程中文乱码
  9. 关于华为云盘无法登陆,显示“无法连接服务器”
  10. eMule电驴eDonkey源代码精辟分析