小程序微信退款(退款信息回调解密)

一、微信支付(简单贴一下)

    /** 发起微信支付  */
    public Map wechatPay(String openid, HttpServletRequest request, String outTradeNo, Integer totalFee) {
        //生成的随机32位字符串
        String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS);
        //商品金额 单位分 将原来的金额*100 元->分
        Integer total_fee = (totalFee);
        //商品名称
        String packageName = "杭家订单:" + outTradeNo;
        //获取客户端的ip地址
        String spbill_create_ip = getIpAddress(request);
        //生成商户订单号
        String out_trade_no = outTradeNo;
        //组装参数,用户生成统一下单接口的签名
        Map<String, String> packageParams = new HashMap<String, String>();
        packageParams.put("appid", weChatConfig.appId);
        packageParams.put("mch_id", weChatConfig.mchId);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", packageName);
        packageParams.put("out_trade_no", out_trade_no);//商户订单号
        packageParams.put("total_fee", String.valueOf(total_fee));//支付金额,这边需要转成字符串类型,否则后面的签名会失败
        packageParams.put("spbill_create_ip", spbill_create_ip);
        packageParams.put("notify_url", weChatConfig.notifyUrl);//支付成功后的回调地址
        packageParams.put("trade_type", weChatConfig.tradetype);//支付方式
        packageParams.put("openid", openid);
        String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        //MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
        String mysign = PayUtil.sign(prestr, weChatConfig.key, "utf-8").toUpperCase();
        //拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
        String xml = "<xml>" + "<appid>" + weChatConfig.appId + "</appid>"
                + "<body><![CDATA[" + packageName + "]]></body>"
                + "<mch_id>" + weChatConfig.mchId + "</mch_id>"
                + "<nonce_str>" + nonce_str + "</nonce_str>"
                + "<notify_url>" + weChatConfig.notifyUrl + "</notify_url>"
                + "<openid>" + openid + "</openid>"
                + "<out_trade_no>" + out_trade_no + "</out_trade_no>"
                + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
                + "<total_fee>" + total_fee + "</total_fee>"
                + "<trade_type>" + weChatConfig.tradetype + "</trade_type>"
                + "<sign>" + mysign + "</sign>"
                + "</xml>";
        //调用统一下单接口,并接受返回的结果
        String result = PayUtil.httpRequest(weChatConfig.payUrl, "POST", xml);
        System.out.println("调试模式 返回XML数据:" + result);
        // 将解析结果存储在HashMap中
        Map map = PayUtil.doXMLParse(result);
        String return_code = (String) map.get("return_code");//返回状态码
        String return_msg = (String) map.get("return_msg");//返回状态信息
        //返回给移动端需要的参数
        Map<String, Object> response = new HashMap<>();
        if ("SUCCESS".equals(return_code)) {
            // 业务结果
            String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
            String sign = (String) map.get("sign");//微信返回的签名值
            response.put("sign", sign);
            response.put("signType", weChatConfig.signtype);
            response.put("nonceStr", nonce_str);
            response.put("package", "prepay_id=" + prepay_id);
            Long timeStamp = System.currentTimeMillis() / 1000;
            response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
            String stringSignTemp = "appId=" + weChatConfig.appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=" + weChatConfig.signtype + "&timeStamp=" + timeStamp;
            //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
            String paySign = PayUtil.sign(stringSignTemp, weChatConfig.key, "utf-8").toUpperCase();
            log.info("=======================第二次签名:" + paySign + "=====================");
            response.put("paySign", paySign);
            response.put("totalFee",total_fee);
            /**业务代码*/
        } else {
            throw new BaseException(ResultEnum.WECHAT_PAY_ERROR, return_msg);
        }
        response.put("appid", weChatConfig.appId);
        return response;
    }
    /** 支付回调 */
    @Override
    @Transactional
    public String wechatPayCallBack(HttpServletRequest request, HttpServletResponse response) {
        try {
            String line = null;
            BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            //sb为微信返回的xml
            String notityXml = sb.toString();
            String resXml = "";
            log.info("接收到的报文:" + notityXml);
            Map map = PayUtil.doXMLParse(notityXml);
            String returnCode = (String) map.get("return_code");
            if ("SUCCESS".equals(returnCode)) {
                //验证签名是否正确
                Map<String, String> validParams = PayUtil.paraFilter(map);  //回调验签时需要去除sign和空值参数
                String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
                String sign = PayUtil.sign(validStr, weChatConfig.key, "utf-8").toUpperCase();//拼装生成服务器端验证的签名
                //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
                if (sign.equals(map.get("sign"))) {
                    log.info("sign == sigin 签名通过");
                    /**此处添加自己的业务逻辑代码start*/
                    String outTradeNo = (String) map.get("out_trade_no");
                    String transactionId = (String) map.get("transaction_id");
                    String ip = getIpAddress(request);
                    // 支付回调处理
                    Boolean check = orderService.wechatPayCallBack(outTradeNo,transactionId,ip);
                    if(check){
                        // 通知微信服务器已经支付成功
                        resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                                + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    } else {
                        // 通知微信服务器已经支付失败
                        resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                                + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> ";
                    }
                    /**此处添加自己的业务逻辑代码end**/
                }
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            }
            log.info(resXml);
            log.info("微信支付回调数据结束");
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            out.close();
            return resXml;
        } catch (Exception e) {
            return null;
        }
    }

二、微信退款

1.主要代码:

    /** 微信退款 */
    @Override
    public Map<String, String> wechatRefund(int refundMoney, int totalMoney, String out_trade_no) {
        //生成的随机32位字符串
        String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS);
        String out_refund_no = SnowflakeIdWorker.getId();//商户退款单号
        //签名算法
        SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>();
        paramMap.put("appid", weChatConfig.appId);
        paramMap.put("mch_id", weChatConfig.mchId);
        paramMap.put("nonce_str", nonce_str);
        paramMap.put("out_trade_no", out_trade_no);
        paramMap.put("out_refund_no", out_refund_no);
        paramMap.put("total_fee", String.valueOf(totalMoney));
        paramMap.put("refund_fee", String.valueOf(refundMoney));
        paramMap.put("notify_url", weChatConfig.refundNotifyUrl);
        String sign = PayUtil.createSign(weChatConfig.key,paramMap);
        //获取最终待发送的数据
        String requestXml = "<xml>" +
                "<appid>" + weChatConfig.appId + "</appid>" +
                "<mch_id>" + weChatConfig.mchId + "</mch_id>" +
                "<nonce_str>" + nonce_str + "</nonce_str>" +
                "<out_trade_no>" + out_trade_no + "</out_trade_no>" +
                "<out_refund_no>" + out_refund_no + "</out_refund_no>" +
                "<total_fee>" + String.valueOf(totalMoney) + "</total_fee>" +
                "<refund_fee>" + String.valueOf(refundMoney) + "</refund_fee>" +
                "<notify_url>" + weChatConfig.refundNotifyUrl + "</notify_url>" +
                "<sign>" + sign + "</sign>" +
                "</xml>";
        //定义本函数返回值:
        Map<String,String> returnMap = new HashMap<>();
        try {
            //建立连接并发送数据
            String result = PayUtil.WeixinSendPostToRefund(weChatConfig.refundUrl,requestXml, weChatConfig.mchId);
            //解析返回的xml
            Map<String,String> resultMap = PayUtil.doXMLParse(result);
            //退款返回标志码
            String return_code = resultMap.get("return_code").toString();
            String result_code = resultMap.get("result_code").toString();
            if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){
                //code...
                returnMap.put("status","success");
                returnMap.put("msg","发起微信退款成功");
                returnMap.put("transactionId",resultMap.get("transaction_id"));
                returnMap.put("outTradeNo",resultMap.get("out_trade_no"));
                returnMap.put("outRefundNo",resultMap.get("out_refund_no"));
                return returnMap;
            }else if(return_code.equals("SUCCESS") && result_code.equals("FAIL")){
                returnMap.put("status","fail");
                returnMap.put("msg","微信原路返款失败!");
                return returnMap;
            }else{
                returnMap.put("status","fail");
                returnMap.put("msg","微信原路返款失败!");
                return returnMap;
            }
        }catch (Exception e){
            e.printStackTrace();
            returnMap.put("status","fail");
            returnMap.put("msg",e.getMessage());
            return returnMap;
        }
    }

2.证书使用

微信退款是需要证书的,怎么获取证书,微信支付文档比任何人说得都详细

/**  微信退款--向微信端发送post请求 */
    public static String WeixinSendPostToRefund(String url,String xmlObj,String mch_id) throws Exception{
        ClassPathResource classPathResource = new ClassPathResource("apiclient_cert.p12");//证书路径(此处我用的相对路径,你也可以考虑安全性,放在项目外)
        InputStream instream = classPathResource.getInputStream();
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try {
            keyStore.load(instream, mch_id.toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        String result = "";
        try {
            HttpPost httpPost = new HttpPost(url);
            HttpEntity xmlData = new StringEntity((String) xmlObj, "text/xml", "iso-8859-1");
            httpPost.setEntity(xmlData);
            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                result = EntityUtils.toString(entity, "UTF-8");
                System.out.println(response.getStatusLine());
                EntityUtils.consume(entity);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
        //去除空格
        return result.replaceAll(" ", "");
    }

3.代码中引用的方法

生成随机字符串:你可以用微信提供的javaSDK,其中有这个,而且map转xml和xml转map等都有
商户订单号:我用的SnowflakeIdWorker,这个随你,只要唯一就行了
sign签名:

 /**
     * 微信退款--sign签名
     */
    public static String createSign(String secretKey, SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        Set<Map.Entry<Object, Object>> es = parameters.entrySet();
        Iterator<Map.Entry<Object, Object>> it = es.iterator();
        while (it.hasNext()) {
            Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) 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=" + secretKey);
        String sign = MD5Utils.MD5(sb.toString()).toUpperCase();
        parameters.put("sign", sign);
        return sign;
    }

三、微信退款回调

首先不用多说,在外网地址上开放一个接口
在这里插入图片描述

1.主要代码

	/** 微信退款回调 */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信回调开始啦!!!!");
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
            String line = null;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            //sb为微信返回的xml
            Map resultMap = PayUtil.doXMLParse(sb.toString());
            if ("SUCCESS".equals(resultMap.get("return_code"))){//通信成功
                String req_info = String.valueOf(resultMap.get("req_info"));
                String resultXml = AESUtil.decryptData(req_info,weChatConfig.key);
                Map<String,Object> reqInfoMap = PayUtil.doXMLParse(resultXml);
				//code....
                String resultStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                return resultStr;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        throw new RuntimeException("微信回调失败");
    }

2.解密方法

AESUtil

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AESUtil {
    /**  密钥算法 */
    private static final String ALGORITHM = "AES";
    /**  加解密算法/工作模式/填充方式  */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
    /** AES加密  */
    public static String encryptData(String data,String password) throws Exception {
        // 创建密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64Util.encode(cipher.doFinal(data.getBytes()));
    }
    /** AES解密 */
    public static String decryptData(String base64Data,String password) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decode = Base64Util.decode(base64Data);
        byte[] doFinal = cipher.doFinal(decode);
        return new String(doFinal,"utf-8");
    }
}

Base64Util

import java.util.Base64;

public class Base64Util {
    /** 解码  */
    public static byte[] decode(String encodedText){
        final Base64.Decoder decoder = Base64.getDecoder();
        return decoder.decode(encodedText);
    }
    /** 编码 */
    public static String encode(byte[] data){
        final Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(data);
    }
}

MD5Utils

 /** 普通MD5 */
    public static String MD5(String input) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            return "check jdk";
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        char[] charArray = input.toCharArray();
        byte[] byteArray = new byte[charArray.length];
        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

ps:很多教程说要替换jre的两个jar包,我这里没照这一步,在AES解密的时候
SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
这步将商户key加密串转成小写了,这样就解决了"JAVA运行环境默认不允许256位密钥的AES加解密""的问题

有啥不懂得,或者我贴的代码有啥缺漏的,可以留言
参考:https://blog.csdn.net/zhangxing52077/article/details/80269999

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值