微信退款接口开发

步骤:

发送退款请求到微信->同步告诉你请求成功还是失败->异步回调告诉你退款成功还是失败

说明:微信退款是需要证书的,先要下载证书.如何下载百度一下就可以,很简单

//所需要的参数

appid:公众账号id,微信分配的公众账号id

mch_id:微信分配的商户id

nonce_str:随机字符创,长度不超过32位,随机生成便可以

out_refund_no:商户退款单号,请求方生成的流水号

out_trade_no:商户订单号,微信支付时,商户自己生成的订单号

refund_fee:退款金额

total_fee:订单金额(支付的金额)

notify_url:回调地址,一定要能够请求到的地址

wxKey:微信商户平台秘钥(这个在商户平台上能找到)

 Map<String, String> map = new HashMap<String, String>(); //参数对象
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("nonce_str", StringUtils.getUUId());
        map.put("out_refund_no",outRefundId);
        map.put("out_trade_no", orderId);
        map.put("refund_fee",String.valueOf(refundFee));
        map.put("total_fee",String.valueOf(totalFee));
        map.put("notify_url", notify);

  String sing=refundToSign(map,wxKey);

  map.put("sign",sing);

//将map转换为xml格式

 String param=mapToXml(map);

//发送请求

//发送请求的url地址:https://api.mch.weixin.qq.com/secapi/pay/refund

//下载下来的证书的路径:classpath:cert/apiclient_cert.p12  //这里我是在resource下面建了一个cert文件夹

String result = refundHttpRequest(mchId,url, param, classpath);

//将返回来的xml格式数据转换为map格式的数据

Map<String, String> map=xmlToMap(result);

//获取返回的状态码

String returnCode = (String) map.get("return_code");

//判断退款请求是否发送成功

if("SUCCESS".equals(returnCode)){

System.err.pringln("发送成功!"+returnCode);

//获取业务结果状态码

 String resultCode = (String) map.get("result_code");
if("SUCCESS".equals(resultCode){

System.err.pringln(“微信退款请求发送成功==”+resultCode);

}

}

 

//接收异步返回信息,微信的异步返回信息是加密的,所以要解密,解密有很多的坑,要注意

    @Path("http:reciver.com") //这里的路径是你在发送请求的时候所写的notify_url地址,一定要保证能够请求
    public boolean refundSuccess(Map<String, String> map) throws Exception {
        // 获取加密信息
        String information = map.get("req_info").toString();
        System.err.println("加密信息==" + information);

  //获取返回的微信appId
        String appId = map.get("appid");
        System.err.println("返回来的appId==" + appId);
        //微信解密是要根据秘钥解密的

        String wxKey = “”; //这里的秘钥和appid一样都是微信给的
       
        Aes aes=new Aes();
        // 解密返回来的密文
        String aesInformation = aes.getRefundDecrypt(information, wxKey);
        // 将xml格式转换为map
        Map<String, String> maps = xmlToMap(aesInformation);
        logger.info("解密出来的内容[map}===" + maps);
        try {
    //判断是否退款成功
            if ("SUCCESS".equals((String) maps.get("refund_status"))) {
                logger.info("微信退款成功!!");
            }

        } catch (Exception e) {
            logger.info("出现异常[e==]" + e);
            return false;
        }
    }

 

 

 

 

 

 

//生成签名

    public String refundToSign(Map<String, String> map, String wxKey) {
        String prestr =getParamsOrderByKey(map); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        String key = "&key=" + wxKey; // 商户支付密钥
        // MD5运算生成签名
        String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
        return mysign;
    }

 /**
     * 按照key排序得到参数列表字符串
     *
     * @param paramValues 参数map对象
     * @return 参数列表字符串
     *
     *         /** 除去map中的空值和签名参数
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static String getParamsOrderByKey(Map<String, String> paramValues) {
        String params = "";
        Set<String> key = paramValues.keySet();
        String beginLetter = "";

        List<String> paramNames = new ArrayList<String>(paramValues.size());

        paramNames.addAll(paramValues.keySet());
        Collections.sort(paramNames);
        for (String paramName : paramNames) {

            Object b = paramValues.get(paramName);

            if (b == null || b.equals("") || b.toString().equalsIgnoreCase("sign") || b.toString().equalsIgnoreCase("sign_type")) {
                continue;
            }


            if (params.equals("")) {
                params += beginLetter + paramName + "=" + paramValues.get(paramName);
            } else {
                params += "&" + paramName + "=" + paramValues.get(paramName);
            }
        }

        return params;
    }

 

 

//生成out_refund_no

public String getRandomUUID() {
        java.util.Date dateNow = new java.util.Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String dateNowStr = dateFormat.format(dateNow);
        StringBuffer sb = new StringBuffer(dateNowStr);
        Random rd = new Random();
        String n = "";
        int rdGet;

        do {
            rdGet = Math.abs(rd.nextInt()) % 10 + 48;
            char num1 = (char) rdGet;
            String dd = Character.toString(num1);
            n += dd;
        } while (n.length() < 6);
        sb.append(n);

        return sb.toString();
    }

//生成nonce_str

    public static String getUUId(){
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

  /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); // .replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }

 

public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

  /**
     * 向微信退款发送请求
     * @param mchid 商户号
     * @param url 微信退款请求路径
     * @param parm 请求的参数
     * @return
     */
    
    public static String refundHttpRequest(String mchid,String url,String parm,String filepath) {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore;
        try {
            keyStore = KeyStore.getInstance("PKCS12");
            File file = (ResourceUtils.getFile(filepath));//证书路径
            FileInputStream instream = new FileInputStream(file);
            keyStore.load(instream,mchid.toCharArray());//这里写密码..默认是你的MCHID
               SSLContext sslcontext = SSLContexts.custom()
                        .loadKeyMaterial(keyStore, mchid.toCharArray())//这里密码
                        .build();
               SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
                CloseableHttpClient httpclient = HttpClients.custom()
                        .setSSLSocketFactory(sslsf)
                        .build();
                try {
                    HttpPost httpost = new HttpPost(url);
                    httpost.setEntity(new StringEntity(parm, "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();
                }
                
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        }
     return null;
    }
    

 /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }
    }

 

//微信返回的加密信息进行解密

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Security;
import java.util.Map;

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

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;

public class Aes {

    static Logger logger = LoggerFactory.getLogger(Aes.class);
    private static MessageDigest sMd5MessageDigest;
    private static StringBuilder sStringBuilder;

    private static void removeCryptographyRestrictions() {
        if (!isRestrictedCryptography()) {
            logger.info("Cryptography restrictions removal not needed");
            return;
        }
        try {

            final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
            final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
            final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

            final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
            isRestrictedField.setAccessible(true);
            final Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
            isRestrictedField.set(null, false);

            final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
            defaultPolicyField.setAccessible(true);
            final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

            final Field perms = cryptoPermissions.getDeclaredField("perms");
            perms.setAccessible(true);
            ((Map<?, ?>) perms.get(defaultPolicy)).clear();

            final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
            instance.setAccessible(true);
            defaultPolicy.add((Permission) instance.get(null));

            logger.info("Successfully removed cryptography restrictions");
        } catch (final Exception e) {
            logger.error("Failed to remove cryptography restrictions", e);
        }
    }

    private static boolean isRestrictedCryptography() {
        // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
        final String name = System.getProperty("java.runtime.name");
        final String ver = System.getProperty("java.version");
        return name != null && name.equals("Java(TM) SE Runtime Environment")
                && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
    }
    
    //破解jdk密文长度限制,因为在使用ase解密的时候,会有长度限制
    public  void Crack(){
        try {
            Class<?> clazz = Class.forName("javax.crypto.JceSecurity");
            Field nameField = clazz.getDeclaredField("isRestricted");
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
            nameField.setAccessible(true);
            nameField.set(null, java.lang.Boolean.FALSE);
        } catch (Exception ex) {
            logger.info("破解密文长度出现异常=="+ex);
        }
    }

    public  String getRefundDecrypt(String reqInfoSecret, String key) {
        removeCryptographyRestrictions();
        //解密之前先调用破解方法
        Crack();
        String result = "";
        try {
            Security.addProvider(new BouncyCastleProvider());
            BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            byte[] bt = decoder.decodeBuffer(reqInfoSecret);
            String md5key = md5(key).toLowerCase();
            SecretKey secretKey = new SecretKeySpec(md5key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] resultbt = cipher.doFinal(bt);
            result = new String(resultbt);
        } catch (Exception e) {
            logger.info("转换过程中出现的异常==" + e);
        }
        return result;
    }

    public static String md5(String s) {
        md5();
        sMd5MessageDigest.reset();
        sMd5MessageDigest.update(s.getBytes());
        byte[] digest = sMd5MessageDigest.digest();
        sStringBuilder.setLength(0);

        for (int i = 0; i < digest.length; ++i) {
            int b = digest[i] & 255;
            if (b < 16) {
                sStringBuilder.append(‘0‘);
            }

            sStringBuilder.append(Integer.toHexString(b));
        }

        return sStringBuilder.toString().toUpperCase();
    }

    public static void md5(){
        try {
            sMd5MessageDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException ex) {
            logger.info("md5出现异常==="+ex);    
        }
        sStringBuilder = new StringBuilder();
    }
/*
    public static void main(String[] args) {
        String a ="";
        String key = "";
        Aes as=new Aes();
        System.out.println(a);
        System.out.println(key);
        String B =as. getRefundDecrypt(a, key);
        System.out.println(B);
        try {
            Map<String, String> map = WXPayUtil.xmlToMap(B);
            System.err.println(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }*/
}

说明:

微信的加密信息要用到ase解密,而jdk对密文的长度是有限制的,JAVA运行环境默认不允许256位密钥的AES加解密,会报Illegal key size or default parameters.。在jdk1.8.5后的版本它会自带两个文件夹,一个是有限制的,一个是没有限制的,只要切换为没有限制的就可以,但是之前的版本,是没有的,所以只能将有限制的文件给替换掉

在官方网站下载JCE无限制权限策略文件:

JDK7版本JCE下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

JDK8版本JCE下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

注意:你的jdk版本是几就下载对应的版本

下载解压后就是替换${java_home}/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar。

除此之外还有一种办法就是将其规则给破解掉,但是这种方法是不推荐使用的,我因为特殊情况就是用了这种办法,在上面的代码中已经有说明

https://blog.csdn.net/z199172177/article/details/78954503 ;这是破解方法的出处

https://blog.csdn.net/tangtao_xp/article/details/84944049;这里也有说明,可以查看

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

微信退款证书说明:

证书放在项目中,在服务器上是堵不到的,你在本地测试的时候是可以的,所以要在pom.xml中配置一下,在<plugins></plugins>中添加

         <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>  
                <artifactId>maven-resources-plugin</artifactId>  //这里要注意不要和maven-compiler-plugin搞成一样
                <configuration>
                    <encoding>UTF-8</encoding>
                    <!-- 过滤后缀为pem、pfx的证书文件 -->
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>pem</nonFilteredFileExtension>
                        <nonFilteredFileExtension>pfx</nonFilteredFileExtension>
                        <nonFilteredFileExtension>p12</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
        </plugins>

 

 

 

 

 

 

 

 

 

 

 


   

微信退款接口开发

上一篇:小程序的开发学习


下一篇:vue微信分享链接添加动态参数