最近公司财务爸爸提需求,把三个支付退款api集成到公司系统里面去,由于之前api文档看的不够仔细,遇到很多坑,特此记录,分享给同样遇到坑的小伙伴:

商户能提供的是
appid 你的appid 也就是对于微信来说的唯一标示
appsecret 通过你的微信商户号进入就可以看到一个32位加密
key 商户的秘钥 这秘钥不是一开始就有的。需要你自己去设置,在设置的时候还需要与本商户号绑定的手机发下验证码
mchid 商户id不解释

强烈建议仔细阅读,微信和支付宝的开发文档可以避免少踩很多坑;

微信app开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6

微信jsapi开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

支付宝退款文档地址:https://docs.open.alipay.com/api_1/alipay.trade.refund

支付宝相关参数获取页面:

微信证书及apiKey获取页面:

1.微信jsapi退款、微信app退款:两个接口看官方文档参数基本一致,不同在于商户号(mch_id)、商户秘钥(API_KEY)、应用id即微信标识(APPID)

import com.cdy.common.util.DisconfDataGetter;
import com.cdy.common.util.MD5Util;
import com.cdy.lx.common.constant.CommonConstant;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.net.ssl.SSLContext;
import java.io.*;
import java.math.BigDecimal;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import static java.lang.System.out;/*** @author liuxue* @ClassName WxPayRefundUtil* @ProjectName cdy-ccb* @Description: TODO  微信退款工具类* @date 2019/5/20 0020上午 10:35* @Version 1.0*/
public class WxPayRefundUtil {private static final Logger LOGGER = LoggerFactory.getLogger(WxPayRefundUtil.class);//微信appid (注*以下配置文件,我是使用百度的分布式配置文件配置,实际替换成你们自己的配置)private static final String WX_APPID = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "AppID");//微信商户号private static final String WX_MCH_ID = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "WX_MCH_ID");//微信api秘钥private static final String API_KEY = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "WECHAT_MCH_SECRET");//环境版本控制(1表示测试环境,2表示生产环境)private static final String VERSION_CONTRO = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "VERSION_CONTRO");//微信公众号支付退款回调地址private static final String WX_REFUND_NOTIFY_URL = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "WX_REFUND_NOTIFY_URL");//微信app支付退款回调地址private static final String WX_APP_REFUND_NOTIFY_URL = (String) DisconfDataGetter.getByFileItem("wxconfig.properties", "WX_APP_REFUND_NOTIFY_URL");/*** @param out_trade_no   商户订单号* @param transaction_id 微信订单号* @param total_fee      订单总金额* @Author: liux* @Description:微信退款* @Date: 2019/5/20 0020上午 10:35* @return:*/private static String wxPayRefund(String refund_account,String notify_url ,String refundNum,String out_trade_no, String transaction_id, String total_fee,String appId,String mchId,String apiKey) {StringBuffer xml = new StringBuffer();String data = null;try {String nonceStr = genNonceStr();//生成32位随机字符串xml.append("</xml>");Map<String, Object> parameters = new HashMap<>();parameters.put("appid", appId);parameters.put("mch_id", mchId);parameters.put("nonce_str", nonceStr);parameters.put("out_trade_no", out_trade_no);if(transaction_id != null){parameters.put("transaction_id", transaction_id);}parameters.put("out_refund_no", refundNum);parameters.put("fee_type", "CNY");parameters.put("total_fee", total_fee);parameters.put("refund_fee", total_fee);parameters.put("op_user_id", mchId);parameters.put("notify_url", notify_url);parameters.put("refund_account", refund_account);parameters.put("sign", WechatPayUtil.getSign(parameters,apiKey)/*createSign(parameters, apiKey)*/);data = SortedMaptoXml(parameters);} catch (Exception e) {System.err.println(e.getMessage());return null;}return data;}/*** @param out_trade_no   商户订单号* @param transaction_id 微信订单号* @param total_fee      订单总金额* @param refund_fee     退款金额* @Author: liux* @Description:微信部分退款* @Date: 2019/5/20 0020上午 10:35* @return:*/private static String wxPayRefund(String refund_account,String notify_url ,String refundNum,String out_trade_no, String transaction_id, String total_fee, String refund_fee,String appId,String mchId,String apiKey) {StringBuffer xml = new StringBuffer();String data = null;try {String nonceStr = genNonceStr();//生成32位随机字符串xml.append("</xml>");Map<String, Object> parameters = new HashMap<>();parameters.put("appid", appId);parameters.put("mch_id", mchId);parameters.put("nonce_str", nonceStr);parameters.put("out_trade_no", out_trade_no);if(transaction_id != null){parameters.put("transaction_id", transaction_id);}parameters.put("out_refund_no", refundNum);parameters.put("fee_type", "CNY");parameters.put("total_fee", total_fee);parameters.put("refund_fee", refund_fee);//部退款金额parameters.put("op_user_id", mchId);parameters.put("notify_url", notify_url);parameters.put("refund_account", refund_account);//仅针对老资金流商户使用REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款parameters.put("sign", WechatPayUtil.getSign(parameters,apiKey)/*createSign(parameters, apiKey)*/);data = SortedMaptoXml(parameters);} catch (Exception e) {System.err.println(e.getMessage());return null;}return data;}/*** 生成32位随机数字*/public static String genNonceStr() {Random random = new Random();return MD5Util.MD5(String.valueOf(random.nextInt(10000)));}/*** @param characterEncoding* @param parameters* @Author: liux* @Description:支付参数生成签名* @Date: 2019/5/20 0020上午 10:35*/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=" + API_KEY);String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase();return sign;}/*** 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。*/public static String createSign(SortedMap<String, String> packageParams, String AppKey) {StringBuffer sb = new StringBuffer();Set es = packageParams.entrySet();Iterator it = es.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 + "&");}}sb.append("key=" + AppKey);String sign = MD5Util.MD5Encode(sb.toString()).toUpperCase();return sign;}/*** @param params* @Author: liux* @Description:请求值转换为xml格式 SortedMap转xml* @Date:2019/5/20 0020上午 10:35*/private static String SortedMaptoXml(Map<String, Object> params) {StringBuilder sb = new StringBuilder();Set es = params.entrySet();Iterator it = es.iterator();sb.append("<xml>\n");while (it.hasNext()) {Map.Entry entry = (Map.Entry) it.next();String k = (String) entry.getKey();Object v = entry.getValue();sb.append("<" + k + ">");sb.append(v);sb.append("</" + k + ">\n");}sb.append("</xml>");return sb.toString();}/*** 生成 MD5** @param data 待处理数据* @return MD5结果*/public static String MD5(String data) throws Exception {MessageDigest md = MessageDigest.getInstance("MD5");byte[] array = md.digest(data.getBytes("UTF-8"));StringBuilder sb = new StringBuilder();for (byte item : array) {sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));}return sb.toString().toUpperCase();}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 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];}private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};/*** 证书使用* 微信退款*/private static String wxPayBack(String url, String data,String fileName,String mchId) throws Exception {KeyStore keyStore  = KeyStore.getInstance("PKCS12");
//        FileInputStream instream = new FileInputStream(new File("F:\\wx\\apiclient_cert.p12"));//FileInputStream instream=(FileInputStream)inputStream;String result="";InputStream inputStream = inputStream =new FileInputStream(new File(fileName));if(inputStream == null){return result;}try {keyStore.load(inputStream, mchId.toCharArray());} finally {inputStream.close();}// Trust own CA and all self-signed certsSSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();// Allow TLSv1 protocol onlySSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();try {HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");StringEntity entitys = new StringEntity(data);httppost.setEntity((HttpEntity) entitys);CloseableHttpResponse response = httpclient.execute(httppost);try {HttpEntity entity = response.getEntity();if (entity != null) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));String text="";String t="";while ((text=bufferedReader.readLine()) != null) {t+=text;}byte[] temp=t.getBytes("utf-8");//这里写原编码方式String newStr=new String(temp);//这里写转换后的编码方式result=newStr;}EntityUtils.consume(entity);} finally {response.close();}} finally {httpclient.close();}return result;}/*** @Description: 退款接口 供外部调用* @param ${refundNum} :商户退款${source} :订单来源(微信公众号或者app微信)* @param ${out_trade_no} :商户订单号 ${transaction_id} :微信平台订单号* @param ${total_fee} :订单金额 ${refund_fee} :退费金额* @param ${refund_account} 退费钱包(默认未结算金额退款)* @return ${return_type} * @throws* @author liuxue* @date 2019/5/24 0024 下午 3:30 */public static Map<String,String> wxRefund(String refundNum,String source,String out_trade_no, String transaction_id, BigDecimal total_fee, BigDecimal refund_fee,String refund_account) {String param = "" ;String appId = "";String mchId = "";String apiKey = "";String notifyUrl = "";String totalAmount = String.valueOf(Double.valueOf(total_fee.multiply(BigDecimal.valueOf(100)).toString()).intValue());String refundAmount = String.valueOf(Double.valueOf(refund_fee.multiply(BigDecimal.valueOf(100)).toString()).intValue());if("app".equals(source)){appId = CommonConstant.OPEN_WECHAT_APPID;mchId = CommonConstant.OPEN_WECHAT_MCHID;apiKey = CommonConstant.OPEN_WECHAT_MCH_SECRET;notifyUrl = WX_APP_REFUND_NOTIFY_URL;//退款通知回调地址}else{appId = WX_APPID;mchId = WX_MCH_ID;apiKey = API_KEY;notifyUrl = WX_REFUND_NOTIFY_URL;//退款通知回调地址}String fileName = ProjectPathUtil.getRootPath("/refundceat/test_wx_apiclient_cert.p12");if(VERSION_CONTRO!=null && "2".equals(VERSION_CONTRO)){//2表示生产环境fileName = ProjectPathUtil.getRootPath("/refundceat/wx_apiclient_cert.p12");}if ("app".equals(source)){fileName = ProjectPathUtil.getRootPath("/refundceat/wx_app_apiclient_cert.p12");}if(total_fee.subtract(refund_fee).compareTo(BigDecimal.valueOf(0))==0){LOGGER.info("微信|退款|单号|"+out_trade_no+"|全额退款:请求参数out_trade_no:"+out_trade_no+":transaction_id:"+transaction_id+":total_fee:"+total_fee+":refund_fee:"+refund_fee);param = wxPayRefund(refund_account,notifyUrl,refundNum,out_trade_no,transaction_id,totalAmount,appId,mchId,apiKey);}else{LOGGER.info("微信|退款|单号|"+out_trade_no+"|微信部分退款:请求参数out_trade_no:"+out_trade_no+":transaction_id:"+transaction_id+":total_fee:"+total_fee+":refund_fee:"+refund_fee);param = wxPayRefund(refund_account,notifyUrl,refundNum,out_trade_no,transaction_id,totalAmount,refundAmount,appId,mchId,apiKey);}LOGGER.info("微信|退款|单号|"+out_trade_no+"|请求xml|"+param);String result = "";String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";try {result = wxPayBack(url, param,fileName,mchId);LOGGER.info("微信|退款|单号|"+out_trade_no+"|返回结果"+result);Map<String,String> map = WechatPayUtil.getMapStringFromXML(result);return map;} catch (Exception e) {e.printStackTrace();LOGGER.error("微信|退款|单号|"+out_trade_no+"|出错",e);}return null;}}
上面的代码,我遇到过一个问题,就是maven编译打包会把2进制这种证书文件给改变,导致证书不可用的问题,可以在maven打包中
过滤证书文件不编译,添加一下配置即可,(实际生产环境可能会与到路径不对,无法读取证书问题,这个就没法给一个公用的解决方案了,
只能具体情况,自己去特殊处理了):

<nonFilteredFileExtensions><nonFilteredFileExtension>p12</nonFilteredFileExtension>
</nonFilteredFileExtensions>
WechatPayUtil工具类
import com.cdy.common.util.MD5Util;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;import static java.lang.System.out;/*** 微信支付签名算法** @author liux* @since 2019-05-15 14:38*/
public class WechatPayUtil {private static Logger log = Logger.getLogger(WechatPayUtil.class);public static String getSign(Map<String, Object> map, String mch_secret) {ArrayList<String> list = new ArrayList<String>();for (Map.Entry<String, Object> entry : map.entrySet()) {if (!"".equals(entry.getValue()) && entry.getValue() != null) {list.add(entry.getKey() + "=" + entry.getValue() + "&");}}int size = list.size();String[] arrayToSort = list.toArray(new String[size]);Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);StringBuilder sb = new StringBuilder();for (int i = 0; i < size; i++) {sb.append(arrayToSort[i]);}String result = sb.toString();result += "key=" + mch_secret;log.debug("before sign:" + result);result = MD5Util.MD5Encode(result).toUpperCase();log.debug("after sign:" + result);return result;}}Md5util工具类:import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;import org.apache.commons.codec.digest.DigestUtils;/***/
public class MD5Util {private static final char[] hexDigest = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7","8", "9", "a", "b", "c", "d", "e", "f"};public final static String MD5(String content) {//用于加密的字符char md5String[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F'};try {//使用平台的默认字符集将此 String 编码为 byte序列,并将结果存储到一个新的 byte数组中byte[] btInput = content.getBytes();//信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。MessageDigest mdInst = MessageDigest.getInstance("MD5");//MessageDigest对象通过使用 update方法处理数据, 使用指定的byte数组更新摘要mdInst.update(btInput);// 摘要更新之后,通过调用digest()执行哈希计算,获得密文byte[] md = mdInst.digest();// 把密文转换成十六进制的字符串形式int j = md.length;char str[] = new char[j * 2];int k = 0;for (int i = 0; i < j; i++) {   //  i = 0byte byte0 = md[i];  //95str[k++] = md5String[byte0 >>> 4 & 0xf];    //    5str[k++] = md5String[byte0 & 0xf];   //   F}//返回经过加密后的字符串return new String(str);} catch (Exception e) {return null;}}/*** * @Description: 生成member的token* @param id* @param mobilePhone* @param password* @return    * String  * @author liux* @date 2017年10月16日*/public static String generateToken(Long id, String mobilePhone, String password) {StringBuilder sb = new StringBuilder();sb.append(id);sb.append(mobilePhone);sb.append(password);sb.append(new Date().toString());//生成tokenreturn md5(sb.toString());}public static String md5(String content) {try {//1.创建消息摘要实例MessageDigest md = MessageDigest.getInstance("MD5");//2.获取待加密内容的字节数组byte[] contentB = content.getBytes();//3.使用指定的字节更新摘要md.update(contentB);//4.加密byte[]  newContent = md.digest();//长度为16的字节数组//5.将加密后的16位字节数组转换为32位十六进制数字int k=0;char[] contentC = new char[newContent.length * 2];for(int  i=0;i<newContent.length;i++){byte b = newContent[i];contentC[k++] = hexDigest[b >>> 4 & 0xf];   //高四位contentC[k++] = hexDigest[b & 0xf];              //低四位}return  new String(contentC);} catch (NoSuchAlgorithmException e) {e.printStackTrace();return null;}}/*** MD5编码* @param origin 原始字符串* @return 经过MD5加密之后的结果*/public static String MD5Encode(String origin) {String resultString = null;try {resultString = origin;MessageDigest md = MessageDigest.getInstance("MD5");resultString = byteArrayToHexString(md.digest(resultString.getBytes("utf-8")));} catch (Exception e) {e.printStackTrace();}return resultString;}/*** 转换字节数组为16进制字串* @param b 字节数组* @return 16进制字串*/public static String byteArrayToHexString(byte[] b) {StringBuilder resultSb = new StringBuilder();for (byte aB : b) {resultSb.append(byteToHexString(aB));}return resultSb.toString();}/*** 转换byte到16进制* @param b 要转换的byte* @return 16进制格式*/private static String byteToHexString(byte b) {int n = b;if (n < 0) {n = 256 + n;}int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}/*** * @Description: * @param text 需要签名的字符串* @param key 密钥* @param input_charset 编码格式* @return    签名结果* String  * @author liux* @date 2017年11月8日*/public static String sign(String text, String key, String input_charset) {text = text + key;System.out.println(text);return DigestUtils.md5Hex(getContentBytes(text, input_charset));}private static byte[] getContentBytes(String content, String charset) {if (charset == null || "".equals(charset)) {return content.getBytes();}try {return content.getBytes(charset);} catch (UnsupportedEncodingException e) {throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);}}public static String md5Str(String str){if (str == null)return "";return md5Str(str, 0);}/*** 计算消息摘要。* @param data 计算摘要的数据。* @param offset 数据偏移地址。* @param length 数据长度。* @return 摘要结果。(16字节)*/public static String md5Str(String str, int offset){try{MessageDigest md5 = MessageDigest.getInstance("MD5");byte[] b = str.getBytes("UTF8");md5.update(b, offset, b.length);return byteArrayToHexString(md5.digest());}catch (NoSuchAlgorithmException ex){ex.printStackTrace();return null;}catch (UnsupportedEncodingException ex){ex.printStackTrace();return null;}}}

支付宝退款相关代码,这里有点注意的,代码里面用到的私钥和公钥,如果不知道可以去对应的支付宝商户平台上去查看,还有加密的方式是使用的“RSA”还是“RSA2”可以去上商户平台上查看,这个写错 也是无法退款的:

引入sdk

<dependency><groupId>com.alipay</groupId><artifactId>sdk-java</artifactId><version></version>
</dependency>

AlipayRefundUtil

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.cdy.common.util.DisconfDataGetter;
import com.cdy.common.util.JSONUtil;
import com.cdy.lx.common.constant.CommonConstant;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.math.BigDecimal;/*** @author liux* @ClassName AlipayRefundUtil* @ProjectName cdy-ccb* @Description: TODO* @date 2019/5/20 0020上午 10:05* @Version 1.0*/
public class AlipayRefundUtil {private static final Logger LOGGER = LoggerFactory.getLogger(AlipayRefundUtil.class);//支付宝退款请求的网关private static String requestUrl = "https://openapi.alipay.com/gateway.do";//编码级别private static String CHARSET = "UTF-8";public static AlipayTradeRefundResponse refundOrder(String out_trade_no, String refundAmount){System.out.println("开始调用支付宝加密。。。");//实例化客户端AlipayClient alipayClient = new DefaultAlipayClient(requestUrl, CommonConstant.APP_ID, CommonConstant.APP_PRIVATE_KEY, "json", CHARSET, CommonConstant.ALIPAY_PUBLIC_KEY, "RSA");//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。AlipayTradeRefundModel refundModel = new AlipayTradeRefundModel();//refundModel.setTradeNo(trade_no);refundModel.setOutTradeNo(out_trade_no);refundModel.setRefundAmount(refundAmount);refundModel.setRefundReason("商品退款");//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.payAlipayTradeRefundRequest request = new AlipayTradeRefundRequest();request.setBizModel(refundModel);try{AlipayTradeRefundResponse response = alipayClient.execute(request);LOGGER.info("支付宝|退款|单号|"+out_trade_no+"|返回结果"+ JSONUtil.toJson(response));return response;}catch(Exception e){e.printStackTrace();LOGGER.error("支付宝退款错误!",e.getMessage());                    }      return null;}/**  支付宝退款接口(部分退款)* @param out_trade_no 订单支付时传入的商户订单号,不能和支付宝交易号(trade_no)同时为空。* @param trade_no 支付宝交易号* @param refund_amount 需要退款的金额,该金额不能大于订单金额,单位为元,支持两位小数* @return 将提示信息返回*/public synchronized static AlipayTradeRefundResponse alipayRefundRequest(String out_trade_no,String trade_no,BigDecimal refund_amount,String out_request_no) {String returnStr = null;try {LOGGER.info("支付宝|退款|单号|"+out_trade_no+"申请退款:请求参数out_trade_no:"+out_trade_no+":trade_no:"+trade_no+":refund_amount:"+refund_amount);AlipayClient alipayClient = new DefaultAlipayClient(requestUrl,CommonConstant.APP_ID, CommonConstant.APP_PRIVATE_KEY,"JSON", CHARSET, CommonConstant.ALIPAY_PUBLIC_KEY, "RSA");AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();request.setBizContent("{" +"\"out_trade_no\":\"" + out_trade_no + "\"," +"\"trade_no\":\"" + trade_no + "\"," +"\"refund_amount\":\"" + refund_amount + "\"," +"\"out_request_no\":\"" + out_request_no+ "\"," +"\"refund_reason\":\"正常退款\"" +" }");AlipayTradeRefundResponse response;response = alipayClient.execute(request);LOGGER.info("支付宝|退款|单号|"+out_trade_no+"|返回结果"+ JSONUtil.toJson(response));return response;} catch (Exception e) {e.printStackTrace();LOGGER.error("支付宝|退款|单号|"+out_trade_no+"|出错",e);}return null;}
}

遇到过的错误:

1.java.security.InvalidKeyException: Illegal key size or default parameters

参考https://blog.csdn.net/cl11992/article/details/86703694 (我这边是测试环境linux jdk 1.8.0 151遇到的,通过升级jdk版本解决)

2.DER input, Integer tag error (测试环境遇到的错误,好像是秘钥配置问题,秘钥一定要和证书对应)

3.{"code":"40003","msg":"Insufficient Conditions","sub_code":"isv.missing-signature-config","sub_msg":"验签出错, 未配置对应签名算法的公钥或者证书"}(这种问题,都是支付宝的秘钥配置不正确)

4.微信退款返回签名错误(这种问题也是秘钥和证书不一致,(商户秘钥不是微信公众平台上面配置的秘钥))

浦发相关的代码就不贴了,有不清楚的可以问题;

微信公众号退款,app微信退款,支付宝退款,浦发退款相关推荐

  1. 商城前端模板_如何理解微信小程序和微商城,微信公众号以及APP之间的关系?一张图看懂了!...

    老张的一位粉丝,花了几天时间把知乎里面分享的一些关于微信小程序,微信商城,微信公众号,以及APP的相关介绍全看完了. 然后用他自己的话描述了微信小程序和微商城,微信公众号以及APP之间的关系,如下图所 ...

  2. delphi XE关于微信公众号支付及微信零钱支付的便捷解决方案

    delphi XE关于微信公众号支付及微信零钱支付的便捷解决方案 https://download.csdn.net/download/pulledup/12683611 一.需求 因为微信公众号支付 ...

  3. 视频教程-微信公众号使用教程-微信开发

    微信公众号使用教程 大秦电商创始人,专注网络技术的电商应用.已经出品<1小时建站><微博营销实战><PHP采集><网络招商系统> 秦子恒 ¥39.00 ...

  4. 微信公众号引入使用微信JS-SDK

    微信公众号引入使用微信JS-SDK 一定要确定JS安全域名已经配置,不然会报错 导致config 配置不成功 引入JS-SDK npm install weixin-js-sdk --save 挂载引 ...

  5. 视频教程-基于python的微信公众号开发教程-微信开发

    基于python的微信公众号开发教程 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试(软考)--"信 ...

  6. 微信公众号数据2019_如何制作微信公众号图文素材 微信公众号采集器好用吗

    现在有很多人都会通过微信公众号来发布文章.图片,这时候就需要使用一些编辑技巧了.下面拓途数据就和大家一同来看看如何制作微信公众号图文素材,微信公众号采集器好用吗? 微信公众号图文素材 如何制作微信公众 ...

  7. 微信公众号数据2019_微信公众号榜单排名,2020微信公众号排名

    微信公众号榜单排名,2020微信公众号排名 公众号排名优化的注意事项及细节今天给大家分享一下,作为微信公众号的排名优化对于大多数人来说都已经知道了有这个渠道的事情,其实很多的新产品及渠道出来以后有不少 ...

  8. 微信公众号数据2019_历史微信公众号排名,微信公众号新榜排名

    历史微信公众号排名,微信公众号新榜排名 公众号排名优化的注意事项及细节今天给大家分享一下,作为微信公众号的排名优化对于大多数人来说都已经知道了有这个渠道的事情,其实很多的新产品及渠道出来以后有不少的人 ...

  9. 【TUN模式】对QQ或者微信域名、QQ邮箱、微信公众号文章、微信开发者社区相关的网站访问非常慢、图片加载不出来、网页空白的解决方案

    一.问题背景 最近在电脑上登录微信时,或者浏览微信公众号的文章时,经常出现卡顿得出不来图片的情况,即使出来了也是排版异常. 二.研究现状(哈哈哈哈科研人的写作习惯?) 目前网上有些大咖对这个问题给出的 ...

  10. java中微信公众号框架_java微信公众号开发框架包含哪些内容呢?为什么要使用框架...

    java微信公众号开发框架包含哪些内容呢?为什么要使用框架 微信公众号是现在人们生活中经常会用到的,微信公众号有着各种各样的信息,包含了齐全的内容以及完善的功能,这也让人们可以更加轻松的在公众号上找到 ...

最新文章

  1. 网站托管的内容细节你都清楚吗?
  2. FZU - 2020 计算大组合数取模
  3. Python Mysql_db对数据查询进行处理
  4. jMenu 不添加item 直接自己添加监听actionPerformed不行mousePressed可以
  5. oracle11gasm,Oracle11gASM之ACFS创建案例
  6. 分组统计 - DataFrame.groupby() 所见的各种用法 - Python代码
  7. C# 中执行 msi 安装
  8. 用户登入身份验证,手机app登入身份验证,TokenAuth身份验证,JSON Web Token(JWT)身份验证
  9. DIADEM_metric不能运行及解决办法
  10. 高速下载文件的方法(对百度,城通等网盘无效)
  11. android为什么总是闪退怎么办,手机应用老是闪退该怎么办?
  12. 百度App组件化之路
  13. 常用工具类之jwt的学习使用
  14. 计算机网络DNS域名解析协议详解
  15. number of items to replace is not a multiple of replacement length
  16. 使用 Metasploit 利用 OpenSSH 用户枚举漏洞 (CVE-2018-15473, CVE-2016-6210, CVE-1999-0502)
  17. GitHub Desktop 2.0 发布;华为与俄最大电信公司签约开发 5G
  18. MT5正版白标搭建。MT5CRM源码出售。
  19. 如何在Windows中调整ClearType以提高屏幕可读性
  20. 【Proteus仿真】8×8LED点阵屏仿电梯数字滚动显示

热门文章

  1. 初级会计实务(2020年)——第二章 资产
  2. 基于vue+vant+koa+MongoDB用户扫码点餐的H5项目(包含源码+论文)
  3. ionic上传身份证正反面照片、上传图片、FileReader使用
  4. 深度学习·理论篇(2023版)·第006篇高维空间下的维度与体积距离的关系:采样和维度+高维空间下体积与距离+中心极限定律与距离分布(深度学习)
  5. 年薪百万怎么了?程序员这个职业一周工作70+个小时是在用命换钱啊!
  6. 解决Chrome使用截图插件时,不能截本地的HTML
  7. C++(标准库):45---并发之(底层接口thread()、promise、packaged_task)
  8. 远程连接桌面出现内部错误
  9. 高新技术企业认定攻略
  10. 多字段mysql触发器实例_mysql触发器原理与用法实例分析