微信退款

2020-01-09  本文已影响0人  小别墅是毛坯

微信退款

 @ApiOperation(value = "确认并退款", notes = "确认并退款")
    @PutMapping(value = "/updateAfter/{refundId}")
    @ApiImplicitParams({@ApiImplicitParam(name = "ACCESS_TOKEN", value = "接口调用凭证", defaultValue = "06855244f2f221da4cd0a395c0d3c68c", dataType = "string", required = true, paramType = "query")})
    public void updateAfter(@ApiParam(value = "售后单Id", required = true) @PathVariable("refundId") Integer refundId,
                            @ApiParam(value = "订单类型(0 购物车付款 1 直接支付)")@RequestParam(value = "orderType")Integer orderType)  {
         AdminUser adminUser = SessionUtil.getUser(request, AdminUser.class);
        goodsOrderRefundService.updateAfter(refundId,adminUser.getId(), HttpUtil.getIpAddr(request),orderType);
    }

    @ApiOperation(value = "微信退款回调通知", notes = "订单退款回调通知", hidden = true)
    @PostMapping(value = "/notify")
    @ResponseAdvice(value = ResponseAdviceEnum.NOT_REQUIRED)
    public String payNotify() {
        boolean checkFlag = true;
        try {
            String notifyInfo = RequestUtil.parseWechatNotifyParams(request);
            Map<String, String> map = WechatPayUtil.checkCallBackWechatRefund(notifyInfo);
            checkFlag = goodsOrderRefundService.updateNotice(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return WechatPayUtil.generateWxCallBackResponse(checkFlag);
    }
 /**
     * 确认收货并退款
     *
     * @param refundId
     * @param orderType
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateAfter(Integer refundId, Integer adminId, String ip, Integer orderType) {
        GoodsOrderRefund goodsOrderRefund = new GoodsOrderRefund();
        if (Objects.equals(orderType, shoppingPay.getCode())){
             goodsOrderRefund = shoppingPay(refundId);

        }else {

            OrderRefundBo orderRefundBo = goodsOrderRefundMapper.getOrderRefundBo(refundId);
            AssertUtil.notNull(orderRefundBo.getPayType(), ExceptionEnum.OrderRefundInvalid);

            goodsOrderRefund.setId(refundId);
            goodsOrderRefund.setDeleteFlag(DeleteFlagEnum.NotDeleted.getCode());
            goodsOrderRefund = goodsOrderRefundMapper.selectOne(goodsOrderRefund);
            AssertUtil.notNull(goodsOrderRefund, ExceptionEnum.invalidOrderRefund);
            //生成退款单号
            String refundNo = "R" + DateUtils.getTimeString(DateUtils.DATE_YYYYMMDDHHMMSS, new Date()) + RandomStringUtils.randomNumeric(6);

            //更新退款单状态为-退款中
            GoodsOrderRefund temp = new GoodsOrderRefund();
            temp.setRefundStatus(OrderRefundStatusEnum.refunding.getCode());
            temp.setRefundNo(refundNo);
            Example examples = new Example(GoodsOrderRefund.class);
            examples.createCriteria()
                    .andEqualTo("id", refundId)
                    .andEqualTo("deleteFlag", DeleteFlagEnum.NotDeleted.getCode());
            goodsOrderRefundMapper.updateByExampleSelective(temp, examples);
            //更新订单详情为-退款中
            GoodsOrderDetail goodsOrderDetail = new GoodsOrderDetail();
            goodsOrderDetail.setRefundStatus(OrderRefundStatusEnum.refunding.getCode());
            Example goodsOrderDetailExample = new Example(GoodsOrderDetail.class);
            goodsOrderDetailExample.createCriteria().andEqualTo("orderId", goodsOrderRefund.getOrderId())
                    .andEqualTo("goodsProductId", goodsOrderRefund.getGoodsProductId())
                    .andEqualTo("refundStatus", OrderRefundStatusEnum.checking.getCode());
            goodsOrderDetailMapper.updateByExampleSelective(goodsOrderDetail, goodsOrderDetailExample);

            WechatPayDto dto = goodsOrderRefundMapper.selectRefund(refundId);
            //订单id
            String outTradeNo = dto.getOutTradeNo();
            //订单总金额
            String totalFee = dto.getTotalFee();
            //退款金额
            String refundFee = dto.getRefundFee();
            //退款单号
            String outRefundNo = dto.getOutRefundNo();
            //微信反的订单号
            String transactionId = dto.getTransactionId();
            /******************微信退款 ***************/
            if (Objects.equals(orderRefundBo.getPayType(), PayTypeEnum.waitDelivery.getCode())) {
                Map<String, String> refund = WechatPayUtil.refund(transactionId, outTradeNo, outRefundNo,
                        new BigDecimal(totalFee), new BigDecimal(refundFee), WechatPayUtil.REFUND_NOTIFY_URL);
                //用来判断数据是否为真
                System.out.println(refund);
                AssertUtil.isTrue(Objects.equals(refund.get("result_code"), "SUCCESS") &&
                        Objects.equals(refund.get("return_code"), "SUCCESS"), ExceptionEnum.wechatRefundError);
            }

/************************wechat.properties***************/

app.id=*****
app.secret=*****
mch.id=1488883392
pay.secret=*********
## 自己电脑ip
refund.notify.url=http://*****/refund/notify
##微信退款证书
cert.url=C:/Users/Administrator/Desktop/apiclient_cert.p12
package com.qcdl.web.wechat;

import com.google.common.collect.Maps;
import com.qcdl.utils.CodecUtil;
import com.qcdl.utils.HttpUtil;
import com.qcdl.utils.PropertiesUtil;
import com.qcdl.web.config.advice.ExceptionEnum;
import com.qcdl.web.config.advice.WebException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;

import javax.annotation.Nonnull;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qcdl
 * @date 2019/6/11
 */
@Slf4j
public class WechatPayUtil {

    private static final PropertiesUtil WECHAT_PRO = new PropertiesUtil("/configs/wechat.properties");
    public static final String REFUND_NOTIFY_URL;
    private static final String APP_ID;
    private static final String MCH_ID;
    private static final String PAY_SECRET;
    private static final String CERT_URL;

    static {
        APP_ID = WECHAT_PRO.getProperty("app.id");
        MCH_ID = WECHAT_PRO.getProperty("mch.id");
        REFUND_NOTIFY_URL = WECHAT_PRO.getProperty("refund.notify.url");
        PAY_SECRET = WECHAT_PRO.getProperty("pay.secret");
        CERT_URL = WECHAT_PRO.getProperty("cert.url");
    }

    private static final String TRADE_STATUS_SUCCESS = "SUCCESS";

    private static String generateWechatPaySign(Map<String, String> params) {
        String plainValue = MapUtils.mapParamOrderByAscJoinUrlDelimiter(params) + "&key=" + PAY_SECRET;
        return CodecUtil.getMD5Cryptography(plainValue).toUpperCase();
    }

    /**
     * 校验微信支付回调信息
     *
     * @param notifyInfo 微信支付回调信息
     * @return true/false
     */
    public static boolean checkCallBackWechatPaySign(String notifyInfo) throws DocumentException {
        Map<String, String> params = XmlUtils.xmlStr2Map(notifyInfo);
        String oldSignStr = params.get("sign");
        params.remove("sign");

        String newSignStr = generateWechatPaySign(params);
        if (!oldSignStr.equals(newSignStr)) {
            return false;
        }

        return StringUtils.equalsIgnoreCase(params.get("return_code"), TRADE_STATUS_SUCCESS)
                && StringUtils.equalsIgnoreCase(params.get("result_code"), TRADE_STATUS_SUCCESS);
    }

    /**
     * 生成微信支付回调响应信息
     *
     * @param checkFlag 签名校验结果
     * @return 微信支付回调响应信息
     */
    public static String generateWxCallBackResponse(boolean checkFlag) {
        Map<String, String> params = Maps.newHashMap();
        String returnCode = "FAIL";
        String returnMsg = "校验签名失败";
        if (checkFlag) {
            returnCode = "SUCCESS";
            returnMsg = "OK";
        }
        params.put("return_code", returnCode);
        params.put("return_msg", returnMsg);

        return XmlUtils.map2XmlStr(params);
    }

    /**
     * 微信订单申请退款
     * 1、交易时间超过一年的订单无法提交退款
     * 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
     * 申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
     * 3、每个支付订单的部分退款次数不能超过50次
     * <p>
     * 退款金额<=订单总金额
     *
     * @param transactionId 微信订单ID
     * @param orderNo       系统订单号
     * @param refundNo      系统退款单号
     * @param orderAmount   订单总金额
     * @param refundAmount  退款金额
     * @param notifyUrl     退款回调通知地址
     * @return 微信退款
     */
    public static Map<String, String> refund(String transactionId, String orderNo, String refundNo, BigDecimal orderAmount, BigDecimal refundAmount, String notifyUrl) {
        try {
            String refundOrder = generateRefund(transactionId, orderNo, refundNo, orderAmount, refundAmount, notifyUrl);
            String refundResult = RequestUtil.httpsRequestAndCert(WechatConstant.REFUND_ORDER, refundOrder, CERT_URL, MCH_ID);
            return XmlUtils.xmlStr2Map(refundResult);
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new WebException(ExceptionEnum.Exception);
    }

    private static String generateRefund(String transactionId, @Nonnull String orderNo, String refundNo,
                                         BigDecimal orderAmount, @Nonnull BigDecimal refundAmount, @Nonnull String notifyUrl) {
        Map<String, String> params = Maps.newHashMap();
        params.put("appid", APP_ID);
        params.put("mch_id", MCH_ID);
        params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
        params.put("transaction_id", transactionId);
        params.put("out_trade_no", orderNo);
        params.put("out_refund_no", refundNo);
        orderAmount = orderAmount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_DOWN);
        refundAmount = refundAmount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_DOWN);
        params.put("total_fee", orderAmount.toString());
        params.put("refund_fee", refundAmount.toString());
        params.put("notify_url", notifyUrl);
        params.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS");
        params.put("sign", generateWechatPaySign(params));

        return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
    }

    public static Map<String, String> checkCallBackWechatRefund(String notifyInfo) throws Exception {
        Map<String, String> params = XmlUtils.xmlStr2Map(notifyInfo);
        String reqInfo = params.get("req_info");

        byte[] encryptMsg = Base64.getDecoder().decode(reqInfo.getBytes(StandardCharsets.UTF_8));
        String encryptKey = CodecUtil.getMD5Cryptography(PAY_SECRET).toLowerCase();
        String plainNotifyInfo = AESUtil.decryptData(encryptMsg, encryptKey);

        return XmlUtils.xmlStr2Map(plainNotifyInfo);
    }

    public static Map<String, String> queryOrderRefund(@NonNull String orderNo, @NonNull Integer page) throws DocumentException {
        String refundParam = generateOrderRefundParam(orderNo, page);
        String result = HttpUtil.getResult(WechatConstant.QUERY_ORDER_REFUND, refundParam);

        return XmlUtils.xmlStr2Map(result);
    }

    public static Map<String, String> queryOrderPayStart(@NonNull String orderNo) throws DocumentException {
        String queryOrderPayParam = generateQueryOrderParam(orderNo);
        String result = HttpUtil.getResult(WechatConstant.QUERY_ORDER, queryOrderPayParam);

        return XmlUtils.xmlStr2Map(result);
    }

    private static String generateQueryOrderParam(String orderNo) {
        HashMap<String, String> params = Maps.newHashMap();
        params.put("appid", APP_ID);
        params.put("mch_id", MCH_ID);
        params.put("out_trade_no", orderNo);
        params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
        params.put("sign", generateWechatPaySign(params));

        return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
    }

    private static String generateOrderRefundParam(String orderNo, Integer page) {
        Map<String, String> params = Maps.newHashMap();
        params.put("appid", APP_ID);
        params.put("mch_id", MCH_ID);
        params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
        params.put("out_trade_no", orderNo);
        params.put("offset", page * 10 + "");
        params.put("sign", generateWechatPaySign(params));

        return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
    }
}

/*****************RequestUtil************/

package com.qcdl.web.wechat;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayOfflineMaterialImageUploadRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.*;

/**
 * @author qcdl
 * @date 2019/6/10
 */
public final class RequestUtil {
    /**
     * 封装前台请求参数并转换为map保存
     *
     * @param request request
     * @return map
     */
    public static Map<String, String> parseRequestParam(HttpServletRequest request) {
        Map<String, String> result = new HashMap<>(16);
        Enumeration<String> enumerations = request.getParameterNames();
        while (enumerations.hasMoreElements()) {
            String key = enumerations.nextElement();
            result.put(key, request.getParameter(key));
        }
        return result;
    }

    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        try {
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes(StandardCharsets.UTF_8));
                outputStream.close();
            }
            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str;
            StringBuilder buffer = new StringBuilder();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            conn.disconnect();
            return buffer.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析微信回调通知参数(简单封装,仅限微信回调用)
     *
     * @param request request
     * @return 微信回调通知参数
     * @throws IOException e
     */
    public static String parseWechatNotifyParams(HttpServletRequest request) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = in.readLine()) != null) {
            sb.append(line);
        }
        in.close();
        return sb.toString();
    }

    public static String getFullRequestUrl(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        if (StringUtils.isNotBlank(request.getQueryString())) {
            url += "?" + request.getQueryString();
        }
        return url;
    }

    public static String httpsRequestAndCert(String requestUrl, String param, String certUrl, String certPass) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream inputStream = new FileInputStream(new File(certUrl))) {
            keyStore.load(inputStream, certPass.toCharArray());
        }
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, certPass.toCharArray())
                .build();
        SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslFactory)
                .build();
        StringBuilder stringBuffer = new StringBuilder();
        try {
            HttpPost httpPost = new HttpPost(requestUrl);
            InputStream is = new ByteArrayInputStream(param.getBytes(StandardCharsets.UTF_8));
            InputStreamEntity inputStreamEntity = new InputStreamEntity(is, is.available());
            httpPost.setEntity(inputStreamEntity);
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                        entity.getContent(), StandardCharsets.UTF_8));
                String inputLine;
                while ((inputLine = reader.readLine()) != null) {
                    stringBuffer.append(inputLine);
                }
            }
        } finally {
            httpClient.close();
        }
        return stringBuffer.toString();
    }
}
@Data
@ApiModel
public class WechatPayDto {

    @ApiModelProperty("微信订单号")
    private String transactionId;

    @ApiModelProperty("订单总金额")
    private String totalFee;

    @ApiModelProperty("系统支付单号")
    private String outTradeNo;

    @ApiModelProperty("系统退款单号")
    private String outRefundNo;

    @ApiModelProperty("微信退款单号")
    private String refundId;

    //退款资金来源 (REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款/基本账户、REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款)
    @ApiModelProperty("退款资金来源")
    private String refundAccount;

    //退款总金额,单位为分,可以做部分退款
    @ApiModelProperty("退款总金额")
    private String refundFee;

    @ApiModelProperty("退款入账账户")
    private String refundRecvAccount;

    //退款状态(SUCCESS—退款成功、REFUNDCLOSE—退款关闭、PROCESSING—退款处理中、CHANGE—退款异常)
    @ApiModelProperty("退款状态")
    private String refundStatus;

    @ApiModelProperty("退款成功时间")
    private String successTime;

}

上一篇下一篇

猜你喜欢

热点阅读