Java integrated WeChat applet payment and refund

WeChat payment documentation: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter1_1_1.shtml

WeChat refund document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml

1. WeChat Mini Program Payment

premise

Before docking WeChat payment, we first need to prepare the following points:

  • Apply for APPID
  • Apply for a merchant number
  • The Mini Program opens WeChat Pay and binds the merchant account that has been applied for. Log in to the background of the mini program (mp.weixin.qq.com). Click WeChat Pay on the left navigation bar to activate it on the page. (A small program for opening application requirements has been released and launched)

Precautions

  • appid must be the appid of the applet that pulled up the cashier at the end;
  • mch_id is the payment merchant ID paired with appid, and the received funds will enter the merchant ID;
  • Please fill in JSAPI for trade_type;
  • openid is the user ID corresponding to appid, that is, the openid obtained by using the wx.login interface.

This article mainly records the back-end steps. The front-end steps are nothing more than obtaining back-end data and then calling the provided API to make payment. You can check the official documents by yourself.

1. Payment business flow chart

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-JNr92RdW-1683010524685)(E:\PRD\Images\image-20230502113439837.png)]

2. Import dependencies

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. Write related tool classes

Parsing Xml tool class
/**
 * @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();    
    }
}  

WeChat payment tools

/**
 * @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;
    }
}
Payment related tools
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;    
    }  
}  
Amount conversion tools
public class Utils {
    
    
    public static String getFee(String fee) {
    
    
        System.out.println(Float.valueOf(fee).floatValue());
        Float a =Float.valueOf(fee).floatValue()*100;
        System.out.println(a);
            fee = String.valueOf(a);//浮点变量a转换为字符串str
        //先把小数点后的0截取掉
        int idx = fee.lastIndexOf(".");//查找小数点的位置
        System.out.println(idx);
        String strNum = fee.substring(0,idx);//截取从字符串开始到小数点位置的字符串,就是整数部分
        System.out.println(strNum);
        //将截取后的金额转换为整数
        int num = Integer.valueOf(strNum);//把整数部分通过Integer.valueof方法转换为数字
        //工行金额以分为单位,将金额* 100
       // num = num * 100;
        System.out.println(num);
        return num+"";
    }

    public static String getFee2(String fee) {
    
    
        System.out.println(Float.valueOf(fee).floatValue());
      //  System.out.println(  String.valueOf(+ Float.parseFloat(fee)) );
        Float a =Float.valueOf(fee).floatValue()/100;
        System.out.println(a);
         fee = String.valueOf(a);//浮点变量a转换为字符串str
        return fee+"";
    }

    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. Generate a prepaid transaction order

The merchant system first calls this interface to generate a prepayment transaction order in the WeChat payment service background, returns the correct prepayment transaction session ID, and then generates transaction strings according to different scenarios such as Native, JSAPI, and APP to initiate payment.

Enter:

/**
 * @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; // 总金额
}

Prepaid order business logic layer

	/**
     * 电子处方订单支付接口
     * @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. Payment success callback

 	@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. Wechat mini program refund

Within one year after the transaction occurs, if a refund is required due to the buyer or seller, the seller can refund the payment amount to the buyer through the refund interface, and WeChat Pay will make the payment after receiving the refund request and verifying it successfully. Return it to the buyer's account according to the original route.

premise

(1) The configuration required for WeChat refund! Only a certificate is required for refund. Certificate is required for WeChat refund: A certificate is required for fund changes. Payment interface is not required. Click on the certificate to use.

Follow the steps: Download the certificate.

(2) Use API certificate

◆ apiclient_cert.p12 is the merchant certificate file, which is used for development except PHP.
◆ If the merchant uses the .NET environment for development, please confirm that the Framework version is greater than 2.0, and the certificate apiclient_cert.p12 must be installed by double-clicking on the operating system before it can be called normally.
◆ A password is required to call or install the API certificate, and the value of the password is the WeChat merchant number (mch_id)
(3) API certificate security

1. The certificate file cannot be placed in the virtual directory of the web server, but should be placed in a directory with access control to prevent it from being downloaded by others; 2.
It is recommended to change the name of the certificate file to a file name that is complicated and not easy to guess;
3. Merchant server Do a good job of virus and Trojan horse protection to prevent certificate files from being stolen by illegal intruders.

Certificate related API: https://blog.csdn.net/liyanlei5858/article/details/120021692

Notice:

1. Orders with a transaction time of more than one year cannot submit a refund

2. WeChat payment refund supports multiple refunds for a single transaction (no more than 50 times). For multiple refunds, you need to submit the merchant order number of the original payment order and set a different refund order number. The total amount applied for refund cannot exceed the order amount. Resubmit after a failed refund, please do not change the refund order number, please use the original merchant refund order number

3. Error or invalid request frequency limit: 6qps, that is, no more than 6 abnormal or wrong refund application requests per second

4. The number of partial refunds for each payment order cannot exceed 50 times

5. If the same user has multiple refunds, it is recommended to refund in different batches to avoid refund failure caused by concurrent refunds

6. The return of the refund application interface only represents the acceptance of the business. Whether the refund is successful or not, you need to obtain the result through the refund query interface

7. The frequency limit for applying for refunds for orders made one month ago is: 5000/min

8. Multiple refund requests for the same order need to be separated by 1 minute

1. Write refund-related tool classes

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. Refund code

@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());
	}
}

Guess you like

Origin blog.csdn.net/ITKidKid/article/details/130463701