Java版微信退款

前言

前面一篇说了微信公众号支付,趁着手热,顺便把退款也测试了,强调一下,这就是搭建环境测试流程的,没有对接业务,所以有些地方怎么方便怎么来的,退款要讲的就是安装P12证书,以及退款回调 req_info的解密

环境准备

微信商户平台–>账户设置–> API安全–> 点击申请,按要求即可,下载完之后,解压之后如下图几个文件
在这里插入图片描述windows上可以直接双击导入系统,导入过程中会提示输入证书密码,证书密码默认为您的商户号,导入成功即可,Linux系统如果是Java版的好像直接匹配P12证书路径就可以了,没有测试

退款代码

工具类可以看我上一篇公众号支付,这里就不多讲,直接撸
controller层代码

  /**
     * 退款
     * @param tradeNo 退款的原交易订单号
     * @param refundFee 退款金额
     * @param desc 退款说明
     * @return
     */
    @PostMapping("/refund")
    @ResponseBody
    public ResultEntity exRefund(@RequestParam("tradeNo")String tradeNo,@RequestParam("refundFee")Integer refundFee,
                           @RequestParam(value = "desc",required = false)String desc){
        if(StringUtils.isBlank(tradeNo) || null == refundFee){
            return ResultEntity.fail("交易订单号和退款金额必传");
        }
        if(!wxPayService.exRefund(tradeNo, refundFee, desc)){
            return ResultEntity.fail("退款失败");
        }
        return ResultEntity.success();
    }

实现类代码
官方接口文档
我的订单总额是写死的,可以根据订单号去查数据库的

	@Override
    public boolean exRefund(String tradeNo, Integer refundFee, String desc) {
        Map<String, String> map = new HashMap<>();
        map.put("appid", wechatAccountConfig.getAppId());
        map.put("mch_id", wechatAccountConfig.getMchId());
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        map.put("out_trade_no", tradeNo);
        String refundNo = System.currentTimeMillis()+"";
        map.put("out_refund_no", refundNo);
        map.put("total_fee", "5");
        map.put("refund_fee", refundFee.toString());
        if(StringUtils.isNotBlank(desc)){
            map.put("refund_desc", desc);
        }
        map.put("notify_url",wechatAccountConfig.getRefundNotifyUrl());
        try {
            String sign = WXPayUtil.generateSignature(map, wechatAccountConfig.getMchKey());
            map.put("sign",sign);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("退款申请签名错误");
            return false;
        }
        log.info("退款申请请求参数:{}",JSON.toJSONString(map));
        String mapToXml;
        try {
             mapToXml = WXPayUtil.mapToXml(map);
        } catch (Exception e) {
            log.error("申請退款map转str出错");
            e.printStackTrace();
            return false;
        }
        try {
            String response = HttpUtil.doRefund(Constants.WXPAY_REFUND_GATEWAY, mapToXml, wechatAccountConfig.getMchId(),wechatAccountConfig.getCertPath());
            Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
            log.info("退款申请返回结果:{}",JSON.toJSONString(responseMap));
            if (responseMap.get("return_code").equals(Constants.SUCCESS)&& responseMap.get("result_code").equals(Constants.SUCCESS)) {
                log.info("退款成功");
            }else {
                log.info("退款失败");
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

这里涉及到 doRefund 这个方法,这里是直接百度找的

public static String doRefund(String url,String data,String mchId,String certPath) throws Exception {
        //指定读取证书格式为PKCS12(注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的)
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //读取本机存放的PKCS12证书文件 certPath 就是我本地存放的路径
        FileInputStream instream = new FileInputStream(new File(certPath));
        //比如安装在D:/pkcs12/apiclient_cert.p12情况下,就可以写成如下语句
        //FileInputStream instream = new FileInputStream(new File("D:/pkcs12/apiclient_cert.p12"));
        try {
            //指定PKCS12的密码(商户ID)
            keyStore.load(instream, mchId.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
        //指定TLS版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext,new String[] { "TLSv1"},null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        //设置httpclient的SSLSocketFactory
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            HttpPost httpost = new HttpPost(url);
            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();
        }
    }

退款回调controller方法

 	@RequestMapping("/refundNotify")
    public void refundNotify(HttpServletRequest request, HttpServletResponse response){
        if(wxPayService.refundNotify(request,response)){
            try {
            	// 微信响应消息
                response.getWriter().write(Constants.NOTIFY_RESPONSE_BODY);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            log.error("退款回调出现异常");
        }
    }

退款回调实现类

 	@Override
    public boolean refundNotify(HttpServletRequest request, HttpServletResponse response) {
        InputStream is = null;
        try {
            is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
            String xml = WXPayUtil.inputStream2String(is, "UTF-8");
            Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
            log.info("退款回调返回的数据:{}", JSON.toJSONString(notifyMap));
            // 验签返回的数据
            if(notifyMap.get("return_code").equals(Constants.SUCCESS)){
                // 此时在解密 req_info 字段信息
                Map<String, String> reqInfo = WXPayUtil.xmlToMap(AesUtil.decryptData(notifyMap.get("req_info"),wechatAccountConfig.getMchKey()));
                if(!reqInfo.get("refund_status").equals(Constants.SUCCESS)){
                    return false;
                }
            }else {
                log.info("退款失败:{}",JSON.toJSONString(notifyMap));
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return true;
    }

上面注意的是req_info解析
先上解密类的代码,工具类百度找的

public class AesUtil {

    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";

    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
    
    /**
     * AES加密
     *
     * @param data d
     * @return str
     * @throws Exception e
     */
    public static String encryptData(String data) throws Exception {
        // 创建密码器
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5("T0rai838GUHYkTNYWWGoxzJLAOE7HUa1").toLowerCase().getBytes(), ALGORITHM);
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return base64Encode8859(new String(cipher.doFinal(data.getBytes()), "ISO-8859-1"));

    }

    /**
     * AES解密
     *
     * @param base64Data 64
     * @return str
     * @throws Exception e
     */
    public static String decryptData(String base64Data,String mchKey) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(mchKey).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String(cipher.doFinal(base64Decode8859(base64Data).getBytes("ISO-8859-1")), "utf-8");
    }

    /**
     * Base64解码
     * @param source base64 str
     * @return str
     */
    public static String base64Decode8859(final String source) {
        String result = "";
        final Base64.Decoder decoder = Base64.getDecoder();
        try {
            // 此处的字符集是ISO-8859-1
            result = new String(decoder.decode(source), "ISO-8859-1");
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * Base64加密
     * @param source str
     * @return base64 str
     */
    public static String base64Encode8859(final String source) {
        String result = "";
        final Base64.Encoder encoder = Base64.getEncoder();
        byte[] textByte = null;
        try {
            //注意此处的编码是ISO-8859-1
            textByte = source.getBytes("ISO-8859-1");
            result = encoder.encodeToString(textByte);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }
}

如果解密代码报 java.security.InvalidKeyException: Illegal key size or default parameters这个错,则看下面

原因

因为美国的进口管制限制,Java发布的运行环境包中的加解密有一定的限制,默认不允许256位密钥的AES加解密

解决办法

1、如果是 Java 1.8.0_151以前的版本,则下载 https://www.oracle.com/java/technologies/javase-jce8-downloads.html,解压之后可以看到 local_policy.jar和US_export_policy.jar 这个2个文件,替换掉你本地jdk目录/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar即可,此方法是亲测,如果你没有效果,那就在把 jre本地目录/lib/security,也替换掉这2个文件在试一下
2、如果是 Java 1.8.0_151 以后的版本
则在 本地jdk目录/jre/lib/security 有一个 java.security 文件打开,找到定义java安全性属性 crypto.policy的行,他有limited或unlimited - 默认值是limited。默认情况是 #crypto.policy=unlimited,打开注释,去掉#即可,大概在 860 多行

题外话
原创
这里顺便说一下,linux上如果没有配置环境变量的情况下,一般是命令安装Javajdk的,怎么找这些 java.security 文件

1、echo $JAVA_HOME  先确定下是否配置了环境变量,没有的话,输出的是空
2、which java 查看Java目录
3、第2步之后不出意外会输出 /usr/bin/java ,接下来在使用 ls -lrt 命令
	-a :显示所有文件即目录(ls内定将文件名或目录名称开头为“.”的视为隐藏档,不会列出)
	-l: 除文件名称外,亦将文件形态、权限、拥有者、文件大小等资讯详细列出。
	-r: 将文件以相反次序显示(原定依英文字母次序)。
	-t: 将文件依次建立时间之先后次序列出。
	-A: 同-a,但不列出“.” (当前目录)及“…”(副文件)。
	-F: 在列出的文件名称后加一符号;例如可执行档则加“*”,目录则加“/”
	-R: 若目录下有文件,则以下之文件亦皆依序里列出。
4、执行  ls -lrt /usr/bin/java 会弹出一个目录,在继续  ls -lrt 命令查询即可找到
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值