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

时间:2018-11-05 11:15:49   收藏:0   阅读:254

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

前言

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

java 微信App支付

微信App支付文档

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 微信小程序支付

    /**
     * 微信小程序支付
     *
     * @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小时,过期后扫码不能再发起支付。

    /**
     * 微信扫码支付传入金额为分
     *
     * @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 微信退款

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

/**
     * 微信退款
     *
     * @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>

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

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!