微信公众号退款,app微信退款,支付宝退款,浦发退款

最近公司财务爸爸提需求,把三个支付退款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

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

123

微信证书及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()));
            else
                resultString = 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 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 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 = 0
                byte byte0 = md[i];  //95
                str[k++] = md5String[byte0 >>> 4 & 0xf];    //    5
                str[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());
		//生成token
		return 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.pay
             AlipayTradeRefundRequest 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.微信退款返回签名错误(这种问题也是秘钥和证书不一致,(商户秘钥不是微信公众平台上面配置的秘钥))

 

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值