Java 微信小程序笔记 二、 微信支付退款案例

一、前期准备工作:
上篇博客配置的一些参数和文件Jar包 都要用到
微信支付需要小程序和商户绑定
APP绑定微信商户平台获取商户id(mchID)、
证书(商户后台下载)、
支付签名密钥(商户后台设置api密钥)、
退款签名密钥(商户后台设置api密钥ipv3)等
可以去微信官方文档那里查看怎么获取生成后台所需要的参数

官方文档:
支付异步回调通知:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8

退款异步回调通知:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_16&index=11

二、支付流程:
在这里插入图片描述

个人理解:
微信支付需要提供三个接口 :
第一步是你的创建订单的动作
第二步前端确认用户支付的情况下 通过订单ID查询生成调用微信支付的参数 执行扣款动作
第三步微信支付成功后会自动调你提供给微信的回调地址
在回调操作里面修改订单状态 完成你支付成功后的后续动作。

PS:
支付退款的回调地址 必须是外网可以访问的

微信支付金额是分 支付宝是元

三、微信支付详细代码案例:

日期工具类:

import cn.njcool.common.enums.DateEnum;
import cn.njcool.common.enums.NumberEnum;
import org.apache.commons.lang3.StringUtils;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;

/**
 * @Name: DateUtil
 * @Author: zhu
 * @Date: 2020-02-07
 * @Description: 日期时间处理工具类
 */
public class DateUtil {
    
    

    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter
            .ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final DateTimeFormatter NO_SPACE_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private static final DateTimeFormatter DATE_TIME_WITH_HMS_FORMAT = DateTimeFormatter
            .ofPattern("HH:mm:ss");

    public static String formatDateTime(LocalDateTime date, DateEnum dateEnum) {
    
    
        if (date != null) {
    
    
            switch (dateEnum) {
    
    
                case DATE: {
    
    
                    return DATE_FORMAT.format(date);
                }
                case DATE_TIME: {
    
    
                    return DATE_TIME_FORMAT.format(date);
                }
                case NO_SPACE_DATE_TIME: {
    
    
                    return NO_SPACE_DATE_TIME_FORMAT.format(date);
                }
                case TIME_WITH_HMS: {
    
    
                    return DATE_TIME_WITH_HMS_FORMAT.format(date);
                }
            }
        }
        return null;
    }

    public static LocalDateTime formatDateTime(String dateStr) {
    
    
        if (StringUtils.isNotEmpty(dateStr)) {
    
    
            if (dateStr.length() == DateEnum.DATE.getValue()) {
    
    
                dateStr += " 00:00:00";
                return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
            } else if (dateStr.length() == DateEnum.DATE_TIME.getValue()) {
    
    
                return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
            } else if (dateStr.length() == DateEnum.NO_SPACE_DATE_TIME.getValue()) {
    
    
                return LocalDateTime.parse(dateStr, NO_SPACE_DATE_TIME_FORMAT);
            } else if (dateStr.length() == DateEnum.TIME.getValue()) {
    
    
                dateStr = "2020-01-01 " + dateStr + ":00";
                return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
            }
        }
        return null;
    }

    public static List<String> getWeekDateList(int week, String endDate, List<String> excludeDateList) {
    
    
        LocalDateTime now = LocalDateTime.now();
        List<String> resultList = new ArrayList<>();
        TemporalField temporalField = WeekFields.of(DayOfWeek.of(1), 1).dayOfWeek();
        LocalDateTime localDateTime = now.with(temporalField, week);
        if (localDateTime.isBefore(now)) {
    
    
            localDateTime = localDateTime.plusWeeks(1);
        }
        while (true) {
    
    
            String localDateTimeStr = formatDateTime(localDateTime, DateEnum.DATE);
            if (localDateTimeStr.compareTo(endDate) > NumberEnum.ZERO.getValue()) {
    
    
                break;
            }
            if (!excludeDateList.contains(localDateTimeStr)) {
    
    
                resultList.add(localDateTimeStr + "_" + week);
            }
            localDateTime = localDateTime.plusWeeks(1);
        }
        return resultList;
    }
}

枚举工具 两个:
日期:

/**
 * @Name: DateEnum
 * @Author: zhu
 * @Date: 2020-12-07
 * @Description:
 */
public enum DateEnum {
    
    

    // yyyy-MM-dd
    DATE(10),
    // yyyy-MM-dd HH:mm:ss
    DATE_TIME(19),
    // yyyyMMddHHmmss
    NO_SPACE_DATE_TIME(14),
    // HH:mm
    TIME(5),
    // HH:mm:ss
    TIME_WITH_HMS(8);
    private final int value;

    DateEnum(int value) {
    
    
        this.value = value;
    }

    public int getValue() {
    
    
        return value;
    }
}

数字:

/**
 * @Name: NumberEnum
 * @Author: zhu
 * @Date: 2021-01-2021/1/9
 * @Description: 数字枚举
 */
public enum NumberEnum {
    
    

    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5),
    SIX(6),
    SEVEN(7),
    EIGHT(8),
    NINE(9),
    TEN(10),
    ELEVEN(11),
    TWELVE(12),
    THIRTEEN(13),
    FOURTEEN(14),
    FIFTEEN(15),
    SIXTEEN(16),
    SEVENTEEN(17),
    EIGHTEEN(18),
    TWENTY(20),
    THIRTY_ONE(31),
    THIRTY_TWO(32),
    HUNDRED(100),
    FIVE_HUNDRED(500),
    ZERO(0);

    private final int value;

    NumberEnum(int value) {
    
    
        this.value = value;
    }

    public int getValue() {
    
    
        return value;
    }
}

支付代码:

	

   @ApiOperation(value = "支付")
    @ApiOperationSupport(ignoreParameters = {
    
     "limit", "offset", "paramError" })
    @PostMapping("pay")
    public BaseResponse<WxPayOrderResponse> orderPay(@RequestBody OrderPayRequest payRequest,
            HttpServletRequest request) {
    
    
        return wxPayOrderService.orderPay(payRequest, request);
    }



	@Autowired
    private  WxPayService wxPayService;

   /**
     * 根据订单编号调用微信支付
     *
     * @param payRequest 支付参数
     * @param request    http请求体
     * @return WxPayOrderResponse
     */
    @Override
    public BaseResponse<WxPayOrderResponse> orderPay(OrderPayRequest payRequest, HttpServletRequest request) {
    
    
        BaseResponse<WxPayOrderResponse> response = new BaseResponse<>();
        // 校验入参
        if (Objects.isNull(request) || payRequest.isParamError()) {
    
    
            log.info("[WxPayOrderServiceImpl-orderPay] 入参校验错误");
            response.fail(ResponseEnum.PARAM_VALIDATE_ERROR);
            return response;
        }
        //查询订单信息 
        List<OrderDmo> orderDtoList = listByIds(payRequest.getOrderIdList());
        //支付名称
        String body = "xx服务套餐";
        //订单总金额
        BigDecimal priceCount = orderDtoList.stream().map(OrderDmo::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
        log.info("WxPay总金额为:{}", priceCount);
        //生成微信请求订单号
        String orderNo = UUIDUtil.generateUUID();
        //元转成分
        Integer price = BaseWxPayRequest.yuanToFen(String.valueOf(priceCount));
        //用户真实ip
        String ipAddress = getIpAddress(request);
        //回调附加数据 可以自定义回调数据 我这里是将订单表主键ID带过去了
        String attach = orderDtoList.stream()
                .map(OrderDmo::getId)
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        log.info("回调附加数据={}", attach);
        log.info("根据订单编号调用微信支付  OpenId = {}", payRequest.getOpenId());
        WxPayMpOrderResult wxPayMpOrderResult = wxPay(body, orderNo, price, payRequest.getOpenId(), ipAddress, attach);
        if (ObjectUtils.isEmpty(wxPayMpOrderResult)) {
    
    
            log.info("[WxPayOrderServiceImpl-orderPay]微信支付失败");
            response.fail(ResponseEnum.ORDER_PAY_FAIL);
            return response;
        }

        WxPayOrderResponse result = BeanUtil.toBean(wxPayMpOrderResult, WxPayOrderResponse.class);
        log.info("微信支付成功返回信息:{}", result);
        response.setData(new ResultData<>(result));
        return response;
    }



    /**
     * @param body    支付名称
     * @param orderNo 订单编号
     * @param price   订单价格
     * @param openid  用户openid
     * @param ip      用户ip
     * @param attach  附加数据
     * @return WxPayMpOrderResult
     */
    private WxPayMpOrderResult wxPay(String body, String orderNo, Integer price, String openid, String ip,
            String attach) {
    
    

        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
        orderRequest.setBody(body);
        orderRequest.setOutTradeNo(orderNo);
        //分
        orderRequest.setTotalFee(price);
        orderRequest.setOpenid(openid);
        orderRequest.setSpbillCreateIp(ip);
        LocalDateTime now = LocalDateTime.now();
        orderRequest.setTimeStart(DateUtil.formatDateTime(now, DateEnum.NO_SPACE_DATE_TIME));
        LocalDateTime plus = now.plus(30, ChronoUnit.MINUTES);
        orderRequest.setTimeExpire(DateUtil.formatDateTime(plus, DateEnum.NO_SPACE_DATE_TIME));
        orderRequest.setAttach(attach);
        log.info("请求微信支付参数为:{}", orderRequest);
        try {
    
    
            return wxPayService.createOrder(orderRequest);
        } catch (WxPayException e) {
    
    
            log.error("[WxPayBusinessService-wxPay]支付创建订单失败!订单号:{}, 原因:{}", orderNo, e.getMessage());
            return null;
        }
    }


工具类:
 /**
     * 获取微信下单时 用户真实Ip地址
     *
     * @param request HttpServletRequest
     * @return iP
     */
    public String getIpAddress(HttpServletRequest request) {
    
    
        String ip = null;
        //X-Forwarded-For:Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
    
    
            //Proxy-Client-IP:apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
    
    
            //WL-Proxy-Client-IP:weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
    
    
            //HTTP_CLIENT_IP:有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
    
    
            //X-Real-IP:nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }
        //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
        if (StringUtils.isNotEmpty(ipAddresses)) {
    
    
            ip = ipAddresses.split(",")[0];
        }
        //还是不能获取到,最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    
    
            ip = request.getRemoteAddr();
        }
        return ip;
    }


微信回调接口:

@Slf4j
@RestController
@RequestMapping("wxPay")
public class WxPayNotifyController {
    
    
    private final WxPayNotifyService wxPayNotifyService;

    public WxPayNotifyController(WxPayNotifyService wxPayNotifyService) {
    
    
        this.wxPayNotifyService = wxPayNotifyService;
    }

    @RequestMapping("payNotify")
    public String payNotify(HttpServletRequest request) {
    
    
        log.info("[<<<<<  微信支付回调开始 >>>>>]");
        String response;
        try {
    
    
            response = wxPayNotifyService.payNotify(request);
        } catch (Exception e) {
    
    
            log.info("微信支付回调Error= {}", e.getMessage());
            response = "WxCallback Exception";
        }
        log.info("[WxPayNotifyController-payNotify]支付回调Result= {}", response);
        log.info("[<<<<<  微信支付回调结束 >>>>>]");
        return response;
    }
}

业务层:

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author zhu
 * @date 2021/9/15 16:13
 */
@Slf4j
@Service
public class WxPayNotifyServiceImpl implements WxPayNotifyService {
    
    

    private final WxPayService wxPayService;
  
    /**
     * 微信支付回调接口
     *
     * @param request HttpServletRequest
     * @return 日志参数信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String payNotify(HttpServletRequest request) throws Exception {
    
    
        WxPayOrderNotifyResult result;
        try {
    
    
            String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
            result = wxPayService.parseOrderNotifyResult(xmlResult);
            log.info("微信支付回调请求参数= {}", result);
        } catch (Exception e) {
    
    
            log.error("[WxPayNotifyService-payNotify]微信回调结果异常: {}", e.getMessage());
            return WxPayNotifyResponse.fail("回调操作失败" + e);
        }

        return updateOrderState(result);
    }

    String updateOrderState(WxPayOrderNotifyResult result) throws Exception {
    
    
        //获取微信回调订单编号修改状态
        String attach = result.getAttach();
        //获取回调数据
        List<String> orderIdList = Arrays.stream(attach.split(",")).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(orderIdList)) {
    
    
            return WxPayNotifyResponse.fail("订单ID不存在");
        }
        List<OrderDmo> orderDtoList = wxPayOrderService.listByIds(orderIdList);
        log.info("修改订单状态orderDtoList={}", orderDtoList);
        boolean orderState = wxPayOrderService.updateBatchById(orderDtoList);
        log.info("修改订单状态={}", orderState);
        //修改订单支付状态
        if (!orderState) {
    
    
            log.error("[WxPayNotifyService-payNotify]微信回调 修改订单状态失败");
            throw new Exception("微信回调 修改订单状态失败");
        }
      	//..........个人支付回调业务处理
        }
        return WxPayNotifyResponse.success("处理成功");
    }

}

四、微信退款案例代码:

    @ApiOperation(value = "退款")
    @ApiOperationSupport(ignoreParameters = {
    
    "limit", "offset", "paramError"})
    @PostMapping("refund")
    public BaseResponse<Object> refund(@RequestBody OrderRefundRequest refundRequest, HttpServletRequest request) {
    
    
        BaseResponse<Object> response;
        try {
    
    
            response = wxPayBusinessService.refund(refundRequest, request);
        } catch (Exception e) {
    
    
            response = new BaseResponse<>();
            response.fail(ResponseEnum.ORDER_REFUND_FAIL);
        }
        return response;
    }

业务层:

    @Transactional(rollbackFor = Exception.class)
    public BaseResponse<Object> refund(OrderRefundRequest refundRequest, HttpServletRequest request) throws Exception {
    
    
        BaseResponse<Object> response = new BaseResponse<>();
        // 校验入参
        if (refundRequest == null || refundRequest.isParamError()) {
    
    
            log.info("[WxPayBusinessService-refund] 入参校验错误");
            response.fail(ResponseEnum.PARAM_VALIDATE_ERROR);
            return response;
        }
        WxPayRefundResult wxPayRefundResult = getWxPayRefundResult(refundRequest, request);
        if (ObjectUtils.isEmpty(wxPayRefundResult)) {
    
    
            throw new Exception("订单退款失败");
        }
        return response;
    }

    WxPayRefundResult getWxPayRefundResult(OrderRefundRequest refundRequest, HttpServletRequest request) throws Exception {
    
    
        //修改订单状态为退款中
        if (transactionDao.updateOrderStatusById(refundRequest.getId(), NumberEnum.FOUR.getValue()) != NumberEnum.ONE.getValue()) {
    
    
            log.info("[WxPayBusinessService-refund]修改订单状态失败");
            throw new Exception("订单退款失败");
        }
        String orderNo = refundRequest.getOrderNo();
        Integer price = refundRequest.getPrice().scaleByPowerOfTen(2).intValue();
        //退款
        WxPayRefundResult wxPayRefundResult;
        try {
    
    
            wxPayRefundResult = wxRefund(orderNo, price);
        } catch (WxPayException e) {
    
    
            log.error("[WxPayBusinessService-refund]退款失败!订单号: {}, 原因: {}", orderNo, e.getMessage());
            return null;
        }
        return wxPayRefundResult;
    }

    public WxPayRefundResult wxRefund(String orderNo, Integer price) throws WxPayException {
    
    
        WxPayRefundRequest refundRequest = new WxPayRefundRequest();
        //商户订单号
        refundRequest.setOutTradeNo(orderNo);
        //商户退款订单号
        refundRequest.setOutRefundNo(UUIDUtil.generateUUID());
        //订单金额
        refundRequest.setTotalFee(price);
        //退款金额
        refundRequest.setRefundFee(price);
        //商户号
        refundRequest.setOpUserId(wxConfig.wxMchId);
        //微信支付退款结果通知的回调地址
        refundRequest.setNotifyUrl(wxConfig.refundNotifyUrl);
        return wxPayService.refund(refundRequest);
    }

微信退款回调接口:

@Slf4j
@RestController
@RequestMapping("wxRefund")
public class WxRefundNotifyController {
    
    

    @RequestMapping("refundNotify")
    public String refundNotify(HttpServletRequest request) {
    
    
        log.info("[======微信退款回调开始======]");
        String response;
        try {
    
    
            response = wxRefundNotifyService.refundNotify(request);
        } catch (Exception e) {
    
    
            response = "callback exception";
        }
        log.info("[WxPayNotifyController-refundNotify]支付回调: {}", response);
        log.info("[======微信退款回调结束======]");
        return response;
    }
}

业务层:

@Transactional(rollbackFor = Exception.class)
    public String refundNotify(HttpServletRequest request) throws Exception {
    
    
        WxPayRefundNotifyResult result;
        try {
    
    
            String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
            result = wxPayService.parseRefundNotifyResult(xmlResult);
        } catch (Exception e) {
    
    
            log.error("[WxRefundNotifyService-refundNotify]微信退款回调结果异常", e);
            return WxPayNotifyResponse.fail("回调操作失败");
        }
        if (ObjectUtils.isNotEmpty(result)) {
    
    
            return callback(result);
        } else {
    
    
            log.info("[WxRefundNotifyService-refundNotify result is Empty]");
            return WxPayNotifyResponse.fail("回调操作失败");
        }
    }

    private String callback(WxPayRefundNotifyResult result) throws Exception {
    
    
        //获取加密信息字段解密后的内容
        WxPayRefundNotifyResult.ReqInfo reqInfo = result.getReqInfo();
        //商家订单号
        String outTradeNo = reqInfo.getOutTradeNo();
        //商家退款订单号
        String outRefundNo = reqInfo.getOutRefundNo();
       		 //个人业务代码
            //修改订单状态
            //钱包扣除金额
            return WxPayNotifyResponse.success("处理成功!");
        } else {
    
    
            return WxPayNotifyResponse.fail("未查询到订单");
        }
    }

总结: 案例Demo 省去了很多代码 大家可以去看Wx开发工具包提供的接口 里面都有很详细的介绍
结合你当前项目的需求进行开发即可

附上学习途径:
在这里插入图片描述
找到对应的Wx开发工具包提供的Api接口 看对接参数是否必传
线上排查时 只要你的日志打印的好 多测几遍就可以成功了 也可以看看官方的文档报错信息
微信返回过来的参数都是XMl格式的 报错直接百度搜错误Code即可 解决办法一大堆

猜你喜欢

转载自blog.csdn.net/weixin_48134878/article/details/120864566