java springboot对接微信小程序和微信支付v3接口

1、controller代码

package com.saburo.server.controller.weixin;

import cn.dev33.satoken.annotation.SaIgnore;
import com.gcode.common.core.R;
import com.saburo.server.common.dto.WeiXinUserInfoDto;
import com.saburo.server.common.dto.money.PayDto;
import com.saburo.server.common.vo.money.PayRollBackResultVo;
import com.saburo.server.common.vo.user.UserInfoVo;
import com.saburo.server.service.weixin.MiniProgramService;
import com.saburo.server.common.vo.weixin.WeiXinVo;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import kotlin.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;

/**
 * 微信小程序相关接口
 *
 * @author liubh
 */
@RestController
@RequestMapping("/wx")
@RequiredArgsConstructor
public class MiniProgramController {
    private final MiniProgramService miniProgramService;

    /**
     * 获取微信小程序访问accessToken
     *
     * @return accessToken
     */
    @SaIgnore
    @GetMapping("/accessToken")
    public R<String> getAccessToken() {
        return R.ok(miniProgramService.getAccessToken());
    }

    /**
     * 获取微信小程序openId
     *
     * @param weiXinUserInfoDto 微信小程序用户信息
     * @return 用户信息
     */
    @SaIgnore
    @PostMapping("/loginMessageByCode")
    public R<WeiXinVo> getWeiXinLoginOpenId(@RequestBody WeiXinUserInfoDto weiXinUserInfoDto) {
        return R.ok(miniProgramService.getWeiXinLoginOpenId(weiXinUserInfoDto));
    }

    /**
     * 微信登录
     *
     * @param weiXinUserInfo 用户信息
     * @return 用户信息
     */
    @SaIgnore
    @PostMapping("/mobileLogin")
    public R<UserInfoVo> login(@RequestBody WeiXinUserInfoDto weiXinUserInfo) {
        return R.ok(miniProgramService.wxLogin(weiXinUserInfo));
    }

    /**
     * 获取微信小程序手机号
     *
     * @param code code码
     * @return 登录信息
     */
    @SaIgnore
    @GetMapping("/phone")
    public R<String> getPhoneNumber(@RequestParam("code") String code) {
        return R.ok(miniProgramService.getPhoneNumber(code));
    }

    /**
     * 微信支付
     *
     * @param payDto 支付信息
     * @return 支付信息
     */
    @SaIgnore
    @PostMapping("/pay")
    public R<PrepayWithRequestPaymentResponse> weiXinPay(@RequestBody PayDto payDto) {
        return R.ok(miniProgramService.weiXinPay(payDto));
    }

    /**
     * 微信支付回调 JSAPI 下单回调
     *
     * @return 回调结果
     */
    @SaIgnore
    @PostMapping("/paymentCallback")
    public R<Transaction> wxPaymentCallback(HttpServletRequest request) {
        return R.ok(miniProgramService.wxPaymentCallback(request));
    }


    /**
     * 退款
     *
     * @param orderId 订单id
     * @param amount  金额
     * @return 退款信息
     */
    @GetMapping("/refundPayment")
    public R<Refund> refundPayment(@RequestParam String orderId, @RequestParam BigDecimal amount) {
        Refund refund = miniProgramService.refundIndentPayment(orderId, amount);
        return R.ok(refund);
    }

    /**
     * 退款回调
     * @param request 请求
     * @return 退款信息
     */
    @PostMapping("/refund/black")
    public R<Boolean> endRefundIndent(HttpServletRequest request) {
        return R.ok(miniProgramService.getRefundNotification(request));
    }


}

2、接口

package com.saburo.server.service.weixin;

import com.saburo.server.common.dto.WeiXinUserInfoDto;
import com.saburo.server.common.dto.money.PayDto;
import com.saburo.server.common.vo.money.PayRollBackResultVo;
import com.saburo.server.common.vo.user.UserInfoVo;
import com.saburo.server.common.vo.weixin.WeiXinVo;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;

/**
 * @Author: liubh
 * @Date: 2024/10/28 13:37
 */
public interface MiniProgramService {

    /**
     * 获取微信小程序访问accessToken
     * @return accessToken
     */
    String getAccessToken();

    /**
     * 微信小程序登录
     * @return 微信登录信息
     */
    WeiXinVo getWeiXinLoginOpenId(WeiXinUserInfoDto weiXinUserInfoDto);
    /**
     * 获取微信小程序手机号
     *
     * @param code code码
     * @return 登录信息
     */
    String getPhoneNumber(String code);

    /**
     * 微信小程序登录
     * @return 微信登录信息
     */
    UserInfoVo wxLogin(WeiXinUserInfoDto weiXinUserInfo);

    /**
     * 微信支付
     * @param payDto 支付信息
     * @return 支付信息
     */
    PrepayWithRequestPaymentResponse weiXinPay(PayDto payDto);


    /**
     * 微信支付回调 JSAPI 下单回调
     * @return 回调结果
     */
    Transaction wxPaymentCallback(HttpServletRequest request);

    /**
     * 退款
     * @param orderId 订单id
     * @param amount 金额
     * @return 退款信息
     */
    Refund refundIndentPayment(String orderId, BigDecimal amount);

    /**
     * 退款回调
     * @param request 请求
     * @return 退款是否成功
     */
    boolean getRefundNotification(HttpServletRequest request);

}

  3、实现类

package com.saburo.server.service.weixin.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gcode.common.exception.ApiException;
import com.gcode.common.utils.DateUtil;
import com.gcode.webmvc.entity.SysUserEntity;
import com.gcode.webmvc.mapper.SysUserMapper;
import com.gcode.webmvc.utils.IpUtils;
import com.saburo.server.common.dto.WeiXinUserInfoDto;
import com.saburo.server.common.dto.money.PayDto;
import com.saburo.server.common.enums.OrderMachineStateEnum;
import com.saburo.server.common.enums.OrderPowerBankStateEnum;
import com.saburo.server.common.properties.WeiXinConfigureProperties;
import com.saburo.server.common.utils.AesCbcUtil;
import com.saburo.server.common.utils.NumberComputeUtil;
import com.saburo.server.common.vo.user.UserInfoVo;
import com.saburo.server.entity.money.MoneyPointEntity;
import com.saburo.server.entity.money.MoneySetPointEntity;
import com.saburo.server.entity.money.MoneyWaterEntity;
import com.saburo.server.entity.product.PowerBankOrderEntity;
import com.saburo.server.entity.product.ProductInfoEntity;
import com.saburo.server.entity.product.ProductOrderDetailEntity;
import com.saburo.server.entity.product.ProductOrderEntity;
import com.saburo.server.entity.user.UserInfoEntity;
import com.saburo.server.mapper.money.MoneyPointMapper;
import com.saburo.server.mapper.money.MoneySetPointMapper;
import com.saburo.server.mapper.money.MoneyWaterMapper;
import com.saburo.server.mapper.product.PowerBankOrderMapper;
import com.saburo.server.mapper.product.ProductInfoMapper;
import com.saburo.server.mapper.product.ProductOrderDetailMapper;
import com.saburo.server.mapper.product.ProductOrderMapper;
import com.saburo.server.mapper.user.UserInfoMapper;
import com.saburo.server.service.money.MoneyWaterService;
import com.saburo.server.service.weixin.MiniProgramService;
import com.saburo.server.common.vo.weixin.WeiXinVo;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 参考:https://blog.csdn.net/weixin_66709611/article/details/142059592
 *
 * @Author: liubh
 * @Date: 2024/10/28 13:38
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MiniProgramServiceImpl implements MiniProgramService {
    private final WeiXinConfigureProperties weiXinConfigureProperties;
    private final StringRedisTemplate stringRedisTemplate;
    private final UserInfoMapper userInfoMapper;
    private final SysUserMapper sysUserMapper;
    private final MoneyWaterService moneyWaterService;
    private final ProductOrderMapper productOrderMapper;
    private final PowerBankOrderMapper powerBankOrderMapper;
    private final MoneyPointMapper moneyPointMapper;
    private final MoneySetPointMapper moneySetPointMapper;
    private final MoneyWaterMapper moneyWaterMapper;
    private final String WX_TOKEN = "wx_token";
    private final ReentrantLock lock = new ReentrantLock();
    /**
     * 请求参数
     */
    public static RequestParam requestParam = null;

    /**
     * 获取微信小程序访问accessToken
     *
     * @return accessToken
     */
    @Override
    public String getAccessToken() {
        String accessToken = stringRedisTemplate.opsForValue().get(WX_TOKEN + ":" + weiXinConfigureProperties.getAppId());
        if (StringUtils.isEmpty(accessToken)) {
            WeiXinVo miniProgramAccessToken = getMiniProgramAccessToken();
            if (miniProgramAccessToken != null) {
                return miniProgramAccessToken.getAccessToken();
            }
        }
        return accessToken;
    }

    /**
     * 微信小程序登录
     *
     * @return 微信登录信息
     */
    @Override
    public WeiXinVo getWeiXinLoginOpenId(WeiXinUserInfoDto weiXinUserInfoDto) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + weiXinConfigureProperties.getAppId() + "&secret=" + weiXinConfigureProperties.getAppSecret() +
                "&js_code=" + weiXinUserInfoDto.getCode() + "&grant_type=authorization_code";
        String response = null;
        try {
            response = HttpUtil.get(url);
        } catch (Exception e) {
            log.info("获取登录信息失败 " + e.getMessage());
            throw new ApiException("获取登录信息失败");
        }
        log.info("获取登录信息 {}", response);
        WeiXinVo weiXinVo = JSONObject.parseObject(response, WeiXinVo.class);
        if (weiXinVo != null) {
            if (StringUtils.isNotEmpty(weiXinVo.getOpenId())) {
                // 解密用户信息
                String userInfo = null;
                try {
                    userInfo = AesCbcUtil.decrypt(weiXinUserInfoDto.getEncryptedData(), weiXinVo.getSessionKey(), weiXinUserInfoDto.getIv(), "UTF-8");
                } catch (Exception e) {
                    throw new ApiException("解析用户信息失败");
                }
                JSONObject userInfoJSON = JSON.parseObject(userInfo);
                String nickName = userInfoJSON.getString("nickName");
                String avatarUrl = userInfoJSON.getString("avatarUrl");
                weiXinVo.setNickName(nickName);
                weiXinVo.setAvatarUrl(avatarUrl);
                weiXinVo.setGender((String) userInfoJSON.get("gender"));
                weiXinVo.setCity((String) userInfoJSON.get("city"));
                weiXinVo.setProvince((String) userInfoJSON.get("province"));
                weiXinVo.setCountry((String) userInfoJSON.get("country"));
                //获取手机号
                String phoneNumber = getPhoneNumber(weiXinUserInfoDto.getCode());
                weiXinVo.setPhoneNumber(phoneNumber);
                weiXinVo.setPhoneInfo(new WeiXinVo.PhoneInfo().setPhoneNumber(phoneNumber));
                log.info("获取用户信息 {}", weiXinVo);
                return weiXinVo;
            }
        }
        return weiXinVo;
    }

    /**
     * 获取微信小程序手机号
     *
     * @param code code码
     * @return 登录信息
     */
    @Override
    public String getPhoneNumber(String code) {
        String url = "weiXinApi/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN";
        url = url.replace("weiXinApi", weiXinConfigureProperties.getUrl()).replace("ACCESS_TOKEN", getAccessToken());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", code);
        log.info("获取手机号参数 {}", jsonObject.toJSONString());
        String response = null;
        try {
            response = HttpUtil.post(url, jsonObject.toJSONString());
        } catch (Exception e) {
            throw new ApiException("获取手机号失败");
        }
        log.info("获取手机号返回结果 {}", response);
        WeiXinVo weiXinVo = JSONObject.parseObject(response, WeiXinVo.class);
        if (weiXinVo != null) {
            WeiXinVo.PhoneInfo phoneInfo = weiXinVo.getPhoneInfo();
            if (phoneInfo != null) {
                return phoneInfo.getPhoneNumber();
            }
        } else {
            throw new ApiException("获取手机号失败");
        }
        return null;
    }

    /**
     * 微信小程序登录
     *
     * @return 微信登录信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserInfoVo wxLogin(WeiXinUserInfoDto weiXinUserInfo) {
        UserInfoVo userInfoVo = new UserInfoVo();
        //判断用户是否注册,如果没有注册,注册用户,注册的用户,直接登录
        LambdaQueryWrapper<UserInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfoEntity::getOpenId, weiXinUserInfo.getOpenId());
        List<UserInfoEntity> userInfos = userInfoMapper.selectList(queryWrapper);
        SysUserEntity entity = new SysUserEntity();
        if (CollectionUtils.isNotEmpty(userInfos)) {
            UserInfoEntity userInfo = userInfos.get(0);
            entity.setNickName(userInfo.getNickName());
            //手机号作为用户名
            entity.setUserName(userInfo.getPhoneNumber());
            entity.setAvatar(userInfo.getAvatarUrl());
            entity.setPhoneNumber(userInfo.getPhoneNumber());
            entity.setId(userInfo.getId());
            StpUtil.login(entity.getId(), SaLoginModel.create().build());
            StpUtil.getSession().set("LoginUser", entity);
            BeanUtils.copyProperties(userInfos.get(0), userInfoVo);
        } else {
            //保存系统用户表
            SysUserEntity sysUserEntity = getSysUserEntity(weiXinUserInfo);
            //保存用户信息表
            UserInfoEntity userInfo = new UserInfoEntity();
            //系统用户表id和用户表id设置成一样的
            userInfo.setId(sysUserEntity.getId());
            userInfo.setOpenId(weiXinUserInfo.getOpenId());
            userInfo.setNickName(weiXinUserInfo.getNickName());
            userInfo.setAvatarUrl(weiXinUserInfo.getAvatarUrl());
            userInfo.setPhoneNumber(weiXinUserInfo.getPhoneNumber());
            userInfoMapper.insert(userInfo);
            entity.setNickName(userInfo.getNickName());
            //手机号作为用户名
            entity.setUserName(userInfo.getPhoneNumber());
            entity.setAvatar(userInfo.getAvatarUrl());
            entity.setPhoneNumber(userInfo.getPhoneNumber());
            entity.setId(sysUserEntity.getId());
            StpUtil.login(entity.getId(), SaLoginModel.create().build());
            StpUtil.getSession().set("LoginUser", entity);
            BeanUtils.copyProperties(userInfo, userInfoVo);
        }
        // 获取当前会话的 token 信息参数
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        BeanUtils.copyProperties(tokenInfo, userInfoVo);
        return userInfoVo;
    }

    /**
     * 设置系统用户信息
     *
     * @param weiXinUserInfo 系统用户信息
     * @return 系统用户信息
     */
    private SysUserEntity getSysUserEntity(WeiXinUserInfoDto weiXinUserInfo) {
        SysUserEntity sysUserEntity = new SysUserEntity();
        sysUserEntity.setUserName(weiXinUserInfo.getPhoneNumber());
        sysUserEntity.setNickName(weiXinUserInfo.getNickName());
        //默认密码,手机号后六位
        if (StringUtils.isNotEmpty(weiXinUserInfo.getPhoneNumber())) {
            String passWord = weiXinUserInfo.getPhoneNumber().substring(weiXinUserInfo.getPhoneNumber().length() - 6);
            sysUserEntity.setPassword(SaSecureUtil.md5(passWord));
        }
        sysUserEntity.setAvatar(weiXinUserInfo.getAvatarUrl());
        sysUserEntity.setStatus("0");
        sysUserEntity.setDelFlag(false);
        sysUserEntity.setPhoneNumber(weiXinUserInfo.getPhoneNumber());
        sysUserEntity.setUserType("01");
        sysUserEntity.setLoginIp(IpUtils.getIpAddr());
        sysUserEntity.setLoginDate(DateUtil.now());
        sysUserMapper.insert(sysUserEntity);
        return sysUserEntity;
    }

    /**
     * 微信支付
     *
     * @param payDto 支付信息
     * @return 支付信息
     */
    @Override
    public PrepayWithRequestPaymentResponse weiXinPay(PayDto payDto) {
//        PayVo payVo = new PayVo();
        PrepayRequest request = new PrepayRequest();
        //构建支付参数
        request.setAppid(weiXinConfigureProperties.getAppId());
        request.setMchid(weiXinConfigureProperties.getMchId());
        request.setDescription(payDto.getOrderCode() + "order");
        request.setNotifyUrl(weiXinConfigureProperties.getNotifyUrl());
        request.setOutTradeNo(payDto.getOrderCode());
        Amount amount = new Amount();
        amount.setCurrency("CNY");
        BigDecimal multiply = payDto.getAmount().multiply(new BigDecimal(100));
        amount.setTotal(multiply.setScale(0, RoundingMode.HALF_UP).intValue());
        Payer payer = new Payer();
        payer.setOpenid(payDto.getOpenId());
        request.setPayer(payer);
        log.info("微信支付参数 {}", request);
        PrepayWithRequestPaymentResponse response;
        try {
            response = weiXinConfigureProperties.getJsapiServiceExtension().prepayWithRequestPayment(request);
            return response;
        } catch (Exception e) {
            log.info("微信小程序支付异常 {}", e.getMessage());
            throw new ApiException("支付异常");
        }
    }

    /**
     * 微信支付回调 JSAPI 下单回调
     *
     * @return 回调结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Transaction wxPaymentCallback(HttpServletRequest request) {
        NotificationParser notificationParser = getNotificationParser(request);
        Transaction parse = notificationParser.parse(requestParam, Transaction.class);
        //订单号
        String outTradeNo = parse.getOutTradeNo();
        //微信支付流水号
        String transactionId = parse.getTransactionId();
        log.info("微信支付回调信息:{}", parse);
        //校验金额是否正确
        LambdaQueryWrapper<ProductOrderEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductOrderEntity::getOrderCode, outTradeNo);
        List<ProductOrderEntity> productOrderEntities = productOrderMapper.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(productOrderEntities)) {
            ProductOrderEntity productOrderEntity = productOrderEntities.get(0);
            BigDecimal orderAmount = productOrderEntity.getOrderAmount();
            log.info("订单金额:{}", orderAmount);
            BigDecimal amount = new BigDecimal(parse.getAmount().getTotal()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
            log.info("支付金额:{}", amount);
            if (orderAmount.compareTo(amount) != 0) {
                log.info("订单金额与支付金额不相等");
                return null;
            }
            //修改订单状态
            productOrderEntity.setOrderState(payState(parse.getTradeState()));
            productOrderMapper.updateById(productOrderEntity);
            //增加或者更新流水
            moneyWaterService.addBreakFastMachineMoneyWater(productOrderEntity, transactionId, payState(parse.getTradeState()));

        } else {
            LambdaQueryWrapper<PowerBankOrderEntity> powerOrder = new LambdaQueryWrapper<>();
            powerOrder.eq(PowerBankOrderEntity::getOrderCode, outTradeNo);
            List<PowerBankOrderEntity> powerBankOrders = powerBankOrderMapper.selectList(powerOrder);
            if (CollectionUtils.isNotEmpty(powerBankOrders)) {
                PowerBankOrderEntity powerBankOrderEntity = powerBankOrders.get(0);
                //校验金额是否正确
                BigDecimal orderAmount = powerBankOrderEntity.getOrderAmount();
                log.info("订单金额:{}", orderAmount);
                BigDecimal amount = new BigDecimal(parse.getAmount().getTotal()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
                log.info("支付金额:{}", amount);
                if (orderAmount.compareTo(amount) != 0) {
                    log.info("订单金额与支付金额不相等");
                    return null;
                }
                powerBankOrderEntity.setOrderState(payState(parse.getTradeState()));
                //更新订单状态
                powerBankOrderMapper.updateById(powerBankOrderEntity);
                //增加或者是更新流水
                moneyWaterService.addPowerBankMoneyWater(powerBankOrderEntity, transactionId, payState(parse.getTradeState()));
                //只有支付成功的时候,才设置积分
                if (parse.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
                    //设置积分
                    List<MoneySetPointEntity> moneySetPointEntities = moneySetPointMapper.selectList(null);
                    if (CollectionUtils.isNotEmpty(moneySetPointEntities)) {
                        MoneySetPointEntity moneySetPointEntity = moneySetPointEntities.get(0);
                        BigDecimal pointAmount = moneySetPointEntity.getPointAmount();
                        BigDecimal moneyAmount = moneySetPointEntity.getMoneyAmount();
                        MoneyPointEntity moneyPointEntity = new MoneyPointEntity();
                        moneyPointEntity.setOrderId(powerBankOrderEntity.getId()).setOrderType(2)
                                .setPointType(1).setUserId(powerBankOrderEntity.getUserId());
                        moneyPointEntity.setPointAmount(NumberComputeUtil.numberMul(NumberComputeUtil.numberDiv(pointAmount, moneyAmount), orderAmount));
                        moneyPointMapper.insert(moneyPointEntity);
                    }
                }
            }

        }
        return notificationParser.parse(requestParam, Transaction.class);
    }

    /**
     * 退款
     *
     * @param orderId 订单id
     * @param amount  金额
     * @return 退款信息
     */
    @Override
    public Refund refundIndentPayment(String orderId, BigDecimal amount) {
        LambdaQueryWrapper<MoneyWaterEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MoneyWaterEntity::getOrderId, orderId);
        List<MoneyWaterEntity> waterEntities = moneyWaterMapper.selectList(queryWrapper);
        Refund refund = null;
        if (CollectionUtils.isNotEmpty(waterEntities)) {
            MoneyWaterEntity moneyWaterEntity = waterEntities.get(0);
            String orderCode = "";
            if (moneyWaterEntity.getOrderType() == 1) {
                ProductOrderEntity productOrderEntity = productOrderMapper.selectById(moneyWaterEntity.getOrderId());
                if (productOrderEntity != null) {
                    orderCode = productOrderEntity.getOrderCode();
                }
            } else {
                PowerBankOrderEntity powerBankOrderEntity = powerBankOrderMapper.selectById(moneyWaterEntity.getOrderId());
                if (powerBankOrderEntity != null) {
                    orderCode = powerBankOrderEntity.getOrderCode();
                }
            }
            // 退款请求
            CreateRequest createRequest = new CreateRequest();
            // 商户订单号
            createRequest.setOutTradeNo(orderCode);
            // 商户退款单号
            lock.lock();
            try {
                createRequest.setOutRefundNo(IdUtil.getSnowflakeNextIdStr());
            } finally {
                lock.unlock();
            }
            // 退款结果回调
            createRequest.setNotifyUrl(weiXinConfigureProperties.getRefundPayUrl());
            // 退款金额
            AmountReq amountReq = new AmountReq();
            long refundAmount = new BigDecimal(String.valueOf(amount)).movePointRight(2).intValue();
            amountReq.setRefund(refundAmount);
            long totalAmount = new BigDecimal(String.valueOf(moneyWaterEntity.getAmount())).movePointRight(2).intValue();
            amountReq.setTotal(totalAmount);
            amountReq.setCurrency("CNY");
            createRequest.setAmount(amountReq);
            // 申请退款
            try {
                refund = weiXinConfigureProperties.getRefundService().create(createRequest);
            } catch (Exception e) {
                log.error("退款申请失败", e);
                throw new ApiException("退款失败");
            }
            log.info("退款申请结果:{}", JSON.toJSONString(refund));
        }
        return refund;
    }

    /**
     * 退款回调
     *
     * @param request 请求
     * @return 退款是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean getRefundNotification(HttpServletRequest request) {
        NotificationParser notificationParser = getNotificationParser(request);
        RefundNotification refundNotification = notificationParser.parse(requestParam, RefundNotification.class);
        log.info("退款通知,{}", refundNotification.getRefundStatus());
        //支付单号
        String transactionId = refundNotification.getTransactionId();
        LambdaQueryWrapper<MoneyWaterEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MoneyWaterEntity::getTransactionId, transactionId);
        List<MoneyWaterEntity> moneyWaterEntities = moneyWaterMapper.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(moneyWaterEntities)) {
            MoneyWaterEntity moneyWaterEntity = moneyWaterEntities.get(0);
            //修改流水订单状态
            moneyWaterEntity.setTradeState(refundState(refundNotification.getRefundStatus()));
            SimpleDateFormat originalFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
            Date date = null;
            try {
                date = originalFormat.parse(refundNotification.getSuccessTime());
                moneyWaterEntity.setRefundTime(date);
            } catch (ParseException e) {
                log.error("退款时间转换失败", e);
            }
            moneyWaterMapper.updateById(moneyWaterEntity);
            if (moneyWaterEntity.getOrderType() == 1) {
                //修改订单状态
                ProductOrderEntity productOrderEntity = productOrderMapper.selectById(moneyWaterEntity.getOrderId());
                productOrderEntity.setOrderState(refundState(refundNotification.getRefundStatus()));
                productOrderMapper.updateById(productOrderEntity);
            } else {
                //修改订单状态
                PowerBankOrderEntity powerBankOrderEntity = powerBankOrderMapper.selectById(moneyWaterEntity.getOrderId());
                powerBankOrderEntity.setOrderState(refundState(refundNotification.getRefundStatus()));
                powerBankOrderMapper.updateById(powerBankOrderEntity);
            }
        }
        return true;
    }

    /**
     * 支付状态
     * @param tradeStateEnum 微信返回支付状态
     * @return 自定义支付状态
     */
    public Integer payState(Transaction.TradeStateEnum tradeStateEnum) {
        switch (tradeStateEnum) {
            case SUCCESS:
                //支付成功
                return OrderMachineStateEnum.PAY.getType();
            case REFUND:
                //退款中
                return OrderMachineStateEnum.REFUND.getType();
            case NOTPAY:
                //未支付
                return OrderMachineStateEnum.NOT_PAY.getType();
            case CLOSED:
                //已关闭
                return OrderMachineStateEnum.CLOSE.getType();
            case REVOKED:
                //已撤销
                return OrderMachineStateEnum.REVOKED.getType();
            case USERPAYING:
                //用户支付中
                return OrderMachineStateEnum.ACCEPT.getType();
            case PAYERROR:
                //支付失败
                return OrderMachineStateEnum.FAIL.getType();
        }
        return 0;
    }

    /**
     * 退款状态
     * @param status 微信返回退款状态
     * @return 自定义退款状态
     */
    public Integer refundState(Status status) {
        switch (status) {
            //退款成功状态
            case SUCCESS:
                return OrderMachineStateEnum.REFUND_SUCCESS.getType();
                //退款关闭
            case CLOSED:
                return OrderMachineStateEnum.REFUND_CLOSE.getType();
                //退款处理中
            case PROCESSING:
                return OrderMachineStateEnum.REFUND_PROCESSING.getType();
                //退款异常
            case ABNORMAL:
                return OrderMachineStateEnum.REFUND_ABNORMAL.getType();
        }
        return 0;
    }


    /**
     * 根据微信官方发送的请求获取信息
     */
    @SneakyThrows
    public NotificationParser getNotificationParser(HttpServletRequest request) {
        // 获取RSA配置
        NotificationParser notificationParser = new NotificationParser(weiXinConfigureProperties.getRSAConfig());
        // 构建请求
        StringBuilder bodyBuilder = new StringBuilder();
        BufferedReader reader = request.getReader();
        String line;
        while ((line = reader.readLine()) != null) {
            bodyBuilder.append(line);
        }
        String body = bodyBuilder.toString();
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String signature = request.getHeader("Wechatpay-Signature");
        String singType = request.getHeader("Wechatpay-Signature-Type");
        String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
        requestParam = new RequestParam.Builder()
                .serialNumber(wechatPayCertificateSerialNumber)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(singType)
                .body(body)
                .build();
        return notificationParser;
    }


    /**
     * 获取小程序接口调用凭证
     *
     * @return accessToken
     */
    private WeiXinVo getMiniProgramAccessToken() {
        String url = "weiXinApi/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
        url = url.replace("weiXinApi", weiXinConfigureProperties.getUrl()).replace("APPID", weiXinConfigureProperties.getAppId()).replace("APPSECRET", weiXinConfigureProperties.getAppSecret());
        WeiXinVo result = null;
        String data = null;
        try {
            data = HttpUtil.get(url);
            log.info("获取微信小程序accessToken 返回结果 " + data);
            result = JSONObject.parseObject(data, WeiXinVo.class);
            stringRedisTemplate.opsForValue().set(WX_TOKEN + ":" + weiXinConfigureProperties.getAppId(), result.getAccessToken(), result.getExpiresIn() - 10, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("获取微信小程序accessToken 异常 " + weiXinConfigureProperties.getAppId() + data, e);
            throw new ApiException("获取accessToken失败");
        }
        return result;
    }
}

4、配置类

package com.saburo.server.common.properties;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.IOUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 微信小程序属性配置信息
 * 参考:https://blog.csdn.net/weixin_66709611/article/details/142059592
 * @author liubh
 */
@Data
@Component
@ConfigurationProperties(prefix = "wx")
public class WeiXinConfigureProperties {

    /**
     * 微信访问地址
     */
    private String url;

    /**
     * 微信小程序ID
     */
    private String appId;
    /**
     * 微信支付商户号
     */
    private String mchId;

    /**
     * 微信小程序秘钥
     */
    private String appSecret;
    /**
     * 微信支付回调地址
     */
    private String notifyUrl;
    /**
     * 微信支付退款回调地址
     */
    private String refundPayUrl;
    /**
     * 微信支付私钥
     */
    private String privateKeyPath;
    /**
     * 微信支付公钥
     */
    private String privateCertPath;
    /**
     * 微信支付V3密钥
     */
    private String apiV3Key;
    // RSA配置
    private RSAAutoCertificateConfig RSAConfig;

    // JSAPI支付
    private JsapiServiceExtension jsapiServiceExtension;

    // 退款
    private RefundService refundService;
    // 商户API证书序列号
    private String merchantSerialNumber;

    /**
     * 初始化配置
     */
    @Bean
    public void initWxPayConfig() throws IOException {
        this.RSAConfig = buildRSAAutoCertificateConfig();
        this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig);
        this.refundService = buildRefundService(RSAConfig);
    }

    /**
     * 构建并使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
     * @return RSA配置
     */
    private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException {
        // 将 resource 目录下的文件转为 InputStream,然后利用 IOUtil.toString(inputStream) 转化为密钥
        String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream());
        return new RSAAutoCertificateConfig.Builder()
                .merchantId(mchId)
                .privateKey(privateKey)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(apiV3Key)
                .build();
    }

    // 构建JSAPI
    private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) {
        return new JsapiServiceExtension.Builder().config(config).build();
    }

    // 构建退款
    private RefundService buildRefundService(RSAAutoCertificateConfig config) {
        return new RefundService.Builder().config(config).build();
    }



 

 

posted @ 2024-11-04 11:30  刘百会  阅读(86)  评论(0编辑  收藏  举报