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(); }