微信、支付宝各种支付退款

java 版微信、支付宝各种支付退款

前言

最近整理了一下自己做过的各种支付退款的业务,并整理如下,只是大致思路代码不保证百分百没有问题但是都是经过我以前实际验证过并投入生产环境的,省略了一些和支付无关的业务流程。

java 微信App支付

  • 参考时序图了解大致流程。

微信App支付文档

  • 大致步骤:

    • 步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
    • 步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见统一下单API
    • 步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay
    • 步骤4:商户APP调起微信支付。api参见本章节app端开发步骤说明
    • 步骤5:商户后台接收支付通知。api参见支付结果通知API
    • 步骤6:商户后台查询支付结果。,api参见查询订单API
  • java 服务端预下单代码如下
    • 预下单业务逻辑
    /**
     * 微信App支付
     *
     * @param request
     * @param orderId
     */
    public Map<String, String> appPay(HttpServletRequest request, Long orderId) {
        Order order = orderService.findOne(orderId);
        if (order.getStatus() != OrderStatusEnum.CREATE.getStatus()) {
            log.error("order status error orderId:{}", orderId);
            return null;
        }
        String spbill_create_ip = AppUtil.getIpAddress(request);
        if (!AppUtil.isIp(spbill_create_ip)) {
            spbill_create_ip = "127.0.0.1";
        }
        String nonce_str = 1 + RandomUtil.getStr(12);
        // 微信app支付十个必须要传入的参数
        Map<String, String> params = new HashMap<>();
        // appId
        params.put("appid", appProperties.getWx().getApp_id());
        // 微信支付商户号
        params.put("mch_id", appProperties.getWx().getMch_id());
        // 随机字符串
        params.put("nonce_str", nonce_str);
        // 商品描述
        params.put("body", "App weChat pay!");
        // 商户订单号
        params.put("out_trade_no", order.getOutTradeNo());
        // 总金额(分)
        params.put("total_fee", order.getTotalFee().toString());
        // 订单生成的机器IP,指用户浏览器端IP
        params.put("spbill_create_ip", spbill_create_ip);
        // 回调url
        params.put("notify_url", appProperties.getWx().getNotify_url());
        // 交易类型:JS_API=公众号支付、NATIVE=扫码支付、APP=app支付
        params.put("trade_type", "APP");
        // 签名
        String sign = AppUtil.createSign(params, appProperties.getWx().getApi_key());
        params.put("sign", "sign");
        String xmlData = AppUtil.mapToXml(params);
        String wxRetXmlData = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order(), xmlData, null);
        Map retData = AppUtil.xmlToMap(wxRetXmlData);
        log.info("微信返回信息:{}", retData);
    
        // 封装参数返回App端
        Map<String, String> result = new HashMap<>();
        result.put("appid", appProperties.getWx().getApp_id());
        result.put("partnerid", appProperties.getWx().getMch_id());
        result.put("prepayid", retData.get("prepay_id").toString());
        result.put("noncestr", nonce_str);
        result.put("timestamp", RandomUtil.getDateStr(13));
        result.put("package", "Sign=WXPay");
        result.put("sign", AppUtil.createSign(result, appProperties.getWx().getApi_key()));
        return result;
    }
    • AppUtil.java
    import lombok.extern.slf4j.Slf4j;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    
    import javax.crypto.KeyGenerator;
    import javax.crypto.Mac;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import javax.servlet.http.HttpServletRequest;
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Unmarshaller;
    import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import java.io.*;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.util.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     - 项目常用工具
     *
     - @author Leone
     - @since 2018-05-10
     **/
    @Slf4j
    public class AppUtil {
    
        public static String urlEncoder(String value) {
            try {
                return URLEncoder.encode(value, StandardCharsets.UTF_8.displayName());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static String urlDecoder(String value) {
            try {
                return URLDecoder.decode(value, StandardCharsets.UTF_8.displayName());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         - 校验手机号
         *
         - @param phone
         - @return
         */
        public static boolean isMobile(String phone) {
            Pattern pattern = Pattern.compile("^[1][3,4,5,7,8,9][0-9]{9}$");
            Matcher matcher = pattern.matcher(phone);
            return matcher.matches();
        }
    
        /**
         - 匹配ip是否合法
         *
         - @param ip
         - @return
         */
        public static Boolean isIp(String ip) {
            String re = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
            Pattern pattern = Pattern.compile(re);
            Matcher matcher = pattern.matcher(ip);
            return matcher.matches();
        }
    
        /**
         - 支付参数生成签名
         *
         - @param params
         - @param apiKey
         - @return
         */
        public static String createSign(Map<String, String> params, String apiKey) {
            StringBuilder sb = new StringBuilder();
            Set<Map.Entry<String, String>> set = params.entrySet();
            for (Map.Entry<String, String> entry : set) {
                String k = entry.getKey();
                Object v = entry.getValue();
                if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                    sb.append(k).append("=").append(v).append("&");
                }
            }
            sb.append("key=").append(apiKey);
            return MD5(sb.toString()).toUpperCase();
        }
    
        /**
         - 支付参数生成签名
         *
         - @param params
         - @return
         */
        public static String createSign(Map<String, String> params) {
            StringBuilder sb = new StringBuilder();
            Set<Map.Entry<String, String>> set = params.entrySet();
            for (Map.Entry<String, String> entry : set) {
                String k = entry.getKey();
                Object v = entry.getValue();
            }
            return MD5(sb.toString()).toUpperCase();
        }
    
        /**
         - 生成md5摘要
         *
         - @param content
         - @return
         */
        public static String MD5(String content) {
            try {
                MessageDigest messageDigest = MessageDigest.getInstance("MD5");
                messageDigest.update(content.getBytes(StandardCharsets.UTF_8));
                byte[] hashCode = messageDigest.digest();
                return new HexBinaryAdapter().marshal(hashCode).toLowerCase();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         - 生成 HMAC_SHA256
         *
         - @param content
         - @param api_key
         - @return
         - @throws Exception
         */
        public static String HMAC_SHA256(String content, String api_key) {
            try {
                KeyGenerator generator = KeyGenerator.getInstance("HmacSHA256");
                SecretKey secretKey = generator.generateKey();
                byte[] key = secretKey.getEncoded();
                SecretKey secretKeySpec = new SecretKeySpec(api_key.getBytes(), "HmacSHA256");
                Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
                mac.init(secretKeySpec);
                byte[] digest = mac.doFinal(content.getBytes());
                return new HexBinaryAdapter().marshal(digest).toLowerCase();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
    
        }
    
        /**
         - XML格式字符串转换为Map
         *
         - @param xmlStr
         - @return
         */
        public static Map<String, String> xmlToMap(String xmlStr) {
            try (InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8))) {
                Map<String, String> data = new HashMap<>();
                DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
                Document doc = documentBuilder.parse(inputStream);
                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) {
                        Element element = (Element) node;
                        data.put(element.getNodeName(), element.getTextContent());
                    }
                }
                return data;
            } catch (Exception ex) {
                log.warn("xml convert to map failed message: {}", ex.getMessage());
                return null;
            }
        }
    
        /**
         - map转换为xml格式
         *
         - @param params
         - @return
         */
        public static String mapToXml(Map<String, String> params) {
            StringBuilder sb = new StringBuilder();
            Set<Map.Entry<String, String>> es = params.entrySet();
            Iterator<Map.Entry<String, String>> it = es.iterator();
            sb.append("<xml>");
            while (it.hasNext()) {
                Map.Entry<String, String> entry = it.next();
                String k = entry.getKey();
                Object v = entry.getValue();
                sb.append("<").append(k).append(">").append(v).append("</").append(k).append(">");
            }
            sb.append("</xml>");
            return sb.toString();
        }
    
        public static void main(String[] args) throws Exception {
            System.out.println(MD5("hello"));
            String s = null;
            System.out.println(Objects.nonNull(s));
    
        }
    
        /**
         - 获得request的ip
         *
         - @param request
         - @return
         */
        public static String getIpAddress(HttpServletRequest request) {
            String ip = request.getHeader("X-Forwarded-For");
            if (Objects.nonNull(ip) && !"unKnown".equalsIgnoreCase(ip)) {
                //多次反向代理后会有多个ip值,第一个ip才是真实ip
                int index = ip.indexOf(",");
                if (index != -1) {
                    return ip.substring(0, index);
                } else {
                    return ip;
                }
            }
            ip = request.getHeader("X-Real-IP");
            if (Objects.nonNull(ip) && !"unKnown".equalsIgnoreCase(ip)) {
                return ip;
            }
            return request.getRemoteAddr();
        }
    
        /**
         - 过滤掉关键参数
         *
         - @param param
         - @return
         */
        public static HashMap<String, String> paramFilter(Map<String, String> param) {
            HashMap<String, String> result = new HashMap<>();
            if (param == null || param.size() <= 0) {
                return result;
            }
            for (String key : param.keySet()) {
                String value = param.get(key);
                if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) {
                    continue;
                }
                result.put(key, value);
            }
            return result;
        }
    
        /**
         - 把Request中的数据解析为xml
         *
         - @param request
         - @return
         */
        public static String requestDataToXml(HttpServletRequest request) {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
                String line;
                StringBuilder sb = new StringBuilder();
                while ((line = bufferedReader.readLine()) != null) {
                    sb.append(line);
                }
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         - xml 转换 bean
         *
         - @param clazz
         - @param xml
         - @param <T>
         - @return
         */
        public static <T> T xmlToBean(Class<T> clazz, String xml) {
            try {
                JAXBContext context = JAXBContext.newInstance(clazz);
                Unmarshaller unmarshaller = context.createUnmarshaller();
                return (T) unmarshaller.unmarshal(new StringReader(xml));
            } catch (JAXBException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    • App端的到json格式的数据调用本地微信App,json格式如下
    {
     "appId":"wxxxx",
     "partnerid":"xxxx",
     "noncestr":"f7382ae04f15cf4e5fd5fbecf342",
     "prepayid":"xxxx",
     "timeStamp":"20180906095441"
     "package":"Sign=WXPay",
     "sign":"AE3E21CCB1DF50B65A0531000E9EF788"
    }

App端用户支付成功后微信会发起异步回调,回调url是我们发起支付时设置的url我们在回调业务中做对应的保存流水等业务并向微信响应收到异步通知不然微信会一直调用异步通知方法会使流水信息保存多次等情况。

  • 支付回调
    /**
     * 微信支付回调
     *
     * @param request
     * @param response
     * @throws IOException
     */
    public void notifyOrder(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String resXml = "<xml><return_code><![CDATA[SUCCESS]]>" +
                "</return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        log.info("wx notify success");
        // 保存流水信息
    }

待完善。。。

java 微信小程序支付

  • 小程序支付开发步骤参见官方文档
  • 开发前你需要有微信公众平台的账号、已注册好的小程序、微信商户平台等信息
  • 大致步骤

    和App支付差不多,后台向微信发起预下单,需要appid、商户号、api_key、等一些必要的参数调用微信的统一下单接口返回对应的信息,然后我们需要自己从微信那边返回的信息中拿到prepay_id这个字段然后封装一些其他的信息如appid、时间戳、随机字符串、paySign等返回到前端,前端拿到这些参数调用微信App的支付,当用户支付成功后微信会发起异步回调,然后后台收到回调向微信响应回调成功。

  • java服务端业务逻辑
    /**
     * 微信小程序支付
     *
     * @param orderId
     * @param request
     */
    public Map xcxPay(Long orderId, HttpServletRequest request) {
        Order order = orderService.findOne(orderId);
        User user = userService.findOne(order.getUserId());
        String nonce_str = RandomUtil.getNum(12);
        String outTradeNo = 1 + RandomUtil.getNum(11);
        String spbill_create_ip = AppUtil.getIpAddress(request);
        if (!AppUtil.isIp(spbill_create_ip)) {
            spbill_create_ip = "127.0.0.1";
        }
        // 小程序支付需要参数
        SortedMap<String, String> reqMap = new TreeMap<>();
        reqMap.put("appid", appProperties.getWx().getApp_id());
        reqMap.put("mch_id", appProperties.getWx().getMch_id());
        reqMap.put("nonce_str", nonce_str);
        reqMap.put("body", "小程序支付");
        reqMap.put("out_trade_no", outTradeNo);
        reqMap.put("total_fee", order.getTotalFee().toString());
        reqMap.put("spbill_create_ip", spbill_create_ip);
        reqMap.put("notify_url", appProperties.getWx().getNotify_url());
        reqMap.put("trade_type", appProperties.getWx().getTrade_type());
        reqMap.put("openid", user.getOpenid());
        String sign = AppUtil.createSign(reqMap, appProperties.getWx().getApi_key());
        reqMap.put("sign", sign);
        String xml = AppUtil.mapToXml(reqMap);
        String result = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order(), xml, null);
        Map<String, String> resData = AppUtil.xmlToMap(result);
        log.info("resData:{}", resData);
        if ("SUCCESS".equals(resData.get("return_code"))) {
            Map<String, String> resultMap = new LinkedHashMap<>();
            //返回的预付单信息
            String prepay_id = resData.get("prepay_id");
            resultMap.put("appId", appProperties.getWx().getApp_id());
            resultMap.put("nonceStr", nonce_str);
            resultMap.put("package", "prepay_id=" + prepay_id);
            resultMap.put("signType", "MD5");
            resultMap.put("timeStamp", RandomUtil.getDateStr(14));
            String paySign = AppUtil.createSign(resultMap, appProperties.getWx().getApi_key());
            resultMap.put("paySign", paySign);
            log.info("return data:{}", resultMap);
            return resultMap;
        } else {
            throw new ValidateException(ExceptionMessage.WEI_XIN_PAY_FAIL);
        }

    }

小程序端用户支付成功后微信会发起异步回调,回调url是我们发起支付时设置的url我们在回调业务中做对应的保存流水等业务并向微信响应收到异步通知不然微信会一直调用异步通知方法会使流水信息保存多次等情况。

  • 支付回调
    /**
     * 微信支付回调
     *
     * @param request
     * @param response
     * @throws IOException
     */
    public void notifyOrder(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String resXml = "<xml><return_code><![CDATA[SUCCESS]]>" +
                "</return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        log.info("wx notify success");
        // 保存流水信息
    }

java 微信扫码支付

这里我们参考模式二,模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

  • 大致流程

    • (1)商户后台系统根据用户选购的商品生成订单。
    • (2)用户确认支付后调用微信支付统一下单API生成预支付交易;
    • (3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
    • (4)商户后台系统根据返回的code_url生成二维码。
    • (5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
    • (6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
    • (7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
    • (8)微信支付系统根据用户授权完成支付交易。
    • (9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
    • (10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
    • (11)未收到支付通知的情况,商户后台系统调用查询订单API
    • (12)商户确认订单已支付后给用户发货。
  • 后台发起支付业务方法
    /**
     * 微信扫码支付传入金额为分
     *
     * @param totalFee
     * @param response
     * @param request
     * @return
     * @throws Exception
     */
    public boolean qrCodePay(String totalFee, HttpServletResponse response,
                             HttpServletRequest request) {
        String nonce_str = RandomUtil.getStr(12);
        String outTradeNo = 1 + RandomUtil.getNum(11);
        String spbill_create_ip = AppUtil.getIpAddress(request);
        if (!AppUtil.isIp(spbill_create_ip)) {
            spbill_create_ip = "127.0.0.1";
        }
        Map<String, String> params = new TreeMap<>();
        params.put("appid", appProperties.getWx().getApp_id());
        params.put("mch_id", appProperties.getWx().getMch_id());
        params.put("nonce_str", nonce_str);
        params.put("body", "微信扫码支付");
        params.put("out_trade_no", outTradeNo);
        params.put("total_fee", totalFee);
        params.put("spbill_create_ip", spbill_create_ip);
        params.put("notify_url", appProperties.getWx().getRefund_url());
        params.put("trade_type", "NATIVE");
        String sign = AppUtil.createSign(params, appProperties.getWx().getApi_key());
        params.put("sign", sign);
        String requestXml = AppUtil.mapToXml(params);
        String responseXml = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order(), requestXml, null);
        Map<String, String> respMap = AppUtil.xmlToMap(responseXml);
        //return_code为微信返回的状态码,SUCCESS表示成功,return_msg 如非空,为错误原因 签名失败 参数格式校验错误
        if ("SUCCESS".equals(respMap.get("return_code")) && "SUCCESS".equals(respMap.get("result_code"))) {
            log.info("wx pre pay success response:{}", respMap);
            // 二维码中需要包含微信返回的信息
            ImageCodeUtil.createQRCode(respMap.get("code_url"), response);
            // 保存订单信息
            return true;
        }
        log.error("wx pre pay error response:{}", respMap);
        return false;
    }
  • 创建二维码方法
    /**
     * 生成二维码并响应到浏览器
     *
     * @param content
     * @param response
     */
    public static void createQRCode(String content, HttpServletResponse response) {
        int width = 300, height = 300;
        String format = "png";
        Map<EncodeHintType, Object> hashMap = new HashMap<>();
        hashMap.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);
        hashMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hashMap.put(EncodeHintType.MARGIN, 1);
        try {
            response.setHeader("Cache-control", "no-cache");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("content-type", "image/png");
            response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
            response.setDateHeader("Expires", 0);
            BitMatrix bitMatrix = new MultiFormatWriter()
                    .encode(content, BarcodeFormat.QR_CODE, width, height, hashMap);
            BufferedImage img = MatrixToImageWriter.toBufferedImage(bitMatrix);
            ImageIO.write(img, format, response.getOutputStream());
        } catch (Exception e) {
            log.warn("create QRCode error message:{}", e.getMessage());
        }
    }

生成二维码用户扫码支付成功后微信会发起异步调用我们发起支付时设置的回调url我们在回调业务中做对应的保存流水等业务并向微信响应收到异步通知不然微信会一直调用异步通知方法会使流水信息保存多次等情况

  • 支付回调
    /**
     * 微信支付回调
     *
     * @param request
     * @param response
     * @throws IOException
     */
    public void notifyOrder(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String resXml = "<xml><return_code><![CDATA[SUCCESS]]>" +
                "</return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        log.info("wx notify success");
        // 保存流水信息
    }

java 微信退款

  • 场景介绍

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

  • 注意:

    • 1、交易时间超过一年的订单无法提交退款;
    • 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。
    • 3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
    • 4、每个支付订单的部分退款次数不能超过50次
    • 5、微信退款需要到微信商户平台下载证书,并配合证书一起使用。
  • java服务端代码
/**
     * 微信退款
     *
     * @param orderId
     * @return
     * @throws Exception
     */
    public boolean wxRefund(Long orderId) throws Exception {
        String nonceStr = RandomUtil.getStr(12);
        String out_refund_no = RandomUtil.getStr(12);
        Order order = orderService.findOne(orderId);

        SortedMap<String, String> params = new TreeMap<>();
        // 公众账号ID
        params.put("appid", appProperties.getWx().getApp_id());
        // 商户号
        params.put("mch_id", appProperties.getWx().getMch_id());
        // 随机字符串
        params.put("nonce_str", nonceStr);
        // 商户订单号
        params.put("out_trade_no", order.getOutTradeNo());
        // 订单金额
        params.put("total_fee", order.getTotalFee().toString());
        // 商户退款单号
        params.put("out_refund_no", out_refund_no);
        // 退款原因
        params.put("refund_fee", order.getTotalFee().toString());
        // 退款结果通知url
        params.put("notify_url", appProperties.getWx().getRefund_notify_url());
        // 签名
        params.put("sign", AppUtil.createSign(params, appProperties.getWx().getApi_key()));
        String data = AppUtil.mapToXml(params);

        CloseableHttpClient httpClient = HttpUtil.sslHttpsClient(appProperties.getWx().getCertificate_path(), appProperties.getWx().getApi_key());
        String xmlResponse = HttpUtil.sendSslXmlPost(appProperties.getWx().getRefund_url(), data, null, httpClient);
        Map<String, String> mapData = AppUtil.xmlToMap(xmlResponse);
        // return_code为微信返回的状态码,SUCCESS表示申请退款成功,return_msg 如非空,为错误原因 签名失败 参数格式校验错误
        if ("SUCCESS".equalsIgnoreCase(mapData.get("return_code"))) {
            log.info("wx refund success response:{}", mapData);
            // 修改订单状态为退款保存退款订单等操作

            return true;
        }
        log.error("wx refund error response:{}", mapData);
        return false;
    }
  • 最终封装好的参数如下
<xml>
   <appid>wx2421b1c4370ec43b</appid>
   <mch_id>10000100</mch_id>
   <nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str>
   <out_refund_no>1415701182</out_refund_no>
   <out_trade_no>1415757673</out_trade_no>
   <refund_fee>1</refund_fee>
   <total_fee>1</total_fee>
   <transaction_id>4006252001201705123297353072</transaction_id>
   <sign>FE56DD4AA85C0EECA82C35595A69E153</sign>
</xml>
  • HttpUtil.java

import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * http请求工具类
 *
 * @author Leone
 **/
@Slf4j
public class HttpUtil {

    private HttpUtil() {
    }

    private final static String UTF8 = StandardCharsets.UTF_8.displayName();

    private static CloseableHttpClient httpClient;

    static {
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000).setExpectContinueEnabled(true).build();
        PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
        pool.setMaxTotal(300);
        pool.setDefaultMaxPerRoute(50);
        HttpRequestRetryHandler retryHandler = (IOException exception, int executionCount, HttpContext context) -> {
            if (executionCount > 1) {
                return false;
            }
            if (exception instanceof NoHttpResponseException) {
                log.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                return true;
            } else if (exception instanceof SocketException) {
                log.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
                return true;
            }
            return false;
        };
        httpClient = HttpClients.custom().setConnectionManager(pool).setDefaultRequestConfig(requestConfig).setRetryHandler(retryHandler).build();
    }

    /**
     * @param certPath
     * @param password
     * @return
     * @throws Exception
     */
    public static CloseableHttpClient sslHttpsClient(String certPath, String password) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (InputStream inputStream = new FileInputStream(new File(certPath))) {
            keyStore.load(inputStream, password.toCharArray());
        }
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
    }

    /**
     * 设置请求头信息
     *
     * @param headers
     * @param request
     * @return
     */
    private static void setHeaders(Map<String, Object> headers, HttpRequest request) {
        if (null != headers && headers.size() > 0) {
            for (Map.Entry<String, Object> entry : headers.entrySet()) {
                request.addHeader(entry.getKey(), entry.getValue().toString());
            }
        }
    }

    /**
     * 发送post请求请求体为xml
     *
     * @param url
     * @param xml
     * @param headers
     * @return
     */
    public static String sendPostXml(String url, String xml, Map<String, Object> headers) {
        String result = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            setHeaders(headers, httpPost);
            StringEntity entity = new StringEntity(xml, StandardCharsets.UTF_8);
            httpPost.addHeader("Content-Type", "text/xml");
            httpPost.setEntity(entity);
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseData = response.getEntity();
            result = EntityUtils.toString(responseData, StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 发送json请求
     *
     * @param url
     * @param json
     * @return
     */
    public static String sendPostJson(String url, String json, Map<String, Object> headers) {
        String result = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            setHeaders(headers, httpPost);
            StringEntity stringEntity = new StringEntity(json, StandardCharsets.UTF_8);
            stringEntity.setContentType("application/json");
            httpPost.setEntity(stringEntity);
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseData = response.getEntity();
            result = EntityUtils.toString(responseData, StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 发送get请求
     *
     * @param url
     * @param params
     * @param header
     * @return
     */
    public static String sendGet(String url, Map<String, Object> params, Map<String, Object> header) {
        String result = null;
        try {
            URIBuilder builder = new URIBuilder(url);
            if (params != null && params.size() > 0) {
                List<NameValuePair> pairs = new ArrayList<>();
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
                }
                builder.setParameters(pairs);
            }
            HttpGet httpGet = new HttpGet(builder.build());
            setHeaders(header, httpGet);
            HttpResponse response = httpClient.execute(httpGet);
            result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 发送get请求
     *
     * @param url
     * @param xml
     * @param headers
     * @return
     */
    public static String sendSslXmlPost(String url, String xml, Map<String, Object> headers, CloseableHttpClient httpClient) {
        String result = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            setHeaders(headers, httpPost);
            StringEntity entity = new StringEntity(xml, StandardCharsets.UTF_8);
            httpPost.addHeader("Content-Type", "text/xml");
            httpPost.setEntity(entity);
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseData = response.getEntity();
            result = EntityUtils.toString(responseData, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

java 支付宝App支付

// 待完善

github:https://github.com/janlle/java-pay

原文地址:https://www.cnblogs.com/ruolin/p/9907609.html

时间: 2024-10-04 01:45:57

微信、支付宝各种支付退款的相关文章

如何可靠的对接微信、支付宝条码支付

场景 餐厅提供了网络点餐服务,用户通过微信能很方便的进行点餐并支付,享受餐厅提供的各种餐饮服务.其中可靠的支付服务是其中的核心环节之一,如果支付出了问题,对餐厅或用户都是一个损失,甚至会引起纠纷.如何避免发生这样的问题或者是把发生这样问题的概率降到最低,那就需要结合业务特点和使用场景来仔细分析隐藏的问题. 下面以微信支付中的2种支付场景来解析一下对接过程中遇到的问题以及如何解决 条码支付 对于支付宝和微信的条码支付,都是没有支付成功回调的.这点必须注意,那么基于这个特点,服务器对接了条码支付,就

spring_boot_pay支付宝,微信,银联支付详细代码案例

spring-boot-pay 支付服务:支付宝,微信,银联详细代码案例(除银联支付可以测试以外,支付宝和微信支付测试均需要企业认证,个人无法完成测试),项目启动前请仔细阅读 注意事项  . 友情提示 由于工作原因,项目正在完善中(仅供参考),随时更新日志,有疑问请留言或者加群 演示界面 欢迎关注 支付宝 电脑支付:https://docs.open.alipay.com/270 扫码支付:https://docs.open.alipay.com/194 手机支付:https://docs.op

app微信支付宝支付后台的插件模式+回调通过spring广播处理后续业务(已亲测可用)

写在前面的话:每当我们做一个项目,基本上都会涉及到支付的业务,最常用的莫过于微信和支付宝的支付了,项目有bug,有问题,都不叫问题,可一旦钱出了问题,那就是大问题了,所以在支付业务上我们必须慎之又慎! 但是我们做开发的,并不是在一个项目中完成支付模块就万事大吉了,在下一个项目中,我们是不是又要将支付模块的代码复制粘贴一遍,然后再重改支付模块?这样的坏处是频繁修改支付模块难免出现一些我们自己都意识不到的问题,一旦暴露在一些不怀好心的又懂技术的人面前,那我们哭都不知道去找谁. 所以,我试着通过利用s

iOS开发笔记14:微博/微信登录与分享、微信/支付宝支付

产品中接入了微博/微信的第三方登录分享功能.微信和支付宝的第三方支付功能,之前在开发过程中涉及到这些部分,于是抽空将接入过程梳理了一遍. 1.微博.微信.支付宝SDK相关接入设置 (1)微博SDK SDK下载 设置URL Scheme,用于从第三方应用或浏览器中启动app 将SDK添加到工程中(支持CocoaPods),app启动时(didFinishLaunchingWithOptions)注册appkey 重写AppDelegate 的handleOpenURL和openURL方法,其它引用

微信支付宝扫码支付相关接口

微信支付宝扫码支付相关接口 ##################支付宝扫码支付################## 当面付--扫码支付:商户专柜或者收银台打印或者副屏展示支付宝二维码,用户使用支付宝钱包扫码工具扫描二维码,并在手机端完成付款. 文档中心:http://doc.open.alipay.com/doc2/detail?spm=0.0.0.0.E3tvGh&treeId=26&articleId=103286&docType=1SDK下载地址:http://doc.ope

微信小程序支付及退款流程详解

微信小程序的支付和退款流程 近期在做微信小程序时,涉及到了小程序的支付和退款流程,所以也大概的将这方面的东西看了一个遍,就在这篇博客里总结一下. 首先说明一下,微信小程序支付的主要逻辑集中在后端,前端只需携带支付所需的数据请求后端接口然后根据返回结果做相应成功失败处理即可.我在后端使用的是php,当然在这篇博客里我不打算贴一堆代码来说明支付的具体实现,而主要会侧重于整个支付的流程和一些细节方面的东西.所以使用其他后端语言的朋友有需要也是可以看一下的.很多时候开发的需求和相应问题的解决真的要跳出语

小黑式烂代码之微信APP支付 + 退款(JAVA实现)

首先,你得先有微信开发平台账号密码还需要开通应用,然后还有微信服务商平台商户版账号(这些我都是给产品经理拿的) 其次我认为你先去看一看微信开发平台的文档!  https://pay.weixin.qq.com/wiki/doc/api/index.html 这里有很多种支付,我就采用APP支付来说了(会了APP支付其实H5支付都差不多的!) 进来后是这样的,随便看看'APP支付那几篇文章'讲的流程!,看完后知道大概了就可以看看'API列表了' 我们后台开发需要关注的就是这三个API了! 1 /*

第三方聚合支付vs微信支付宝支付,有何区别?

自移动支付逐步取缔了现金支付后,现在我们在商户的收银台上会看到各种二维码牌,一般比较经常看到的主要是微信L牌.支付宝二维码L牌,有部分商户会直接打印二维码帖子在桌面上.现在很多人估计会听到有些朋友是做微信和支付宝的移动支付,那么他们所说的做微信和支付宝的移动支付究竟是什么?聚合支付品牌有哪些?微信作为聚合支付品牌国内支付龙头企业,根据微信在2018年发布的<2018微信年度数据报告>显示,2018年,微信每个月有10.82亿用户保持活跃,每天有450亿次信息发送出去,每天有4.1亿音视频呼叫成

支付宝微信网银第三方支付靠谱稳定对接开发

需要了解 JR金融项目 区块链,比特币,p2p项目,B2B网关,现货资金盘,期货招商,股票配资,商品交易所 大盘的 金融支付 第三方支付,网关支付,网银支付,银行卡支付,话费卡支付,银联代扣,支付渠道,支付宝支付,微信支付,扫码支付,快捷支付,支付牌照申请,线上支付通道搭建,支付通道申请,支付接口对接,原生支付宝网关支付!独立后台,D0实时结算,API批量代付接口 JR / BC / QP 菠菜奔驰游戏等稳定安全通道!大量三方支付资质出售!请加扣:143~126~5775 结构和功能在中国,已经