Java集成微信小程序支付和退款

微信支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter1_1_1.shtml

微信退款文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml

1、微信小程序支付

前提

在进行对接微信支付之前,我们首先需要将以下几点准备好:

  • 申请APPID
  • 申请商户号
  • 小程序开通微信支付,绑定已经申请好的商户号。登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(开通申请要求小程序已发布上线)
注意事项
  • appid必须为最后拉起收银台的小程序appid;
  • mch_id为和appid成对绑定的支付商户号,收款资金会进入该商户号;
  • trade_type请填写JSAPI;
  • openid为appid对应的用户标识,即使用wx.login接口获得的openid。

本文主要记录后端步骤,前端步骤无非就是获取后端数据然后调用提供的API进行支付,大家可自行查看官方文档。

1. 支付业务流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNr92RdW-1683010524685)(E:\PRD\Images\image-20230502113439837.png)]

2. 导入依赖
implementation group: 'com.github.wechatpay-apiv3', name: 'wechatpay-apache-httpclient', version: '0.4.7'
implementation group: 'com.github.wxpay',name: 'wxpay-sdk',version: '0.0.3'
3. 编写相关工具类
解析Xml工具类
/**
 * @author zzw
 * @description TODO 解析xml工具类
 * @date 2023-04-28 14:02
 */
public class XMLUtil {  

	/**  
     * 瑙f瀽xml,杩斿洖绗竴绾у厓绱犻敭鍊煎銆傚鏋滅锟�?绾у厓绱犳湁瀛愯妭鐐癸紝鍒欐鑺傜偣鐨勶拷?锟芥槸瀛愯妭鐐圭殑xml鏁版嵁锟�?  
     * @param strxml  
     * @author Lyp
     * @return  
     * @throws JDOMException  
     * @throws IOException  
     */    
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {    
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  
        if(null == strxml || "".equals(strxml)) {    
            return null;    
        }    

        Map m = new HashMap();    

        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));    
        SAXBuilder builder = new SAXBuilder();    
        Document doc = builder.build(in);    
        Element root = doc.getRootElement();    
        List list = root.getChildren();    
        Iterator it = list.iterator();    
        while(it.hasNext()) {    
            Element e = (Element) it.next();    
            String k = e.getName();    
            String v = "";    
            List children = e.getChildren();    
            if(children.isEmpty()) {    
                v = e.getTextNormalize();    
            } else {    
                v = XMLUtil.getChildrenText(children);    
            }    

            m.put(k, v);    
        }    

        //鍏抽棴锟�?    
        in.close();    

        return m;    
    }    
    
    /**  
     * 鑾峰彇瀛愮粨鐐圭殑xml  
     * @param children  
     * @return String  
     */    
    public static String getChildrenText(List children) {    
        StringBuffer sb = new StringBuffer();    
        if(!children.isEmpty()) {    
            Iterator it = children.iterator();    
            while(it.hasNext()) {    
                Element e = (Element) it.next();    
                String name = e.getName();    
                String value = e.getTextNormalize();    
                List list = e.getChildren();    
                sb.append("<" + name + ">");    
                if(!list.isEmpty()) {    
                    sb.append(XMLUtil.getChildrenText(list));    
                }    
                sb.append(value);    
                sb.append("</" + name + ">");    
            }    
        }    

        return sb.toString();    
    }
}  

微信支付工具类

/**
 * @author zzw
 * @description TODO 微信支付工具类
 * @date 2023-04-28 14:02
 */
public class WXPayUtil {
	/**
     * 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>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.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.getTagName());
                }
            }
            try {
            	stream.close();
            } catch (Exception ex) {
               // do nothing
            }
            return data;
		} catch (Exception ex) {
			WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
                   throw ex;
        }

	}
   
   /**
     * ??ap?????ML?????????
     * @param data Map??????
     * @return XML?????????
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.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;
    }
    
    /**
     * ?????? sign ?? XML ????????
     * @param data Map??????
     * @param key API???
     * @return ???sign?????ML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, SignType.MD5);
    }

    /**
     * ?????? sign ?? XML ????????
     * @param data Map??????
     * @param key API???
     * @param signType ??????
     * @return ???sign?????ML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }

    /**
     * ????????????
     *
     * @param xmlStr XML??????
     * @param key API???
     * @return ?????????
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * ????????????????????ign???????????alse?????D5?????
     *
     * @param data Map??????
     * @param key API???
     * @return ?????????
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, SignType.MD5);
    }

    /**
     * ????????????????????ign???????????alse??
     *
     * @param data Map??????
     * @param key API???
     * @param signType ??????
     * @return ?????????
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * ??????
     *
     * @param data ????????
     * @param key API???
     * @return ???
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

    /**
     * ??????. ?????????sign_type?????????signType????????????
     *
     * @param data ????????
     * @param key API???
     * @param signType ??????
     * @return ???
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // ??????????????????
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }
    
    /**
     * ??????????? Nonce Str
     *
     * @return String ????????
     */
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    
   /**
     * ??? 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();
    }

    /**
     * ??? HMACSHA256
     * @param data ????????
     * @param key ???
     * @return ??????
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(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();
    }

    /**
     * ???
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * ?????????????????
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * ??????????????????
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * ??? uuid?? ?????????????????? nonce_str
     * @return
     */
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    
    /**
     * ??????ip???
     * @return
     */
    public static String localip(){
        String ip=null;
        Enumeration allNetInterfaces;
        try {
            allNetInterfaces=NetworkInterface.getNetworkInterfaces();
            while(allNetInterfaces.hasMoreElements()){
            NetworkInterface netInterface=(NetworkInterface)allNetInterfaces.nextElement();
            List<InterfaceAddress> InterfaceAddress=netInterface.getInterfaceAddresses();
            for(InterfaceAddress add:InterfaceAddress){
                InetAddress Ip=add.getAddress();
                if(Ip!=null&&Ip instanceof Inet4Address){
                    ip=Ip.getHostAddress();
                }

                }
            }
        } catch (SocketException e) {
            System.out.println("??????ip???????????");
            e.printStackTrace();

        }
        return ip;
    }
}
支付相关工具类
public class PayForUtil {  

	private static Logger lg=Logger.getLogger(PayForUtil.class);  

	/**  
     * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。  
     * @return boolean  
     */    
	public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {    
		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(!"sign".equals(k) && null != v && !"".equals(v)) {    
            	sb.append(k + "=" + v + "&");    
         	}    
     	}    
		sb.append("key=" + API_KEY); 
        //算出摘要    
        String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();    
        String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();    
        return tenpaySign.equals(mysign);    
    }    
    /**  
     * @Description:sign签名  
     * @param characterEncoding  
     *            编码格式  
     * @param parameters  
     *            请求参数  
     * @return  
     */    
    public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {    
        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=" + API_KEY);    
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();    
        return sign;    
    }    

    /**  
     * @Description:将请求参数转换为xml格式的string  
     * @param parameters  
     *            请求参数  
     * @return  
     */    
    public static String getRequestXml(SortedMap<Object,Object> parameters) {    
        StringBuffer sb = new StringBuffer();    
        sb.append("<xml>");    
        Set es = parameters.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 ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {    
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");    
            } else {    
                sb.append("<" + k + ">" + v + "</" + k + ">");    
            }    
        }    
        sb.append("</xml>");    
        return sb.toString();    
    }    

    /**  
     * 取出一个指定长度大小的随机正整数.  
     *   
     * @param length  
     *            int 设定所取出随机数的长度。length小于11  
     * @return int 返回生成的随机数。  
     */    
    public static int buildRandom(int length) {    
        int num = 1;    
        double random = Math.random();    
        if (random < 0.1) {    
            random = random + 0.1;    
        }    
        for (int i = 0; i < length; i++) {    
            num = num * 10;    
        }    
        return (int) ((random * num));    
    }    

    /**  
     * 获取当前时间 yyyyMMddHHmmss  
     * @return String  
     */    
    public static String getCurrTime() {    
        Date now = new Date();    
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");    
        String s = outFormat.format(now);    
        return s;    
    }  
}  
金额换算工具类
public class Utils {
    public static String getFee(String fee) {
        BigDecimal a = new BigDecimal(fee).multiply(BigDecimal.valueOf(100));
        fee = String.valueOf(a);//浮点变量a转换为字符串str
        //先把小数点后的0截取掉
        int idx = fee.lastIndexOf(".");//查找小数点的位置
        String strNum = null;
        if (idx != -1) {
            strNum = fee.substring(0, idx);//截取从字符串开始到小数点位置的字符串,就是整数部分
        } else {
            strNum = fee;
        }
        //将截取后的金额转换为整数
        int num = Integer.valueOf(strNum);//把整数部分通过Integer.valueof方法转换为数字
        //工行金额以分为单位,将金额* 100
        return num + "";
    }
    
    public static boolean isNumeric(String str){
        for (int i = str.length();--i>=0;){
            if (!Character.isDigit(str.charAt(i))){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(getFee("2.50"));
    }
}
4. 生成预支付交易单

商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。

入参:

/**
 * @author zzw
 * @date 2023/4/1 15:51
 * @description TODO 微信WxPayConfig配置
 */
@Data
public class PayVo {
    private String description; // 商品描述
    private String out_trade_no; // 商户订单号
    private String time_expire; // 订单失效时间
    private String attach; // 附加数据
    private String notify_url; // 回调
    private String openid; // 用户标识
    private String total; // 总金额
}

预支付订单业务逻辑层

	/**
     * 电子处方订单支付接口
     * @param payVo
     * @return
     */
    @Override
    public String payPrescription(PayVo payVo) {
        try {
            String errorString = null;
            String errorCode = null;
            String resXml = null;
            String nonce_str = WXPayUtil.generateNonceStr();
            JSONArray json = null;
            Map map = null;
            SortedMap<Object, Object> orderMap = new TreeMap<Object, Object>();
            /*-----  1.生成预支付订单需要的的package数据-----*/
            SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
            // mchid直连商户号、wxappid应用ID、paysuccesscallback通知地址 都需要从程序内部获取,从而保证支付的安全性
            // paysuccesscallback 通知地址需要跟程序内部的地址一致
            packageParams.put("appid",wxappid); 
            packageParams.put("mch_id", mchid);
            packageParams.put("nonce_str", nonce_str);
            packageParams.put("notify_url", paysuccesscallback);
            packageParams.put("attach", payVo.getAttach());
            packageParams.put("openid", payVo.getOpenid());
            packageParams.put("out_trade_no", prescriptionPayVo.getOut_trade_no());
            packageParams.put("spbill_create_ip", WXPayUtil.localip());
            packageParams.put("total_fee",
            		   com.cn.ih.java.main.utils.Utils.getFee(payVo.getTotal()));
            packageParams.put("trade_type", "JSAPI");
            packageParams.put("fee_type", "CNY");
            packageParams.put("body", prescriptionPayVo.getDescription());
            packageParams.put("sign_type", "MD5");
            /*----2.根据package生成签名sign---- */
            String sign = PayForUtil.createSign("UTF-8", packageParams, selfkey);
            packageParams.put("sign", sign);
            logger.info("###signWX" + sign);
            String requestXML = PayForUtil.getRequestXml(packageParams);
            logger.info("###requestXML" + requestXML);
            resXml = WxHttpUtil.postData("https://api.mch.weixin.qq.com/pay/unifiedorder",requestXML);
            logger.info("###resXml" + resXml);
            if (null == resXml || "".equals(resXml)) {
                errorString = "接口异常!返回数据为空,请检查接口是否可用;接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder" ;
                resXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <xml> <return_code>" + "fail"
                        + "</return_code><return_msg>" + errorString + "</return_msg></xml> ";
                logger.info("###" + errorString);
                orderMap.put("ResultCode", "-1");
                orderMap.put("ErrorMsg", "执行失败。");
                json = JSONArray.fromObject(orderMap);
            } else {
                try {
                    map = XMLUtil.doXMLParse(resXml);
                    String return_code = (String) map.get("return_code");
                    System.out.println(return_code);
                    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
                    String nonceStr = String.valueOf(System.currentTimeMillis());
                    orderMap.put("appId", sysConfig.getWxappid());
                    // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                    orderMap.put("timeStamp", timestamp);
                    orderMap.put("nonceStr", nonceStr);
                    logger.info("###nonceStr" + nonceStr);
                    orderMap.put("package", "prepay_id=" + map.get("prepay_id"));
                    orderMap.put("signType", "MD5");
                    String sign1 = PayForUtil.createSign("UTF-8", orderMap, selfkey);
                    orderMap.put("paySign", sign1);
                    orderMap.put("resCode", return_code);
                    logger.info("###sign" + map.get("sign"));
                    json = JSONArray.fromObject(orderMap);
                } catch (JDOMException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            logger.info("###调用JSAPI预支付下单接口需要返回的参数" + json.toString());
            return json.toString();
        }catch(Exception e) {
            e.printStackTrace();
            return super.errorResult(e.getMessage());
        }
5. 支付成功回调
 	@ApiOperation(value = "支付成功回调")
    @RequestMapping(value="/paySucessCallMethod")
    public void paySuccessCallMethod(HttpServletRequest req, HttpServletResponse response){
        logger.info("========== 开始处理订单支付回调通知  ==========");
        String resultStr = null;
        String str = null;
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        try {
            inputStream = req.getInputStream();
            String s;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            while ((s = in.readLine()) != null) {
                sb.append(s);
            }
            in.close();
            inputStream.close();
            // 解析xml成map
            Map<String, String> m = new HashMap<String, String>();
            m = XMLUtil.doXMLParse(sb.toString());
            // 过滤空设置TreeMap,扫码
            SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
            Iterator<String> it = m.keySet().iterator();
            while (it.hasNext()) {
                String parameter = it.next();
                String parameterValue = m.get(parameter);
                String v = "";
                if (null != parameterValue) {
                    v = parameterValue.trim();
                }
                packageParams.put(parameter, v);
            }
            // 微信支付的api密钥
            String key = privatekey;
            logger.info("微信支付返回回来的参数:" + packageParams);
            String return_code = (String) packageParams.get("return_code");
            String result_code = (String) packageParams.get("result_code");
            DrugOrder drugOrder = null;
            // 判断签名是否正确
            if (PayForUtil.isTenpaySign("UTF-8", packageParams, key)) {
                // -------------------------------
                // 处理业务开始
                // --------------------------
                String resXml = "";
                if (StrKit.notBlank(return_code) && StrKit.notBlank(result_code)
                        && return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")) {
                    // 支付成功
                    // 执行自己的业务逻辑
                    // 声明日志插入结果对象
                    String outTradeNo = ((String) packageParams.get("out_trade_no"));
                    logger.info("#########################开始校验订单号:"+outTradeNo+"是否存在" );
                    Order order = orderPayRepository.findOrderPayByOutTradeNo(outTradeNo);
                    if(!StringUtils.isEmpty(order)) {
                        Order order  = order;
                        // 非第一次回调,已经成功生成订单,直接返回结果就可以了
                        /
                        // 执行自己业务逻辑结束
                        logger.info("给微信回调返回成功");
                        // 通知微信异步成功不然会一直通知后台八次之后交易失败
                        resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                                + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    }else {
                        //第一次回调
                        // **************************1.流水表插入*******************************//
                        logger.info("###流水表插入开始");
                        logger.info("###流水表插入开始packageParams:",packageParams);
						
                        /* 执行相关业务逻辑,支付成功,生成支付订单 */
                        
                        logger.info("###流水表插入结束");
                        // 执行自己业务逻辑结束
                        logger.info("给微信回调返回成功");
                        // 通知微信异步成功不然会一直通知后台八次之后交易失败
                        resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                                + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                   }
                } else {
                    logger.info("支付失败,错误信息:" + packageParams.get("err_code"));
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                }
                // ------------------------------
                // 处理业务完毕
                // ------------------------------
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } else {
                logger.info("通知签名验证失败");
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        logger.info("==========结束处理处方支付回调通知==========");
    }

2、微信小程序退款

当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。

前提

(1)微信退款所需要的配置! 退款只需要证书即可。微信退款需要证书:资金发生变化需要证书。支付接口不需要。点击证书使用。

按照步骤:下载证书。

(2)使用API证书

◆ apiclient_cert.p12是商户证书文件,除PHP外的开发均使用此证书文件。
◆ 商户如果使用.NET环境开发,请确认Framework版本大于2.0,必须在操作系统上双击安装证书apiclient_cert.p12后才能被正常调用。
◆ API证书调用或安装需要使用到密码,该密码的值为微信商户号(mch_id)
(3)API证书安全

1.证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
2.建议将证书文件名改为复杂且不容易猜测的文件名;
3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。

证书相关API:https://blog.csdn.net/liyanlei5858/article/details/120021692

注意:

1、交易时间超过一年的订单无法提交退款

2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号

3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

4、每个支付订单的部分退款次数不能超过50次

5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败

6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果

7、一个月之前的订单申请退款频率限制为:5000/min

8、同一笔订单多次退款的请求需相隔1分钟

1. 编写退款相关工具类
public class PayConfig implements WXPayConfig {
   private byte[] certData;

    /**
     * 微信退款所需要的配置! 退款只需要证书即可。
     * @throws Exception
     */
    public PayConfig() throws Exception {
        File file = new File(WxPayConstant.getPrivateppath());
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    @Override
    public String getAppID() {
        return WxPayConstant.getWxappid();  //appid
    }

    public String getAPPSECRET(){
        return WxPayConstant.getAppsecret();  //appSecret
    }

    @Override
    public String getMchID() {
        return WxPayConstant.getMchid();   //商户号id
    }

    @Override
    public String getKey() {
        return WxPayConstant.getPrivatekey();  //支付API密钥
    }
    @Override
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    @Override
    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs() {
        return 10000;
    }
}
2. 退款代码
@Override
public String refund(String transaction_id) {
    try {
        logger.info("#############################################执行微信支付退款接口refund方法开始");
        
       /* 执行微信小程序退款相关业务逻辑 */
        
        logger.info("==========================微信退款开始!!========================");
        Order order = orderRepository.findOrderByTransactionId(transaction_id);
        Map<String,String> data = new HashMap<String,String>();
        Integer pay = order.getTotalFee();
        OrderRefund orderRefund = orderRefundRepository.findOrderRefundByTransactionId(transaction_id);
        if (!ObjectUtils.isEmpty(drugOrderRefund)){
            logger.info("==========================此订单号对应的退款表已存在,重新执行退款操作!!========================"+orderRefund.toString());
            data.put("out_refund_no" , orderRefund.getOutRefundNo());
        }else {
            String out_refund_no = UUIDHexGenerator.createTradeNo();
            data.put("out_refund_no" , out_refund_no);
        }
//            data.put("notify_url", null); // 根据自己的需求决定需要回调地址
        data.put("transaction_id" , transaction_id);
        data.put("total_fee" , String.valueOf(pay));
        data.put("refund_fee" , String.valueOf(pay));
        PayConfig config = new PayConfig();
        WXPay wxpay = new WXPay(config);
        data.put("appid" , appid);
        data.put("mch_id" , mch_id);
        data.put("nonce_str" , WXPayUtil.generateNonceStr());
        data.put("sign" , MD5Util.getSign(data));
        Map<String,String> resp = wxpay.refund(data);//获取微信退款返回的结果
        logger.info("微信返回信息:\n" + resp);
        String return_code = resp.get("return_code");   // 返回状态码
        String return_msg = resp.get("return_msg");     // 返回信息
        // 不管有没有退款成功,都要保存到退款表,如果退款成功,则删除支付表,如果退款失败,则退款表状态为失败
        logger.info("####################################################################流水表插入开始");
        OrderRefund orderRefund=new OrderRefund();
        List<OrderRefund> orderRefundList = orderRefundRepository.findOrderRefundByOutRefundNo(order.getOutTradeNo());
        if (CollectionUtils.isEmpty(orderRefundList)) {
        
        	//保存退款信息

            logger.info("#############################################################流水表对象赋值操作结束");
            // 调用service,插入日志表
            logger.info("#######################################################调用service,插入流水表开始");
            if("SUCCESS".equals(return_code)){
            	String result_code = resp.get("result_code");       //业务结果
                String err_code_des = resp.get("err_code_des");     //错误代码描述
                if("SUCCESS".equals(result_code)){
                
                	// 执行退款相关业务逻辑
                	
					logger.info("####################################################################流水表插入结束");
                    // 执行自己业务逻辑结束
                    logger.info("#####################################################################退款成功");
				}else{
                        // 退款失败
                }
			}else{
                     // 退款失败
            }
            OrderRefund saveAndFlush = orderRefundRepository.saveAndFlush(orderRefund);
            logger.info("#####################################################################执行退款回调结束,退款表type: "+saveAndFlush.getType());
		}
            resMap.put("timestamp", TimeHelper.getCurrentTime());
            String results = JsonHelper.parserMap(resMap);
            return results;
	} catch (Exception e) {
    	e.printStackTrace();
		return super.errorResult(e.getMessage());
	}
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值