微信支付

This commit is contained in:
haown 2025-08-18 09:15:16 +08:00
parent 2f68392778
commit cee381fcef
51 changed files with 4267 additions and 210 deletions

View File

@ -191,6 +191,29 @@
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<version>5.5.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.6</version>
</dependency>
<!-- Jackson数据绑定库 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.6</version>
</dependency>
<!-- Jackson数据格式化支持库例如JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.6</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,21 @@
package com.yf.exam.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
/**
* @Description Redis分布式锁配置
* @Author 纪寒
* @Date 2022-10-26 15:27:41
* @Version 1.0
*/
@Configuration
public class RedisDistributedLockUtilsConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, this.getClass().getName());
}
}

View File

@ -17,6 +17,31 @@ public class Constants {
*/
public static final String TOKEN = "token";
/**
* 微信小程序支付prepay_id前缀
*/
public static final String PREPAY_ID_KEY = "PREPAY_ID_KEY:";
/**
* 微信支付回调标识
*/
public static final String PAY_NOTIFY = "PAY";
/**
* 微信退款回调标识
*/
public static final String REFUND_NOTIFY = "REFUND";
/**
* 微信支付回调通知订单锁前缀
*/
public static final String WE_CHAT_NOTIFY_KEY = "WE_CHAT_NOTIFY_KEY:";
/**
* 微信退款回调通知订单锁前缀
*/
public static final String WE_CHAT_REFUND_KEY = "WE_CHAT_REFUND_KEY:";
/**
* 文件上传路径
*/

View File

@ -1,8 +1,8 @@
package com.yf.exam.core.api.controller;
import com.yf.exam.core.api.ApiError;
import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.core.exception.ServiceException;
/**
@ -151,4 +151,14 @@ public class BaseController {
ApiRest<T> apiRest = message(ex.getCode(), ex.getMsg(), null);
return apiRest;
}
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows) {
return rows > 0 ? AjaxResult.success() : AjaxResult.error();
}
}

View File

@ -0,0 +1,29 @@
package com.yf.exam.core.enums;
import lombok.Getter;
/**
* @Description 确认退款状态枚举
* @Author 纪寒
* @Date 2022-10-26 11:15:21
* @Version 1.0
*/
@Getter
public enum ConfirmRefundStatusEnum {
/**
* 未确认
*/
NOT_CONFIRM("NOT_CONFIRM"),
/**
* 已确认
*/
CONFIRMED("CONFIRMED"),
;
final private String info;
ConfirmRefundStatusEnum(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,69 @@
package com.yf.exam.core.enums;
import lombok.Getter;
/**
* @Description 商品订单状态枚举
* @Author 纪寒
* @Date 2022-10-19 09:15:38
* @Version 1.0
*/
@Getter
public enum GooodsOrderStatusEnum {
/**
* 待付款
*/
WAIT_PAY("WAIT_PAY"),
/**
* 已付款
*/
PAY("PAY"),
/**
* 已取消
*/
CANCEL("CANCEL"),
/**
* 待收货
*/
WAIT_RECEIVED_GOODS("WAIT_RECEIVED_GOODS"),
/**
* 已收货
*/
RECEIVED_GOODS("RECEIVED_GOODS"),
/**
* 退款中
*/
WAIT_REFUND("WAIT_REFUND"),
/**
* 已退款
*/
REFUNDED("REFUNDED"),
/**
* 待退货
*/
WAIT_RETURNED_GOODS("WAIT_RETURNED_GOODS"),
/**
* 已退货
*/
RETURNED_GOODS("RETURNED_GOODS"),
/**
* 已评价
*/
EVALUATED("EVALUATED"),
;
final private String info;
GooodsOrderStatusEnum(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,30 @@
package com.yf.exam.core.enums;
import lombok.Getter;
/**
* @Description 支付了类型枚举
* @Author 纪寒
* @Date 2022-10-18 15:16:02
* @Version 1.0
*/
@Getter
public enum PayTypeEnum {
/**
* 微信
*/
WECHAT_PAY("WECHAT_PAY"),
/**
* 支付宝
*/
ALI_PAY("ALI_PAY"),
;
final private String info;
PayTypeEnum(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,41 @@
package com.yf.exam.core.enums;
import lombok.Getter;
/**
* @Description 微信退款通知枚举
* @Author 纪寒
* @Date 2022-10-25 13:12:03
* @Version 1.0
*/
@Getter
public enum RefundStatusEnum {
/**
* 退款成功
*/
SUCCESS("SUCCESS"),
/**
* 退款关闭
*/
CLOSED("CLOSED"),
/**
* 退款处理中
*/
PROCESSING("PROCESSING"),
/**
* 退款异常退款到银行发现用户的卡作废或者冻结了
* 导致原路退款银行卡失败可前往商户平台>交易中心手动处理此笔退款
*/
ABNORMAL("ABNORMAL"),
;
final private String info;
RefundStatusEnum(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,52 @@
package com.yf.exam.core.enums;
import lombok.Getter;
/**
* 微信支付回调通知状态
*/
@Getter
public enum WeChatTradeStateEnum {
/**
* 支付成功
*/
SUCCESS("SUCCESS"),
/**
* 未支付
*/
NOTPAY("NOTPAY"),
/**
* 已关闭
*/
CLOSED("CLOSED"),
/**
* 转入退款
*/
REFUND("REFUND"),
/**
* 已撤销付款码支付
*/
REVOKED("REVOKED"),
/**
* 用户支付中付款码支付
*/
USERPAYING("USERPAYING"),
/**
* 支付失败(其他原因如银行返回失败)
*/
PAYERROR("PAYERROR")
;
final private String info;
WeChatTradeStateEnum(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,91 @@
package com.yf.exam.core.utils;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;
/**
* @Description Redis锁工具类
* @Author 纪寒
* @Date 2022-10-24 10:00:00
* @Version 1.0
*/
@SuppressWarnings("unused")
@Slf4j
@Component
public class RedisDistributedLockUtils {
/**
* 默认的有效时长
*/
private static final long DEFAULT_EXPIRE_UNUSED = 30000L;
@Resource
private RedisLockRegistry redisLockRegistry;
/**
* 加锁
*
* @param lockKey
*/
@SuppressWarnings("AlibabaLockShouldWithTryFinally")
public void lock(String lockKey) {
Lock lock = obtainLock(lockKey);
lock.lock();
}
/**
* 尝试获取锁
*
* @param lockKey
* @return 结果
*/
public boolean tryLock(String lockKey) {
Lock lock = obtainLock(lockKey);
return lock.tryLock();
}
/**
* 尝试获取锁带有时间单位
*
* @param lockKey
* @param seconds 时间单位秒
* @return 结果
*/
public boolean tryLock(String lockKey, long seconds) {
Lock lock = obtainLock(lockKey);
try {
return lock.tryLock(seconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 释放锁
*
* @param lockKey
*/
public void unlock(String lockKey) {
try {
Lock lock = obtainLock(lockKey);
lock.unlock();
redisLockRegistry.expireUnusedOlderThan(DEFAULT_EXPIRE_UNUSED);
} catch (Exception e) {
log.warn("分布式锁 [{}] 释放异常", lockKey, e);
}
}
/**
* 释放锁
*
* @param lockKey
* @return 结果
*/
private Lock obtainLock(String lockKey) {
return redisLockRegistry.obtain(lockKey);
}
}

View File

@ -0,0 +1,40 @@
package com.yf.exam.modules.applet.controller;
import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.api.controller.BaseController;
import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSearchDTO;
import com.yf.exam.modules.exam.service.ExamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: haown
* @create: 2025-08-14 14:07
**/
@Api(tags={"小程序端考试"})
@RestController
@RequestMapping("/examApplet/api/exam")
public class AppletExamController extends BaseController {
@Autowired
private ExamService examService;
/**
* 查询可考试列表
* @param reqDTO
* @return
*/
@ApiOperation(value = "查询可考试列表")
@RequestMapping(value = "/getExamList", method = { RequestMethod.GET})
public ApiRest getExamList(ExamSearchDTO reqDTO) {
List<ExamDTO> list = examService.getExamList(reqDTO);
return super.success(list);
}
}

View File

@ -0,0 +1,55 @@
package com.yf.exam.modules.applet.controller;
import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.api.controller.BaseController;
import com.yf.exam.core.enums.GooodsOrderStatusEnum;
import com.yf.exam.modules.exam.dto.ExamRegistrationDTO;
import com.yf.exam.modules.exam.dto.response.ExamRegistrationVO;
import com.yf.exam.modules.exam.service.ExamRegistrationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: 考试报名控制器
* @author: haown
* @create: 2025-08-14 14:12
**/
@Api(tags={"小程序考试报名"})
@RestController
@RequestMapping("/examApplet/api/exam/registration")
public class AppletExamRegistrationController extends BaseController {
@Autowired
private ExamRegistrationService baseService;
/**
* 添加或修改
* @param reqDTO
* @return
*/
@ApiOperation(value = "添加或修改")
@RequestMapping(value = "/save", method = { RequestMethod.POST})
public ApiRest save(@RequestBody ExamRegistrationDTO reqDTO) {
//复制参数
return super.success(baseService.save(reqDTO));
}
/**
* 查询已报名的考试列表
* @return
*/
@ApiOperation(value = "查询已报名的考试列表")
@RequestMapping(value = "/getRegExamList", method = { RequestMethod.GET})
public ApiRest getRegExamList(ExamRegistrationDTO examRegistrationDTO) {
examRegistrationDTO.setPaymentState(GooodsOrderStatusEnum.PAY.getInfo());
List<ExamRegistrationVO> list = baseService.getRegExamList(examRegistrationDTO);
return super.success(list);
}
}

View File

@ -55,6 +55,9 @@ public class AppletLoginController extends BaseController {
if (BooleanUtils.isFalse(cardNo)) {
return AjaxResult.error("您输入的身份证号不正确,请重新输入!");
}
if (StringUtils.isBlank(reqDTO.getOpenid())) {
return AjaxResult.error("登录凭证不能为空!");
}
return AjaxResult.success(sysUserService.reg(reqDTO));
}

View File

@ -0,0 +1,139 @@
package com.yf.exam.modules.applet.controller;
import com.yf.exam.core.api.controller.BaseController;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.applet.dto.PaymentDTO;
import com.yf.exam.modules.applet.dto.RefundDTO;
import com.yf.exam.modules.applet.service.WeChatPayNotifyService;
import com.yf.exam.modules.applet.service.WeChatPaymentService;
import com.yf.exam.modules.applet.service.WeChatRefundService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.math.BigDecimal;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description 微信小程序支付控制器
* @Author 纪寒
* @Date 2022-10-18 14:47:11
* @Version 1.0`
*/
@Api(tags={"支付控制器"})
@RestController
@RequestMapping("/examApplet/weChatPayment")
public class WeChatPaymentController extends BaseController {
@Resource
private WeChatPaymentService weChatPaymentService;
@Resource
private WeChatPayNotifyService weChatPayNotifyService;
@Resource
private WeChatRefundService weChatRefundService;
/**
* 微信小程序支付接口
*
* @param paymentDTO 输入参数
* @return 支付返回结果
* @throws Exception 异常信息
*/
@ApiOperation(value = "微信小程序支付接口")
@PostMapping("/appletPay")
public AjaxResult appletPay(@RequestBody PaymentDTO paymentDTO, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new ServiceException(bindingResult.getAllErrors().get(0).getDefaultMessage());
}
if (StringUtils.isBlank(paymentDTO.getOpenid())) {
return AjaxResult.error("微信openid不能为空");
}
if (Objects.nonNull(paymentDTO.getPaymentPrice()) && paymentDTO.getPaymentPrice().compareTo(BigDecimal.ZERO) < 0) {
return AjaxResult.error("订单支付金额不能为负数,无法支付!");
}
if (Objects.nonNull(paymentDTO.getPaymentPrice()) && paymentDTO.getPaymentPrice().compareTo(BigDecimal.ZERO) == 0) {
return AjaxResult.error("订单支付金额不能为零,无法支付!");
}
return weChatPaymentService.appletPay(paymentDTO);
}
/**
* 新医路微信支付回调通知接口
*
* @param request 请求头信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
* @throws Exception 异常信息
*/
@ApiOperation(value = "微信支付回调通知接口")
@PostMapping("/xylWeChatPayNotify")
public String xylWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
return weChatPayNotifyService.xylWeChatPayNotify(request, response);
}
/**
* 取消订单接口暂时废弃
*
* @param orderNo 订单编号
* @return 返回结果信息
* @throws Exception 异常信息
*/
@PostMapping("/cancelOrderInfo")
public AjaxResult cancelOrderInfo(@RequestParam("orderNo") String orderNo) throws Exception {
if (StringUtils.isBlank(orderNo)) {
return AjaxResult.error("请选择所要取消的订单信息!");
}
return weChatPaymentService.cancelOrderInfo(orderNo);
}
/**
* 查询订单状态接口暂时废弃
*
* @param orderNo 订单编号
* @return 返回结果信息
* @throws Exception 异常信息
*/
@PostMapping("/getOrderStatusInfo")
public AjaxResult getOrderStatusInfo(@RequestParam("orderNo") String orderNo) throws Exception {
if (StringUtils.isBlank(orderNo)) {
return AjaxResult.error("请选择所要查询的订单信息!");
}
return weChatPaymentService.getOrderStatusInfo(orderNo);
}
/**
* 微信确认退款接口
*
* @param refundDTO 退款参数
* @return 退款申请结果
* @throws Exception 异常信息
*/
@ApiOperation(value = "微信确认退款接口")
@PostMapping("/weChatRefundOrderApply")
public AjaxResult weChatRefundOrderApply(@RequestBody RefundDTO refundDTO) throws Exception {
return weChatRefundService.weChatRefundOrderApply(refundDTO);
}
/**
* 新医路微信退款回调通知接口
*
* @param request 请求头信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
* @throws Exception 异常信息
*/
@PostMapping("/xylWeChatRefundNotify")
public String xylWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
return weChatPayNotifyService.xylWeChatRefundNotify(request, response);
}
}

View File

@ -0,0 +1,67 @@
package com.yf.exam.modules.applet.dto;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;
/**
* @Description 微信支付参数接受实体类
* @Author 纪寒
* @Date 2022-10-18 15:12:51
* @Version 1.0
*/
@Data
public class PaymentDTO implements Serializable {
private static final long serialVersionUID = 4090244715734558969L;
/**
* ID
*/
@NotNull(message = "考试报名主键不能为空!")
@ApiModelProperty(value = "考试报名表主键", required=true)
private String id;
/**
* 考生id
*/
@NotNull(message = "考生id不能为空")
@ApiModelProperty(value = "考生id", required=true)
private String userId;
/**
* 微信用户openid
*/
@ApiModelProperty(value = "微信用户openid", required=true)
private String openid;
/**
* 考试id
*/
@ApiModelProperty(value = "考试表主键", required=true)
private String examId;
/**
* 支付类型微信支付WECHAT_PAY支付宝支付ALI_PAY
*/
@NotBlank(message = "支付类型不能为空!")
@ApiModelProperty(value = "支付类型微信支付WECHAT_PAY支付宝支付ALI_PAY", required=true)
private String payType;
/**
* 支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLE
*/
@NotBlank(message = "支付渠道不能为空!")
@ApiModelProperty(value = "支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLE", required=true)
private String orderChannel;
/**
* 支付金额
*/
@NotNull(message = "支付金额不能为空!")
@ApiModelProperty(value = "支付金额", required=true)
private BigDecimal paymentPrice;
}

View File

@ -0,0 +1,51 @@
package com.yf.exam.modules.applet.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;
/**
* @Description
* @Author 纪寒
* @Date 2022-10-24 16:15:44
* @Version 1.0
*/
@Data
public class RefundDTO implements Serializable {
private static final long serialVersionUID = -1990509784850994288L;
/**
* 订单编号必传字段
*/
@NotBlank(message = "订单编号不能为空!")
private String id;
/**
* 退款原因
*/
private String refundReason;
/**
* 退款金额必传字段
*/
@NotNull(message = "退款金额不能为空!")
private BigDecimal refundPrice;
/**
* 补充描述和凭证
*/
private String remark;
/**
* 货物到货状态目前用不到
*/
private String goodsStatus;
/**
* 订单类型健康咨询订单退款使用积分兑换INTEGRAL_EXCHANGE直接购买DIRECT_BUY健康咨询HEALTH_CONSULTATION
*/
private String orderType;
}

View File

@ -0,0 +1,34 @@
package com.yf.exam.modules.applet.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description 微信支付回调业务层
* @Author 纪寒
* @Date 2022-10-20 14:00:21
* @Version 1.0
*/
public interface WeChatPayNotifyService {
/**
* 新医路支付回调接口
*
* @param request 请求信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
* @throws Exception 异常信息
*/
String xylWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception;
/**
* 新医路退款回调接口
*
* @param request 请求信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
* @throws Exception 异常信息
*/
String xylWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

View File

@ -0,0 +1,70 @@
package com.yf.exam.modules.applet.service;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.modules.applet.dto.PaymentDTO;
import com.yf.exam.modules.applet.vo.OrderStatusInfoVO;
/**
* @Description 微信小程序和App支付业务层
* @Author 纪寒
* @Date 2022-10-18 15:02:27
* @Version 1.0
*/
public interface WeChatPaymentService {
/**
* 微信小程序支付接口
*
* @param paymentDTO 输入参数
* @return 支付结果
* @throws Exception 异常信息
*/
AjaxResult appletPay(PaymentDTO paymentDTO) throws Exception;
/**
* 取消订单接口
*
* @param orderNo 点单编号
* @return 取消结果信息
* @throws Exception 异常信息
*/
AjaxResult cancelOrderInfo(String orderNo) throws Exception;
/**
* 查询订单状态信息
*
* @param orderNo 点单编号
* @return 结果信息
* @throws Exception 异常信息
*/
AjaxResult getOrderStatusInfo(String orderNo) throws Exception;
/**
* 微信关闭订单方法
*
* @param orderNo 订单编号
* @return 状态码
* @throws Exception 异常信息
*/
int closeWeChatOrderInfo(String orderNo) throws Exception;
/**
* 查询预约服务订单信息
*
* @param orderNo 订单编号
* @param vo 返回值信息
* @return 订单状态标识
*/
OrderStatusInfoVO queryAppointmentOrderStatus(String orderNo, OrderStatusInfoVO vo);
/**
* 查询上商品订单信息
*
* @param orderNo 订单编号
* @param vo 返回值信息
* @return 订单状态标识
* @throws Exception 异常信息
*/
OrderStatusInfoVO queryGoodsOrderStatus(String orderNo, OrderStatusInfoVO vo) throws Exception;
}

View File

@ -0,0 +1,40 @@
package com.yf.exam.modules.applet.service;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.modules.applet.dto.RefundDTO;
/**
* @Description 微信退款业务层
* @Author 纪寒
* @Date 2022-10-24 15:22:49
* @Version 1.0
*/
public interface WeChatRefundService {
/**
* 微信确认退款接口
*
* @param refundDTO 退款参数
* @return 返回信息
* @throws Exception 异常信息
*/
AjaxResult weChatRefundOrderApply(RefundDTO refundDTO) throws Exception;
/**
* 调用微信查询单笔退款接口查询预约订单状态信息
*
* @param refundNo 退款单号
* @return 退款信息
*/
//WeChatRefundInfoVO queryAppointmentOrderRefundStatus(String refundNo);
/**
* 调用微信查询单笔退款接口查询商品订单状态信息
*
* @param refundNo 退单编号
* @param buySource 购买来源
* @return 退款信息
* @throws Exception 异常信息
*/
//WeChatRefundInfoVO queryGoodsOrderRefundStatus(String refundNo, String buySource) throws Exception;
}

View File

@ -61,7 +61,7 @@ public class AppletLoginServiceImpl implements AppletLoginService {
*/
@Override
public AjaxResult appletLogin(String loginCode) {
//根据code获取用户的微信unionId以及openId等信息
//根据code获取用户的微信openId等信息
AppletLoginVO appletLoginInfo = AppletChatUtil.getAppletLoginInfo(appletChatConfig.getAppletId(), appletChatConfig.getSecret(), loginCode, appletChatConfig.getGrantType());
if (Objects.isNull(appletLoginInfo)) {
return AjaxResult.error("获取微信小程序用户信息失败");
@ -75,56 +75,26 @@ public class AppletLoginServiceImpl implements AppletLoginService {
//从Redis中取出accessToken
Object object = redisTemplate.opsForValue().get(accessTokenKey);
if (Objects.isNull(object)) {
//没有获取accessToken
//没有获取accessToken,并存入redis
accessToken = appletAccessTokenUtil.getAppletAccessToken();
} else {
accessToken = (String) object;
}
//获取用户手机号
//AppletPhoneVO appletPhoneInfo = AppletChatUtil.getAppletPhoneInfo(phoneCode, accessToken);
//if (Objects.isNull(appletPhoneInfo)) {
// return AjaxResult.error("获取用户手机号失败");
//}
//if (Objects.nonNull(appletPhoneInfo.getErrcode()) && appletPhoneInfo.getErrcode() == ERROR_ACCESS_CODE) {
// //当前Redis缓存中的access_token无效直接删除
// if (Objects.nonNull(object)) {
// redisTemplate.delete(accessTokenKey);
// //删除之后重新获取获取accessToken
// accessToken = appletAccessTokenUtil.getAppletAccessToken();
// appletPhoneInfo = AppletChatUtil.getAppletPhoneInfo(phoneCode, accessToken);
// if (Objects.isNull(appletPhoneInfo)) {
// return AjaxResult.error("获取用户手机号失败");
// }
// if (Objects.nonNull(appletPhoneInfo.getErrcode()) && appletPhoneInfo.getErrcode() == ERROR_ACCESS_CODE) {
// return AjaxResult.error("登录失败!");
// }
// }
//}
//if (StringUtils.isNotBlank(appletPhoneInfo.getErrmsg()) && !OK.equals(appletPhoneInfo.getErrmsg())) {
// return AjaxResult.error("获取用户手机号失败,失败信息为:" + appletPhoneInfo.getErrmsg());
//}
// 根据手机号和openid判断当前用户是否存在
//String phone = StringUtils.isBlank(appletPhoneInfo.getPhoneInfo().getPhoneNumber()) ? "" : appletPhoneInfo.getPhoneInfo().getPhoneNumber();
// 根据openid判断当前用户是否存在
String openId = StringUtils.isBlank(appletLoginInfo.getOpenid()) ? "" : appletLoginInfo.getOpenid();
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.lambda()
//.eq(SysUser::getPhone, phone)
.eq(SysUser::getOpenid, openId);
SysUser userByPhone = sysUserService.getOne(wrapper, false);
wrapper.lambda().eq(SysUser::getOpenid, openId);
SysUser userByOpenid = sysUserService.getOne(wrapper, false);
SysUser user = new SysUser();
// 考生信息为空返回openId
if (Objects.isNull(userByPhone)) {
if (Objects.isNull(userByOpenid)) {
return AjaxResult.success("您还未注册,请注册后使用!", openId);
}
//更新用户的openid等微信标识信息
user.setId(userByPhone.getId());
user.setOpenid(openId);
user.setPhone(StringUtils.isBlank(userByPhone.getPhone()) ? "" : userByPhone.getPhone());
user.setUpdateTime(new Date());
sysUserService.updateById(user);
return AjaxResult.success(user);
userByOpenid.setOpenid(openId);
userByOpenid.setUpdateTime(new Date());
sysUserService.updateById(userByOpenid);
return AjaxResult.success(userByOpenid);
}
/**

View File

@ -0,0 +1,381 @@
package com.yf.exam.modules.applet.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.yf.exam.config.XylWeChatPaymentConfig;
import com.yf.exam.constant.Constants;
import com.yf.exam.core.enums.GooodsOrderStatusEnum;
import com.yf.exam.core.enums.PayTypeEnum;
import com.yf.exam.core.enums.RefundStatusEnum;
import com.yf.exam.core.enums.WeChatTradeStateEnum;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.core.utils.RedisDistributedLockUtils;
import com.yf.exam.core.utils.http.HttpUtils;
import com.yf.exam.modules.applet.service.WeChatPayNotifyService;
import com.yf.exam.modules.applet.vo.WeChatPayNotifyPlaintextVO;
import com.yf.exam.modules.exam.entity.ExamRegistration;
import com.yf.exam.modules.exam.mapper.ExamRegistrationMapper;
import com.yf.exam.modules.payment.entity.PaymentInfo;
import com.yf.exam.modules.payment.entity.RefundInfo;
import com.yf.exam.modules.payment.mapper.PaymentInfoMapper;
import com.yf.exam.modules.payment.mapper.RefundInfoMapper;
import com.yf.exam.modules.payment.vo.WeChatRefundNotifyVO;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* @Description 微信支付回调业务层实现类
* @Author 纪寒
* @Date 2022-10-20 14:00:52
* @Version 1.0
*/
@Slf4j
@Service
public class WeChatPayNotifyServiceImpl implements WeChatPayNotifyService {
@Resource
private XylWeChatPaymentConfig xylWeChatPaymentConfig;
@Resource
private ExamRegistrationMapper examRegistrationMapper;
/*@Resource
private GoodsOrderMapper goodsOrderMapper;
@Resource
private AppointmentOrderMapper appointmentOrderMapper;
@Resource
private YlypWeChatPaymentConfig ylypWeChatPaymentConfig;*/
@Resource
private PaymentInfoMapper paymentInfoMapper;
@Resource
private RedisDistributedLockUtils redisDistributedLockUtils;
@Resource(name = "transactionManager")
private DataSourceTransactionManager transactionManager;
@Resource
private RefundInfoMapper refundInfoMapper;
@Resource(name = "xylVerifier")
private Verifier xylVerifier;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 新医路账户标识
*/
private static final String XINYILU_ACCOUNT = "BAIXING";
/**
* 新医路账户标识
*/
private static final String YILUYOUPIN_ACCOUNT = "YILUYOUPIN";
/**
* 支付回调标识
*/
private static final String PAY = "PAY";
/**
* 退款回调标识
*/
private static final String REFUND = "REFUND";
/**
* 新医路支付回调接口
*
* @param request 请求信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
*/
@Override
public String xylWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("新医路微信支付回调开始执行...........");
return weChatPayNotifyInfo(request, response, PAY);
}
/**
* 新医路退款回调接口
*
* @param request 请求信息
* @param response 响应信息
* @return 应答信息避免微信平台重复发送回调通知
* @throws Exception 异常信息
*/
@Override
public String xylWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("新医路微信退款回调开始执行...........");
return weChatPayNotifyInfo(request, response, REFUND);
}
/**
* 微信支付回调通知公共方法
*
* @param request 请求信息
* @param response 响应信息
* @param refundAndPaymentFlag 支付和回调标识
* @return 返回信息
* @throws Exception 异常信息
*/
private String weChatPayNotifyInfo(HttpServletRequest request, HttpServletResponse response,
String refundAndPaymentFlag) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
//请求体
String body = HttpUtils.readRequestData(request);
if (StringUtils.isBlank(body)) {
log.error("微信回调通知失败,请求体 ====> {}", body);
response.setStatus(500);
resultMap.put("code", "ERROR");
resultMap.put("message", "通知验签失败");
return JSON.toJSONString(resultMap);
}
log.info("微信通知的请求体信息 ===> {}", body);
//验证签名
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String signature = request.getHeader("Wechatpay-Signature");
String serialNo = request.getHeader("Wechatpay-Serial");
//构建request传入必要参数
Notification notification = null;
NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNo)
.withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build();
NotificationHandler handler = new NotificationHandler(xylVerifier, xylWeChatPaymentConfig.getXylPaymentKey().getBytes(StandardCharsets.UTF_8));
notification = handler.parse(notificationRequest);
if (Objects.isNull(notification)) {
log.error("微信通知验签失败,请求体 ====> {}", body);
response.setStatus(500);
resultMap.put("code", "ERROR");
resultMap.put("message", "通知验签失败");
return JSON.toJSONString(resultMap);
}
//密文随机串附件数据
String ciphertext = StringUtils.isBlank(notification.getResource().getCiphertext()) ? "" : notification.getResource().getCiphertext();
String nonceTwo = StringUtils.isBlank(notification.getResource().getNonce()) ? "" : notification.getResource().getNonce();
String associatedData = StringUtils.isBlank(notification.getResource().getAssociatedData()) ? "" : notification.getResource().getAssociatedData();
//解密密文
String plainText = "";
AesUtil aesUtil = new AesUtil(xylWeChatPaymentConfig.getXylPaymentKey().getBytes(StandardCharsets.UTF_8));
plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonceTwo.getBytes(StandardCharsets.UTF_8), ciphertext);
if (StringUtils.isBlank(plainText)) {
response.setStatus(500);
resultMap.put("code", "ERROR");
resultMap.put("message", "解密失败!");
return JSON.toJSONString(resultMap);
}
//记录支付日志和修改订单状态
if (Constants.PAY_NOTIFY.equals(refundAndPaymentFlag)) {
this.processPaymentInfo(plainText);
}
//修改订单状态
if (Constants.REFUND_NOTIFY.equals(refundAndPaymentFlag)) {
this.processRefundInfo(plainText);
}
response.setStatus(200);
resultMap.put("code", "SUCCESS");
resultMap.put("message", "成功");
log.info("微信回调方法执行完成!");
return JSON.toJSONString(resultMap);
}
/**
* 记录支付信息和修改订单状态
*
* @param plainText 支付验签以后的通知内容
*/
private void processPaymentInfo(String plainText) {
log.info("支付回调明文 ====> {}", plainText);
//密文信息随机串附件数据
WeChatPayNotifyPlaintextVO notifyPlaintext = JSONObject.parseObject(plainText, WeChatPayNotifyPlaintextVO.class);
if (StringUtils.isBlank(notifyPlaintext.getOutTradeNo())) {
return;
}
//尝试获取锁
String orderNoLockKey = Constants.WE_CHAT_NOTIFY_KEY + notifyPlaintext.getOutTradeNo();
boolean tryLock = redisDistributedLockUtils.tryLock(orderNoLockKey, 5);
if (!tryLock) {
return;
}
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
//记录商品订单支付日志和修改订单状态
ExamRegistration examRegistration = examRegistrationMapper.selectById(notifyPlaintext.getOutTradeNo());
if (Objects.nonNull(examRegistration)) {
insertPaymentInfo(notifyPlaintext, examRegistration, plainText);
if (WeChatTradeStateEnum.SUCCESS.getInfo().equals(notifyPlaintext.getTradeState())) {
examRegistrationMapper.updatePaymentStateById(GooodsOrderStatusEnum.PAY.getInfo(), examRegistration.getId());
} else {
log.info("商品订单微信订单状态异常,订单状态为 ====> {}", notifyPlaintext.getTradeState());
}
transactionManager.commit(transactionStatus);
return;
}
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("微信支付回调失败,失败原因:{}", e.getMessage());
throw e;
} finally {
redisDistributedLockUtils.unlock(orderNoLockKey);
log.info("支付回调释放锁完成...........");
}
}
/**
* 记录支付日志信息
*
* @param notifyPlaintext 回调信息
* @param examRegistration 订单信息
* @param plainText 回调明文
*/
private void insertPaymentInfo(WeChatPayNotifyPlaintextVO notifyPlaintext, ExamRegistration examRegistration,
String plainText) {
//记录支付日志
int paymentInfoCount = paymentInfoMapper.getByRegistrationId(notifyPlaintext.getOutTradeNo());
if (paymentInfoCount <= 0) {
//构建支付订单信息
PaymentInfo paymentInfo = buildPaymentInfo(notifyPlaintext, examRegistration, plainText);
int insertCount = paymentInfoMapper.insertPaymentInfo(paymentInfo);
if (insertCount <= 0) {
log.error("记录支付日志出错,参数为 ====> [{}]", paymentInfo);
throw new ServiceException("记录支付日志出错,请联系管理员!");
}
}
}
/**
* 构建支付信息参数
*
* @param notifyPlaintext 通知明文信息
* @param examRegistration 商品订单信息
*/
private PaymentInfo buildPaymentInfo(WeChatPayNotifyPlaintextVO notifyPlaintext, ExamRegistration examRegistration,
String plainText) {
PaymentInfo paymentInfo = new PaymentInfo();
if (Objects.nonNull(examRegistration)) {
paymentInfo.setUserId(Objects.isNull(examRegistration.getUserId()) ? null : examRegistration.getUserId());
paymentInfo.setExamRegistrationId(StringUtils.isBlank(examRegistration.getId()) ? "" : examRegistration.getId());
//paymentInfo.setPayChannel(StringUtils.isBlank(examRegistration.getOrderChannel()) ? "" : examRegistration.getOrderChannel());
}
paymentInfo.setTransactionNo(StringUtils.isBlank(notifyPlaintext.getTransactionId()) ? "" : notifyPlaintext.getTransactionId());
paymentInfo.setPayPrice(BigDecimal.valueOf(Objects.isNull(notifyPlaintext.getAmount().getPayerTotal()) ? 0 : notifyPlaintext.getAmount().getPayerTotal()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_DOWN));
paymentInfo.setPayType(PayTypeEnum.WECHAT_PAY.getInfo());
paymentInfo.setWechatTradeState(StringUtils.isBlank(notifyPlaintext.getTradeState()) ? "" : notifyPlaintext.getTradeState());
paymentInfo.setPayNotifyContent(plainText);
paymentInfo.setPayTime(LocalDateTime.parse(StringUtils.isBlank(notifyPlaintext.getSuccessTime()) ? "" : notifyPlaintext.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME));
paymentInfo.setPaymentMerchantType("WEISHENGXUEHUI");
paymentInfo.setDelFlag(0);
paymentInfo.setCreateTime(new Date());
return paymentInfo;
}
/**
* 修改订单状态以及减少库存信息
*
* @param plainText 支付验签以后的通知内容
*/
private void processRefundInfo(String plainText) {
log.info("退款回调明文 =====> {}", plainText);
//将明文信息转为实体类映射
WeChatRefundNotifyVO refundNotifyVO = JSONObject.parseObject(plainText, WeChatRefundNotifyVO.class);
if (StringUtils.isBlank(refundNotifyVO.getOutTradeNo())) {
return;
}
//尝试获取锁
String orderNoLockKey = Constants.WE_CHAT_REFUND_KEY + refundNotifyVO.getOutTradeNo();
boolean tryLock = redisDistributedLockUtils.tryLock(orderNoLockKey, 5);
if (!tryLock) {
return;
}
//手动开启事务
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
RedisAtomicLong redisAtomicLong = null;
try {
//根据订单编号查询订单信息
ExamRegistration examRegistration = examRegistrationMapper.selectById(refundNotifyVO.getOutTradeNo());
if (Objects.isNull(examRegistration)) {
return;
}
//修改商品订单状态
if (Objects.nonNull(examRegistration)) {
updateGoodsInfo(examRegistration, plainText, refundNotifyVO);
}
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("微信退款回调通知失败,失败信息为:{}", e.getMessage());
if (Objects.nonNull(redisAtomicLong) && redisAtomicLong.get() > 0) {
//出现异常减一
redisAtomicLong.decrementAndGet();
}
throw e;
} finally {
redisDistributedLockUtils.unlock(orderNoLockKey);
log.info("退款回调释放锁完成.............");
}
}
/**
* 处理商品订单信息
*
* @param examRegistration 订单信息
* @param plainText 明文
* @param refundNotifyVO 退款回调信息
*/
private void updateGoodsInfo(ExamRegistration examRegistration, String plainText, WeChatRefundNotifyVO refundNotifyVO) {
if (RefundStatusEnum.SUCCESS.getInfo().equals(refundNotifyVO.getRefundStatus())
&& StringUtils.isNotBlank(examRegistration.getPaymentState())
&& !(GooodsOrderStatusEnum.REFUNDED.getInfo().equals(examRegistration.getPaymentState()))) {
int updateCount = examRegistrationMapper.updatePaymentStateById(GooodsOrderStatusEnum.REFUNDED.getInfo(), examRegistration.getPaymentState());
if (updateCount < 1) {
log.info("微信退款通知,修改商品单状态失败,订单编号 =====> {}", refundNotifyVO.getOutTradeNo());
}
//修改退款信息
RefundInfo refundInfo = buildRefundInfo(plainText, refundNotifyVO);
int refundCount = refundInfoMapper.updateRefundInfoByNo(refundInfo);
if (refundCount < 1) {
log.info("微信退款通知,记录商品订单微信退款单信息失败,订单编号 =====> {}", refundNotifyVO.getOutTradeNo());
}
}
if (RefundStatusEnum.CLOSED.getInfo().equals(refundNotifyVO.getRefundStatus())) {
log.info("当前商品退款单已经关闭,订单号 ====> {}, 退款单号 =====> {}", refundNotifyVO.getOutTradeNo(), refundNotifyVO.getOutRefundNo());
}
if (RefundStatusEnum.ABNORMAL.getInfo().equals(refundNotifyVO.getRefundStatus())) {
log.info("商品订单退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款,订单号 ====> {}, 退款单号 =====> {}", refundNotifyVO.getOutTradeNo(), refundNotifyVO.getOutRefundNo());
}
}
/**
* 构建退款单信息
*
* @param plainText 退款通知解密的明文信息
* @param refundNotifyVO 通知参数
* @return 结果
*/
private RefundInfo buildRefundInfo(String plainText, WeChatRefundNotifyVO refundNotifyVO) {
RefundInfo refundInfo = new RefundInfo();
refundInfo.setWechatRefundStatus(StringUtils.isBlank(refundNotifyVO.getRefundStatus()) ? "" : refundNotifyVO.getRefundStatus());
refundInfo.setSuccessTime(LocalDateTime.parse(StringUtils.isBlank(refundNotifyVO.getSuccessTime()) ? "" : refundNotifyVO.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME));
refundInfo.setRefundNotifyContent(plainText);
refundInfo.setExamRegistrationId(StringUtils.isBlank(refundNotifyVO.getOutTradeNo()) ? "" : refundNotifyVO.getOutTradeNo());
refundInfo.setOutRefundNo(StringUtils.isBlank(refundNotifyVO.getOutRefundNo()) ? "" : refundNotifyVO.getOutRefundNo());
return refundInfo;
}
}

View File

@ -0,0 +1,449 @@
package com.yf.exam.modules.applet.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.yf.exam.config.AppletChatConfig;
import com.yf.exam.config.WeChatPaymentUrlConfig;
import com.yf.exam.config.XylWeChatPaymentConfig;
import com.yf.exam.constant.Constants;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.core.enums.GooodsOrderStatusEnum;
import com.yf.exam.core.enums.WeChatTradeStateEnum;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.applet.dto.PaymentDTO;
import com.yf.exam.modules.applet.service.WeChatPaymentService;
import com.yf.exam.modules.applet.vo.OrderStatusInfoVO;
import com.yf.exam.modules.applet.vo.WeChatAppletSignVO;
import com.yf.exam.modules.applet.vo.WeChatQueryOrderVO;
import com.yf.exam.modules.exam.entity.ExamRegistration;
import com.yf.exam.modules.exam.mapper.ExamRegistrationMapper;
import com.yf.exam.modules.utils.WeChatUtil;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;
/**
* @Description 微信小程序和App支付业务层实现类
* @Author 纪寒
* @Date 2022-10-18 15:02:40
* @Version 1.0
*/
@Service
@Slf4j
public class WeChatPaymentServiceImpl implements WeChatPaymentService {
@Resource
private ExamRegistrationMapper examRegistrationMapper;
@Resource
private AppletChatConfig appletChatConfig;
@Resource
private XylWeChatPaymentConfig xylWeChatPaymentConfig;
@Resource(name = "xinYiLuWeChatPayClient")
private CloseableHttpClient xinYiLuWeChatPayClient;
@Resource
private WeChatPaymentUrlConfig weChatPaymentUrlConfig;
@Resource
private RedisTemplate<String, String> redisTemplate;
@Resource
private WeChatUtil weChatUtil;
@Resource
private AppletChatConfig AppletChatConfig;
/**
* 山东省公共卫生学会支付商品描述
*/
private static final String SD_GONGGONGWEISHENG_XUEHUI_DESCRIPTION = "山东省公共卫生学会";
/**
* JsApi下单成功状态码
*/
private static final int HAVE_BODY_SUCCESS_CODE = 200;
/**
* JsApi下单成功状态码
*/
private static final int NO_HAVE_BODY_SUCCESS_CODE = 204;
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* 签名方式
*/
private static final String SIGN_TYPE = "RSA";
/**
* 支付回调接口地址
*/
private static final String XINYILU_WE_CHAT_NOTIFY_URL = "/examApplet/weChatPayment/xylWeChatPayNotify";
/**
* 微信小程序支付接口
*
* @param paymentDTO 输入参数
* @return 支付结果
* @throws Exception 常信息
*/
@Override
public AjaxResult appletPay(PaymentDTO paymentDTO) throws Exception {
//从Redis中获取当前prepay_id的值
String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getId());
if (StringUtils.isBlank(prepayId)) {
//根据订单编号查询商品订单信息
ExamRegistration examRegistration = examRegistrationMapper.selectById(paymentDTO.getId());
if (Objects.isNull(examRegistration) || StringUtils.isEmpty(examRegistration.getPaymentState())) {
return AjaxResult.error("未查询到当前订单信息,请选择正确的订单信息!");
}
//判断订单状态
if (!examRegistration.getPaymentState().equals(GooodsOrderStatusEnum.WAIT_PAY.getInfo())) {
return AjaxResult.error("订单状态异常,无法支付,请联系管理员!");
}
//构建JsApi下单参数
String jsApiParams = this.buildGoodsOrderJsApiParams(paymentDTO, examRegistration);
//请求微信JsApi下单接口获取prepay_id的值
prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId);
} else {
prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getId());
}
//构建小程序调起支付参数信息
return AjaxResult.success(getSignInfo(prepayId));
}
/**
* 取消订单接口
*
* @param orderNo 点单编号
* @return 取消结果信息
*/
@Transactional(rollbackFor = Exception.class)
@Override
public AjaxResult cancelOrderInfo(String orderNo) throws Exception {
//根据订单编号查询订单信息
ExamRegistration examRegistration = examRegistrationMapper.selectById(orderNo);
if (Objects.isNull(examRegistration)) {
return AjaxResult.error("当前订单信息不存在,请重新选择!");
}
//商品订单信息
if (StringUtils.isBlank(examRegistration.getPaymentState())
|| !GooodsOrderStatusEnum.WAIT_PAY.getInfo().equals(examRegistration.getPaymentState())) {
log.info("订单状态为:====> {}", StringUtils.isBlank(examRegistration.getPaymentState()) ? "不存在" : examRegistration.getPaymentState());
return AjaxResult.error("当前订单状态异常,请联系管理员!");
}
//下单10分种以后才可以取消订单
//LocalDateTime tenAfterTime = goodsOrderInfo.getOrderTime().plusMinutes(10);
//if (LocalDateTime.now().isBefore(tenAfterTime)) {
// return AjaxResult.error("10分钟以后才能取消当前订单请耐心等待");
//}
//调用微信查单接口确定微信系统中订单的状态
OrderStatusInfoVO orderStatusInfoVO = new OrderStatusInfoVO();
OrderStatusInfoVO vo = this.queryGoodsOrderStatus(examRegistration.getId(), orderStatusInfoVO);
if (BooleanUtils.isTrue(vo.getPayFlag())) {
//关闭微信系统的订单和修改本地系统中的订单状态
int statusCode = this.closeWeChatOrderInfo(examRegistration.getId());
if (!(statusCode == HAVE_BODY_SUCCESS_CODE || statusCode == NO_HAVE_BODY_SUCCESS_CODE)) {
return AjaxResult.error("取消订单失败,请联系管理员!");
}
// 修改本地订单状态
examRegistrationMapper.updatePaymentStateById(GooodsOrderStatusEnum.CANCEL.getInfo(), orderNo);
}
return AjaxResult.success("取消订单成功!");
}
/**
* 查询订单状态信息
*
* @param orderNo 订单编号
* @return 结果信息
* @throws Exception 异常信息
*/
@Override
public AjaxResult getOrderStatusInfo(String orderNo) throws Exception {
OrderStatusInfoVO vo = new OrderStatusInfoVO();
//根据订单编号查询订单信息
ExamRegistration examRegistration = examRegistrationMapper.selectById(orderNo);
if (Objects.isNull(examRegistration)) {
// 订单都不存在说明没有下过订单可以支付
vo.setPayFlag(true);
return AjaxResult.success(vo);
}
//商品订单不为空调用微信查单接口确认订单状态
if (Objects.nonNull(examRegistration)) {
OrderStatusInfoVO order = new OrderStatusInfoVO();
vo = this.queryGoodsOrderStatus(examRegistration.getId(), order);
}
return AjaxResult.success(vo);
}
/**
* 微信关闭订单方法
*
* @param orderNo 订单编号
* @return 状态码
* @throws Exception 异常信息
*/
@Override
public int closeWeChatOrderInfo(String orderNo) throws Exception {
log.info("调用微信关闭订单接口,订单号 ====> {}", orderNo);
String closeUrl = String.format(weChatPaymentUrlConfig.getCloseOrderUrl(), orderNo);
HttpPost httpPost = new HttpPost(closeUrl);
Map<String, Object> paramMap = new HashMap<>();
String jsonParams;
CloseableHttpResponse response = null;
paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
jsonParams = JSON.toJSONString(paramMap);
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
response = xinYiLuWeChatPayClient.execute(httpPost);
try {
if (response == null) {
throw new ServiceException("获取微信HttpClient对象失败请联系管理员");
}
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case HAVE_BODY_SUCCESS_CODE:
log.info("微信关闭订单成功!");
break;
case NO_HAVE_BODY_SUCCESS_CODE:
log.info("微信关闭订单成功,无返回体!");
break;
default:
throw new ServiceException("微信关闭订单失败!");
}
return statusCode;
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 查询预约服务订单状态
*
* @param orderNo 订单编号
* @param vo 返回值信息
*/
@Override
public OrderStatusInfoVO queryAppointmentOrderStatus(String orderNo, OrderStatusInfoVO vo) {
log.info("调用微信查询订单接口,订单编号 ====> {}", orderNo);
String requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo);
requestUrl = requestUrl.concat("?mchid=").concat(xylWeChatPaymentConfig.getXylMchId());
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader("Accept", "application/json");
try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpGet)) {
//响应体
String responseBody = EntityUtils.toString(response.getEntity());
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
WeChatQueryOrderVO weChatQueryOrderVO = JSONObject.parseObject(responseBody, WeChatQueryOrderVO.class);
//是否可以支付标识
vo.setPayFlag(StringUtils.isNotBlank(weChatQueryOrderVO.getTradeState())
&& WeChatTradeStateEnum.NOTPAY.getInfo().equals(weChatQueryOrderVO.getTradeState()));
//支付状态
vo.setTradeStatus(weChatQueryOrderVO.getTradeState());
//返回值所有参数
vo.setWeChatQueryOrderVO(weChatQueryOrderVO);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
log.warn("请求微信查单接口成功,无返回信息!");
} else {
throw new ServiceException("调用微信查询订单接口异常,响应码为:" + statusCode + ", 查询订单返回结果 = " + responseBody);
}
return vo;
} catch (Exception e) {
log.error("请求微信查单接口异常,异常信息为 ====> {}", e.getMessage());
throw new ServiceException("查询预约订单状态失败,请联系管理员!");
}
}
/**
* 查询商品订单状态
*
* @param orderNo 订单编号
* @param vo 返回值信息
*/
@Override
public OrderStatusInfoVO queryGoodsOrderStatus(String orderNo, OrderStatusInfoVO vo) throws Exception {
log.info("调用微信查询订单接口,订单编号 ====> {}", orderNo);
String requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo).concat("?mchid=").concat(xylWeChatPaymentConfig.getXylMchId());
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader("Accept", "application/json");
CloseableHttpResponse response = null;
try {
response = xinYiLuWeChatPayClient.execute(httpGet);
if (response == null) {
throw new ServiceException("获取微信HttpClient对象失败请联系管理员");
}
//响应体
String responseBody = EntityUtils.toString(response.getEntity());
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
WeChatQueryOrderVO weChatQueryOrderVO = JSONObject.parseObject(responseBody, WeChatQueryOrderVO.class);
//是否可以支付标识
vo.setPayFlag(StringUtils.isNotBlank(weChatQueryOrderVO.getTradeState())
&& WeChatTradeStateEnum.NOTPAY.getInfo().equals(weChatQueryOrderVO.getTradeState()));
//支付状态
vo.setTradeStatus(weChatQueryOrderVO.getTradeState());
//返回值所有信息
vo.setWeChatQueryOrderVO(weChatQueryOrderVO);
}
if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
log.warn("请求微信查单接口成功,无返回信息!");
}
return vo;
} catch (Exception e) {
log.error("请求微信查单接口异常,异常信息为 ====> {}", e.getMessage());
throw new ServiceException("查询商品订单状态失败,请联系管理员!");
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 构建商品订单JsApi微信下单参数
*
* @param paymentDTO 前端传值
* @param examRegistration 订单信息
* @return 下单参数json串
*/
private String buildGoodsOrderJsApiParams(PaymentDTO paymentDTO, ExamRegistration examRegistration) {
Map<String, Object> paramMap = new LinkedHashMap<>();
paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
paramMap.put("out_trade_no", examRegistration.getId());
//应用id此处为小程序id
paramMap.put("appid", appletChatConfig.getAppletId());
paramMap.put("description", SD_GONGGONGWEISHENG_XUEHUI_DESCRIPTION);
paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL);
Map<String, Object> amountParamMap = new LinkedHashMap<>();
//总金额
int totalPrice = paymentDTO.getPaymentPrice().multiply(BigDecimal.valueOf(100)).intValue();
amountParamMap.put("total", totalPrice);
//货币类型
amountParamMap.put("currency", "CNY");
paramMap.put("amount", amountParamMap);
//支付者信息
Map<String, Object> payerParamMap = new LinkedHashMap<>();
payerParamMap.put("openid", paymentDTO.getOpenid());
paramMap.put("payer", payerParamMap);
return JSON.toJSONString(paramMap);
}
/**
* 请求JsApi下单接口方法
*
* @param jsApiParams 下单参数
* @param paymentDTO 输入参数
* @param prepayId JsAp下单接口返回值
* @return 结果
* @throws Exception 异常信息
*/
private String requestJsApiInterface(String jsApiParams, PaymentDTO paymentDTO, String prepayId) throws Exception {
log.info("JsApi请求下单参数 ====> {}", jsApiParams);
StringEntity stringEntity = new StringEntity(jsApiParams, StandardCharsets.UTF_8);
stringEntity.setContentType("application/json");
HttpPost httpPost = new HttpPost(weChatPaymentUrlConfig.getJsapiPalceOrderUrl());
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = null;
String payAccount = "";
try {
response = xinYiLuWeChatPayClient.execute(httpPost);
payAccount = "山东省公共卫生学会";
//处理响应结果
if (Objects.isNull(response)) {
log.error("JsApi下单接口执行错误 执行账户为 ====> {}", payAccount);
throw new ServiceException("JsApi下单接口执行异常请联系管理员");
}
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String result = EntityUtils.toString(response.getEntity());
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
//请求成功取出prepay_id的值并放入Redis缓存中prepay_id的有效期为两小时此处缓存一个半小时
Map<String, Object> resultMap = JSONObject.parseObject(result);
prepayId = resultMap.getOrDefault("prepay_id", "").toString();
redisTemplate.opsForValue().set(Constants.PREPAY_ID_KEY + paymentDTO.getId(), prepayId, 5400, TimeUnit.SECONDS);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
//请求成功无返回体
log.info("JsApi下单成功无返回值信息");
} else {
//请求失败
log.error("JsApi下单失败失败原因为 ====> {}", result);
throw new ServiceException("JsApi下单失败失败原因为" + result);
}
} catch (Exception e) {
log.error("JsApi下单失败失败原因 =====> {}", e.getMessage());
throw new ServiceException("JsApi下单失败请联系管理员");
} finally {
if (response != null) {
response.close();
}
}
return prepayId;
}
/**
* 构建微信小程序调起支付参数设置
*
* @param prepayId jsapi下单返回参数信息
* @return 参数信息
* @throws Exception 异常信息
*/
private WeChatAppletSignVO getSignInfo(String prepayId) throws Exception {
String appId = appletChatConfig.getAppletId();
//随机字符串
String nonceStr = UUID.randomUUID().toString().replace("-", "");
//时间戳
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//获取商户私钥
PrivateKey privateKey = null;
privateKey = weChatUtil.getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath());
if (privateKey == null) {
throw new ServiceException("获取商户私钥失败,请联系管理员!");
}
//计算签名信息
prepayId = "prepay_id=" + prepayId;
String signatureStr = Stream.of(appId, timestamp, nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
log.info("计算签名认证信息 =====> {}", signatureStr);
Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
sign.initSign(privateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
String paySign = Base64Utils.encodeToString(sign.sign());
return WeChatAppletSignVO.builder().appId(appId).timeStamp(timestamp).nonceStr(nonceStr).prepayId(prepayId)
.signType(SIGN_TYPE).paySign(paySign).build();
}
}

View File

@ -0,0 +1,356 @@
package com.yf.exam.modules.applet.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.yf.exam.config.WeChatPaymentUrlConfig;
import com.yf.exam.config.XylWeChatPaymentConfig;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.core.enums.ConfirmRefundStatusEnum;
import com.yf.exam.core.enums.RefundStatusEnum;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.applet.dto.RefundDTO;
import com.yf.exam.modules.applet.service.WeChatRefundService;
import com.yf.exam.modules.exam.entity.ExamRegistration;
import com.yf.exam.modules.exam.mapper.ExamRegistrationMapper;
import com.yf.exam.modules.payment.entity.RefundInfo;
import com.yf.exam.modules.payment.mapper.RefundInfoMapper;
import com.yf.exam.modules.payment.vo.WeChatRefundInfoVO;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description 微信退款业务层实现类
* @Author 纪寒
* @Date 2022-10-24 15:23:16
* @Version 1.0
*/
@Slf4j
@Service
public class WeChatRefundServiceImpl implements WeChatRefundService {
@Resource
private ExamRegistrationMapper examRegistrationMapper;
@Resource
private XylWeChatPaymentConfig xylWeChatPaymentConfig;
@Resource(name = "xinYiLuWeChatPayClient")
private CloseableHttpClient xinYiLuWeChatPayClient;
@Resource
private WeChatPaymentUrlConfig weChatPaymentUrlConfig;
@Resource
private RefundInfoMapper refundInfoMapper;
/*@Resource
private GoodsStockService goodsStockService;
@Resource
private PatientIntegralChangeMapper patientIntegralChangeMapper;
@Resource
private PatientInfoMapper patientInfoMapper;*/
/**
* 成功状态码
*/
private static final int HAVE_BODY_SUCCESS_CODE = 200;
/**
* 成功状态码
*/
private static final int NO_HAVE_BODY_SUCCESS_CODE = 204;
/**
* 新医路退款回调接口地址
*/
private static final String XINYILU_WE_CHAT_REFUND_URL = "/examApplet/weChatPayment/xylWeChatRefundNotify";
/**
* 微信确认退款接口
*
* @param refundDTO 退款参数
* @return 退款申请结果
* @throws Exception 异常信息
*/
@Transactional(rollbackFor = Exception.class)
@Override
public AjaxResult weChatRefundOrderApply(RefundDTO refundDTO) throws Exception {
//取消商品订单信息修改本地订单状态并调用微信退款申请接口进行退款处理
ExamRegistration examRegistration = examRegistrationMapper.selectById(refundDTO.getId());
if (Objects.nonNull(examRegistration)) {
AjaxResult ajaxResult = judgeRefundInfo(examRegistration.getOrderTime(), refundDTO.getRefundPrice(), examRegistration.getExamFee());
if (Objects.nonNull(ajaxResult)) {
return ajaxResult;
}
//判断订单状态是否确认退款
if (StringUtils.isBlank(examRegistration.getConfirmRefundStatus())
|| !(ConfirmRefundStatusEnum.NOT_CONFIRM.getInfo().equals(examRegistration.getConfirmRefundStatus()))) {
return AjaxResult.error("当前商品订单未进行退款确认,请先进行确认!");
}
/*if (StringUtils.isBlank(examRegistration.getPaymentState())
|| !GooodsOrderStatusEnum.WAIT_REFUND.getInfo().equals(examRegistration.getPaymentState())
|| GooodsOrderStatusEnum.REFUNDED.getInfo().equals(examRegistration.getPaymentState())) {
return AjaxResult.error("当前商品订单非退款中或者已退款,无法进行退款处理!");
}*/
//调用微信申请退款接口发起退款申请
String userId = Objects.isNull(examRegistration.getUserId()) ? "" : examRegistration.getUserId();
String outRefundNo = fillZeroByUserId(userId, 5) + System.nanoTime();
String refundParam = buildRefundParam(examRegistration, refundDTO, outRefundNo);
this.applyWeRefund(refundParam, examRegistration, userId, refundDTO);
return AjaxResult.success();
}
return AjaxResult.success();
}
/**
* 补0公共方法
*
* @param userId 考生id
* @param length 指定长度
* @return 部位长度
*/
private String fillZeroByUserId(String userId, int length) {
if (StringUtils.length(userId) < length) {
userId = String.format("%0" + length + "d", userId);
}
return userId;
}
/**
* 调用微信查询单笔退款接口查询预约订单状态信息
*
* @param refundNo 退款单号
* @return 退款信息
*/
/*@Override
public WeChatRefundInfoVO queryAppointmentOrderRefundStatus(String refundNo) {
WeChatRefundInfoVO weChatRefundInfoVO = new WeChatRefundInfoVO();
log.info("调用微信查询单笔退款订单接口查询预约订单信息,预约订单编号 ====> {}", refundNo);
String requestUrl = String.format(weChatPaymentUrlConfig.getRefundQueryOrderUrl(), refundNo);
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader("Accept", "application/json");
try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpGet)) {
//请求体
String body = EntityUtils.toString(response.getEntity());
//请求状态
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
weChatRefundInfoVO = JSONObject.parseObject(body, WeChatRefundInfoVO.class);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
log.warn("请求微信查询单笔退款接口成功,无返回信息!");
} else {
throw new ServiceException("调用微信查询单笔退款接口异常,响应码为:" + statusCode + ", 查询退款返回结果 = " + body);
}
return weChatRefundInfoVO;
} catch (Exception e) {
log.error("请求微信查询单笔退款接口异常,异常信息为 ====> {}", e.getMessage());
throw new ServiceException("查询预约订单状态失败,请联系管理员!");
}
}*/
/**
* 调用微信查询单笔退款接口查询商品订单状态信息
*
* @param refundNo 退单编号
* @param buySource 购买来源
* @return 退款信息
* @throws Exception 异常信息
*/
/*@Override
public WeChatRefundInfoVO queryGoodsOrderRefundStatus(String refundNo, String buySource) throws Exception {
WeChatRefundInfoVO weChatRefundInfoVO = new WeChatRefundInfoVO();
log.info("调用微信查询单笔退款订单接口查询商品订单信息,商品订单编号 ====> {}", refundNo);
String requestUrl = String.format(weChatPaymentUrlConfig.getRefundQueryOrderUrl(), refundNo);
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader("Accept", "application/json");
CloseableHttpResponse response = null;
try {
if (BuySourceEnum.NURSE_STATION.getInfo().equals(buySource)) {
response = xinYiLuWeChatPayClient.execute(httpGet);
}
if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(buySource)
|| BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(buySource) || BuySourceEnum.TRAINING.getInfo().equals(buySource)) {
response = yiLuYouPinWeChatPayClient.execute(httpGet);
}
if (response == null) {
throw new ServiceException("获取微信HttpClient对象失败请联系管理员");
}
//响应体
String body = EntityUtils.toString(response.getEntity());
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
weChatRefundInfoVO = JSONObject.parseObject(body, WeChatRefundInfoVO.class);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
log.warn("请求微信查询单笔退款接口成功,无返回信息!");
} else {
throw new ServiceException("调用微信查询单笔退款接口异常,响应码为:" + statusCode + ", 查询退款返回结果 = " + body);
}
return weChatRefundInfoVO;
} catch (Exception e) {
log.error("请求微信查询单笔退款接口,异常信息为 ====> {}", e.getMessage());
throw new ServiceException("查询商品订单状态失败,请联系管理员!");
} finally {
if (response != null) {
response.close();
}
}
}*/
/**
* 退款参数检验
*
* @param orderTime 下单时间
* @param refundPrice 退款金额
* @param totalPrice 支付总金额
* @return 返回结果信息
*/
private AjaxResult judgeRefundInfo(LocalDateTime orderTime, BigDecimal refundPrice, BigDecimal totalPrice) {
/*LocalDateTime localDateTime = orderTime.plusYears(1);
if (LocalDateTime.now().isAfter(localDateTime)) {
return AjaxResult.error("当前订单超过一年,无法进行退款处理!");
}*/
if (refundPrice.compareTo(totalPrice) > 0) {
return AjaxResult.error("当前退款金额大于订单支付金额,无法进行退款处理!");
}
return null;
}
/**
* 构建微信退款参数信息
*
* @param examRegistration 商品订单信息
* @param refundDTO 退款参数信息
* @return 退款申请Json串
*/
private String buildRefundParam(ExamRegistration examRegistration, RefundDTO refundDTO, String outRefundNo) {
Map<String, Object> paramMap = new LinkedHashMap<>();
//订单编号
paramMap.put("out_trade_no", refundDTO.getId());
//商户退款单号
paramMap.put("out_refund_no", outRefundNo);
//退款金额参数
Map<String, Object> amountMap = new LinkedHashMap<>();
//回调通知地址和退款金额预约订单使用新医路账户
if (Objects.nonNull(examRegistration)) {
//退款金额
amountMap.put("refund", refundDTO.getRefundPrice().multiply(BigDecimal.valueOf(100)).intValue());
//原始订单金额
amountMap.put("total", examRegistration.getExamFee().multiply(BigDecimal.valueOf(100)).intValue());
}
//退款回调地址
paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_REFUND_URL);
//货币类型
amountMap.put("currency", "CNY");
paramMap.put("amount", amountMap);
return JSON.toJSONString(paramMap);
}
/**
* 发起退款申请
*
* @param refundParam 请求参数
* @param goodsOrderInfo 商品订单信息
* @param userId 考生id
* @param refundDTO 申请信息
* @throws Exception 异常信息
*/
public void applyWeRefund(String refundParam,ExamRegistration goodsOrderInfo,
String userId, RefundDTO refundDTO) throws Exception {
log.info("申请退款请求参数 ===> {}", refundParam);
String requestUrl = weChatPaymentUrlConfig.getRefundApplyUrl();
HttpPost httpPost = new HttpPost(requestUrl);
StringEntity entity = new StringEntity(refundParam, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = null;
String refundMerchantType = "";
try {
response = xinYiLuWeChatPayClient.execute(httpPost);
refundMerchantType = "WEISHENGXUEHUI";
if (Objects.isNull(response)) {
throw new ServiceException("获取httpclient对象失败");
}
int statusCode = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(response.getEntity());
if (statusCode != HAVE_BODY_SUCCESS_CODE && statusCode != NO_HAVE_BODY_SUCCESS_CODE) {
throw new ServiceException("微信申请退款异常, 响应码:" + statusCode + ", 退款返回结果:" + body);
}
//将商品订单状态修改为已确认
/*if (Objects.nonNull(goodsOrderInfo)) {
examRegistrationMapper.updateGoodsConfirmRefundStatus(goodsOrderInfo.getOrderNo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo());
}*/
WeChatRefundInfoVO weChatRefundInfoVO = JSONObject.parseObject(body, WeChatRefundInfoVO.class);
//记录支付日志
int refundInfoCount = refundInfoMapper.getRefundInfoByexamRegistrationId(weChatRefundInfoVO.getOutTradeNo());
if (refundInfoCount <= 0) {
RefundInfo refundInfo = buildRefundInfo(userId, weChatRefundInfoVO, refundDTO, body, refundMerchantType);
int insertCunt = refundInfoMapper.insertRefundInfo(refundInfo);
if (insertCunt <= 0) {
throw new ServiceException("记录退款信息失败,请联系管理员!");
}
}
} catch (Exception e) {
log.error("微信申请退款接口出错,原因:{}", e.getMessage());
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 构建微信退款信息
*
* @param userId 会员id
* @param refundInfoVO 退款信息参数
* @param refundDTO 退款申请参数
* @param refundBody 返回体
* @param refundMerchantType 退款账户类型新医路或者医路优品
*/
private RefundInfo buildRefundInfo(String userId, WeChatRefundInfoVO refundInfoVO,
RefundDTO refundDTO, String refundBody, String refundMerchantType) {
RefundInfo refundInfo = new RefundInfo();
refundInfo.setUserId(userId);
refundInfo.setExamRegistrationId(refundInfoVO.getOutTradeNo());
refundInfo.setRefundNo(refundInfoVO.getRefundId());
refundInfo.setOutRefundNo(refundInfoVO.getOutRefundNo());
refundInfo.setTransactionNo(StringUtils.isBlank(refundInfoVO.getTransactionId()) ? "" : refundInfoVO.getTransactionId());
refundInfo.setRefundReason(StringUtils.isBlank(refundDTO.getRefundReason()) ? "" : refundDTO.getRefundReason());
refundInfo.setRefundType("WE_CHAT");
refundInfo.setWechatRefundStatus(RefundStatusEnum.PROCESSING.getInfo());
refundInfo.setOrderTotalPrice(BigDecimal.valueOf(refundInfoVO.getAmount().getTotal()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_DOWN));
refundInfo.setRefundPrice(BigDecimal.valueOf(refundInfoVO.getAmount().getRefund()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_DOWN));
refundInfo.setCurrency(refundInfoVO.getAmount().getCurrency());
refundInfo.setChannel(refundInfoVO.getChannel());
refundInfo.setUserreceivedaccount(refundInfoVO.getUserReceivedAccount());
refundInfo.setSuccessTime(StringUtils.isBlank(refundInfoVO.getSuccessTime()) ? null : LocalDateTime.parse(refundInfoVO.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME));
refundInfo.setApplyRefundReturnContent(refundBody);
refundInfo.setRefundMerchantType(refundMerchantType);
refundInfo.setDelFlag(0);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
refundInfo.setCreateTime(formatter.parse(refundInfoVO.getCreateTime()));
} catch (ParseException e) {
throw new RuntimeException(e);
}
return refundInfo;
}
}

View File

@ -0,0 +1,29 @@
package com.yf.exam.modules.applet.vo;
import java.io.Serializable;
import lombok.Data;
/**
* @Description 订单状态返回值实体类
* @Author 纪寒
* @Date 2022-10-21 10:47:20
* @Version 1.0
*/
@Data
public class OrderStatusInfoVO implements Serializable {
private static final long serialVersionUID = -8522003863208703215L;
/**
* 支付标识true可以支付false不可支付
*/
private Boolean payFlag;
/**
* 微信订单状态具体值参考TradeStatusEnum枚举
*/
private String tradeStatus;
/**
* 微信查单接口返回参数信息
*/
WeChatQueryOrderVO weChatQueryOrderVO;
}

View File

@ -1,4 +1,4 @@
package com.yf.exam.modules.payment.vo;
package com.yf.exam.modules.applet.vo;
import java.io.Serializable;
import lombok.Builder;

View File

@ -0,0 +1,277 @@
package com.yf.exam.modules.applet.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description 微信支付回调通知明文参数实体类
* @Author 纪寒
* @Date 2022-10-20 14:08:07
* @Version 1.0
*/
@NoArgsConstructor
@Data
public class WeChatPayNotifyPlaintextVO implements Serializable {
private static final long serialVersionUID = -3950813376227389879L;
/**
* 流水号
*/
@JsonProperty("transaction_id")
private String transactionId;
/**
* 金额信息
*/
@JsonProperty("amount")
private AmountDTO amount;
/**
* 商户号
*/
@JsonProperty("mchid")
private String mchid;
/**
* 交易状态枚举值
* SUCCESS支付成功
* REFUND转入退款
* NOTPAY未支付
* CLOSED已关闭
* REVOKED已撤销付款码支付
* USERPAYING用户支付中付款码支付
* PAYERROR支付失败(其他原因如银行返回失败)
*/
@JsonProperty("trade_state")
private String tradeState;
/**
* 付款银行
*/
@JsonProperty("bank_type")
private String bankType;
/**
* 优惠功能
*/
@JsonProperty("promotion_detail")
private List<PromotionDetailDTO> promotionDetail;
/**
* 支付完成时间
*/
@JsonProperty("success_time")
private String successTime;
/**
* 支付者
*/
@JsonProperty("payer")
private PayerDTO payer;
/**
* 商户订单号
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 商户号
*/
@JsonProperty("appid")
private String appid;
/**
* 交易状态描述
*/
@JsonProperty("trade_state_desc")
private String tradeStateDesc;
/**
* 交易类型枚举值
* JSAPI公众号支付
* NATIVE扫码支付
* APPAPP支付
* MICROPAY付款码支付
* MWEBH5支付
* FACEPAY刷脸支付
*/
@JsonProperty("trade_type")
private String tradeType;
/**
* 附加数据
*/
@JsonProperty("attach")
private String attach;
/**
* 场景信息
*/
@JsonProperty("scene_info")
private SceneInfoDTO sceneInfo;
/**
* 订单金额
*/
@NoArgsConstructor
@Data
public static class AmountDTO {
/**
* 用户支付金额
*/
@JsonProperty("payer_total")
private Integer payerTotal;
/**
* 总金额
*/
@JsonProperty("total")
private Integer total;
/**
* 货币类型
*/
@JsonProperty("currency")
private String currency;
/**
* 用户支付币种
*/
@JsonProperty("payer_currency")
private String payerCurrency;
}
/**
* 支付者
*/
@NoArgsConstructor
@Data
public static class PayerDTO {
/**
* 用户标识
*/
@JsonProperty("openid")
private String openid;
}
/**
* 场景信息
*/
@NoArgsConstructor
@Data
public static class SceneInfoDTO {
/**
* 商户端设备号
*/
@JsonProperty("device_id")
private String deviceId;
}
/**
* 优惠功能
*/
@NoArgsConstructor
@Data
public static class PromotionDetailDTO {
/**
* 优惠券面额
*/
@JsonProperty("amount")
private Integer amount;
/**
* 微信出资
*/
@JsonProperty("wechatpay_contribute")
private Integer wechatpayContribute;
/**
* 券ID
*/
@JsonProperty("coupon_id")
private String couponId;
/**
* 优惠范围
*/
@JsonProperty("scope")
private String scope;
/**
* 商户出资
*/
@JsonProperty("merchant_contribute")
private Integer merchantContribute;
/**
* 优惠名称
*/
@JsonProperty("name")
private String name;
/**
* 其他出资
*/
@JsonProperty("other_contribute")
private Integer otherContribute;
/**
* 优惠币种
*/
@JsonProperty("currency")
private String currency;
/**
* 活动ID
*/
@JsonProperty("stock_id")
private String stockId;
/**
* 单品列表
*/
@JsonProperty("goods_detail")
private List<GoodsDetailDTO> goodsDetail;
/**
* 单品列表
*/
@NoArgsConstructor
@Data
public static class GoodsDetailDTO {
/**
* 商品备注
*/
@JsonProperty("goods_remark")
private String goodsRemark;
/**
* 商品数量
*/
@JsonProperty("quantity")
private Integer quantity;
/**
* 商品优惠金额
*/
@JsonProperty("discount_amount")
private Integer discountAmount;
/**
* 商品编码
*/
@JsonProperty("goods_id")
private String goodsId;
/**
* 商品单价
*/
@JsonProperty("unit_price")
private Integer unitPrice;
}
}
}

View File

@ -0,0 +1,138 @@
package com.yf.exam.modules.applet.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description 微信查单接口返回值实体类
* @Author 纪寒
* @Date 2022-10-21 11:26:12
* @Version 1.0
*/
@NoArgsConstructor
@Data
public class WeChatQueryOrderVO implements Serializable {
private static final long serialVersionUID = 8467953500266751257L;
/**
* 返回金额信息
*/
@JsonProperty("amount")
private AmountDTO amount;
/**
* 应用id
*/
@JsonProperty("appid")
private String appid;
/**
* 附加数据信息
*/
@JsonProperty("attach")
private String attach;
/**
* 银行类型
*/
@JsonProperty("bank_type")
private String bankType;
/**
* 商户id
*/
@JsonProperty("mchid")
private String mchid;
/**
* 商户订单号
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 支付者信息
*/
@JsonProperty("payer")
private PayerDTO payer;
/**
* 详情信息
*/
@JsonProperty("promotion_detail")
private List<?> promotionDetail;
/**
* 成功时间
*/
@JsonProperty("success_time")
private String successTime;
/**
* 交易状态
*/
@JsonProperty("trade_state")
private String tradeState;
/**
* 交易状态描述
*/
@JsonProperty("trade_state_desc")
private String tradeStateDesc;
/**
* 交易类型
*/
@JsonProperty("trade_type")
private String tradeType;
/**
* 微信平台订单id
*/
@JsonProperty("transaction_id")
private String transactionId;
@NoArgsConstructor
@Data
public static class AmountDTO {
/**
* 货币类型
*/
@JsonProperty("currency")
private String currency;
/**
* 支付货币类型
*/
@JsonProperty("payer_currency")
private String payerCurrency;
/**
* 支付金额
*/
@JsonProperty("payer_total")
private Integer payerTotal;
/**
* 总金额
*/
@JsonProperty("total")
private Integer total;
}
/**
* 支付者信息
*/
@NoArgsConstructor
@Data
public static class PayerDTO {
/**
* 微信唯一标识
*/
@JsonProperty("openid")
private String openid;
}
}

View File

@ -9,7 +9,6 @@ import com.yf.exam.core.api.dto.BaseIdsReqDTO;
import com.yf.exam.core.api.dto.BaseStateReqDTO;
import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSearchDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
@ -18,7 +17,6 @@ import com.yf.exam.modules.exam.service.ExamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.Date;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
@ -148,17 +146,7 @@ public class ExamController extends BaseController {
return super.success(page);
}
/**
* 查询可考试列表
* @param reqDTO
* @return
*/
@ApiOperation(value = "查询可考试列表")
@RequestMapping(value = "/getExamList", method = { RequestMethod.GET})
public ApiRest getExamList(ExamSearchDTO reqDTO) {
List<ExamDTO> list = baseService.getExamList(reqDTO);
return super.success(list);
}
}

View File

@ -39,7 +39,7 @@ public class ExamRegistrationController extends BaseController {
public ApiRest save(@RequestBody ExamRegistrationDTO reqDTO) {
//复制参数
baseService.save(reqDTO);
return super.success();
return super.success(reqDTO.getId());
}
/**

View File

@ -5,6 +5,7 @@ import com.yf.exam.modules.exam.entity.UserAttachment;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
import lombok.Data;
@ -84,10 +85,10 @@ public class ExamRegistrationDTO extends UserAttachment implements Serializable
private Integer finishState;
/**
* 交费状态0未交费1已交费
* 交费状态待付款WAIT_PAY已付款PAY已取消CANCEL待收货WAIT_RECEIVED_GOODS已收货RECEIVED_GOODS退款中WAIT_REFUND已退款REFUNDED待退货WAIT_RETURNED_GOODS已退货RETURNED_GOODS
*/
@ApiModelProperty(value = "交费状态0未交费1已交费", required=true)
private Integer paymentState;
@ApiModelProperty(value = "交费状态待付款WAIT_PAY已付款PAY已取消CANCEL待收货WAIT_RECEIVED_GOODS已收货RECEIVED_GOODS退款中WAIT_REFUND已退款REFUNDED待退货WAIT_RETURNED_GOODS已退货RETURNED_GOODS", required=true)
private String paymentState;
/**
* 考试类型1模拟考试2正式考试3补考
@ -95,6 +96,11 @@ public class ExamRegistrationDTO extends UserAttachment implements Serializable
@ApiModelProperty(value = "考试类型1模拟考试2正式考试3补考", required=true)
private Integer examType;
/**
* 考试费用
*/
private BigDecimal examFee;
/**
* 交费开始时间
* */

View File

@ -54,10 +54,10 @@ public class ExamRegistrationVO {
private Integer finishState;
/**
* 交费状态0未交费1已交费
* 交费状态待付款WAIT_PAY已付款PAY已取消CANCEL待收货WAIT_RECEIVED_GOODS已收货RECEIVED_GOODS退款中WAIT_REFUND已退款REFUNDED待退货WAIT_RETURNED_GOODS已退货RETURNED_GOODS
*/
@ApiModelProperty(value = "交费状态0未交费1已交费", required=true)
private Integer paymentState;
@ApiModelProperty(value = "交费状态待付款WAIT_PAY已付款PAY已取消CANCEL待收货WAIT_RECEIVED_GOODS已收货RECEIVED_GOODS退款中WAIT_REFUND已退款REFUNDED待退货WAIT_RETURNED_GOODS已退货RETURNED_GOODS", required=true)
private String paymentState;
@ApiModelProperty(value = "考试主键", required=true)
private String examId;

View File

@ -4,7 +4,11 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
@ -76,15 +80,32 @@ public class ExamRegistration extends UserAttachment {
private Integer finishState;
/**
* 交费状态0未交费1已交费
* 考试费用
*/
private Integer paymentState;
private BigDecimal examFee;
/**
* 交费状态待付款WAIT_PAY已付款PAY已取消CANCEL待收货WAIT_RECEIVED_GOODS已收货RECEIVED_GOODS退款中WAIT_REFUND已退款REFUNDED待退货WAIT_RETURNED_GOODS已退货RETURNED_GOODS
*/
private String paymentState;
/**
* 交费时间
*/
private Date paymentDate;
/**
* 下单时间
*/
@ApiModelProperty(value = "下单时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime orderTime;
/**
* 确认退款状态未确认NOT_CONFIRM已确认CONFIRMED
*/
private String confirmRefundStatus;
/**
* 创建时间
*/

View File

@ -24,4 +24,6 @@ public interface ExamRegistrationMapper extends BaseMapper<ExamRegistration> {
void updateFinishState(ExamRegistration examRegistration);
IPage<ExamRegistrationVO> getRegUserList(Page page, @Param("query") ExamRegistrationDTO examRegistrationDTO);
int updatePaymentStateById(String paymentState, String id);
}

View File

@ -22,7 +22,7 @@ public interface ExamRegistrationService extends IService<ExamRegistration> {
* 保存报名信息
* @param reqDTO
*/
void save(ExamRegistrationDTO reqDTO);
String save(ExamRegistrationDTO reqDTO);
/**
* @description 查询已报名的考试列表

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.core.enums.GooodsOrderStatusEnum;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.exam.dto.ExamRegistrationDTO;
import com.yf.exam.modules.exam.dto.response.ExamRegistrationVO;
@ -27,7 +28,7 @@ import org.springframework.stereotype.Service;
public class ExamRegistrationServiceImpl extends ServiceImpl<ExamRegistrationMapper, ExamRegistration> implements ExamRegistrationService {
@Override
public void save(ExamRegistrationDTO reqDTO) {
public String save(ExamRegistrationDTO reqDTO) {
if (StringUtils.isBlank(reqDTO.getUserId())) {
throw new ServiceException("传输数据错误,请重新报名");
}
@ -38,6 +39,7 @@ public class ExamRegistrationServiceImpl extends ServiceImpl<ExamRegistrationMap
ExamRegistrationDTO examRegistrationDTO = new ExamRegistrationDTO();
examRegistrationDTO.setExamId(reqDTO.getExamId());
examRegistrationDTO.setUserId(reqDTO.getUserId());
examRegistrationDTO.setPaymentState(GooodsOrderStatusEnum.PAY.getInfo());
List<ExamRegistrationVO> list = getRegExamList(examRegistrationDTO);
if (CollectionUtils.isNotEmpty(list)) {
throw new ServiceException("您已报名过该考试,请勿重复报名");
@ -55,8 +57,10 @@ public class ExamRegistrationServiceImpl extends ServiceImpl<ExamRegistrationMap
// 复制基本数据
BeanUtils.copyProperties(reqDTO, entity);
entity.setId(id);
entity.setPaymentState(GooodsOrderStatusEnum.WAIT_PAY.getInfo());
entity.setFinishState(ExamFinishState.UNFINISH);
this.saveOrUpdate(entity);
return id;
}
@Override

View File

@ -0,0 +1,70 @@
package com.yf.exam.modules.payment.controller;
import com.yf.exam.core.api.controller.BaseController;
import com.yf.exam.core.domain.AjaxResult;
import com.yf.exam.modules.payment.entity.PaymentInfo;
import com.yf.exam.modules.payment.service.IPaymentInfoService;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 支付记录信息Controller
*
* @author xinyilu
* @date 2022-10-18
*/
@RestController
@RequestMapping("exam/api/paymentInfo")
public class PaymentInfoController extends BaseController {
@Resource
private IPaymentInfoService paymentInfoService;
/**
* 查询支付记录信息列表
*/
/*@GetMapping("/list")
public TableDataInfo list(PaymentInfo paymentInfo) {
startPage();
List<PaymentInfo> list = paymentInfoService.selectPaymentInfoList(paymentInfo);
return getDataTable(list);
}*/
/**
* 获取支付记录信息详细信息
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return AjaxResult.success(paymentInfoService.selectPaymentInfoById(id));
}
/**
* 新增支付记录信息
*/
@PostMapping
public AjaxResult add(@RequestBody PaymentInfo paymentInfo) {
return toAjax(paymentInfoService.insertPaymentInfo(paymentInfo));
}
/**
* 修改支付记录信息
*/
@PutMapping
public AjaxResult edit(@RequestBody PaymentInfo paymentInfo) {
return toAjax(paymentInfoService.updatePaymentInfo(paymentInfo));
}
/**
* 删除支付记录信息
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(paymentInfoService.deletePaymentInfoByIds(ids));
}
}

View File

@ -0,0 +1,156 @@
package com.yf.exam.modules.payment.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 支付记录信息对象 payment_info
*
* @author xinyilu
* @date 2022-10-18
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "支付记录信息对象", description = "payment_info")
public class PaymentInfo extends Model<PaymentInfo> {
/**
* 主键id
*/
private Long id;
/**
* 考生id
*/
@ApiModelProperty(value = "考生id")
private String userId;
/**
* 考试报名表主键系统自动生成
*/
@ApiModelProperty(value = "考试报名表主键,系统自动生成")
private String examRegistrationId;
/**
* 支付标题
*/
@ApiModelProperty(value = "支付标题")
private String paymentTitle;
/**
* 交易流水号
*/
@ApiModelProperty(value = "交易流水号")
private String transactionNo;
/**
* 支付金额
*/
@ApiModelProperty(value = "支付金额")
private BigDecimal payPrice;
/**
* 支付类型微信支付WECHAT_PAY支付宝支付ALI_PAY
*/
@ApiModelProperty(value = "支付类型微信支付WECHAT_PAY支付宝支付ALI_PAY")
private String payType;
/**
* 支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLET
*/
@ApiModelProperty(value = "支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLET")
private String payChannel;
/**
* 微信支付交易状态支付成功SUCCESS转入退款REFUND未支付NOTPAY已关闭CLOSED已撤销付款码支付REVOKED用户支付中付款码支付USERPAYING支付失败(其他原因如银行返回失败)PAYERROR
*/
@ApiModelProperty(value = "微信支付交易状态支付成功SUCCESS转入退款REFUND未支付NOTPAY已关闭CLOSED已撤销")
private String wechatTradeState;
/**
* 支付宝交易状态支付成功SUCCESS转入退款REFUND未支付NOTPAY已关闭CLOSED已撤销付款码支付REVOKED用户支付中付款码支付USERPAYING支付失败(其他原因如银行返回失败)PAYERROR
*/
@ApiModelProperty(value = "支付宝交易状态支付成功SUCCESS转入退款REFUND未支付NOTPAY已关闭CLOSED已撤销")
private String alipayTradeState;
/**
* 支付通知参数内容微信或者支付宝支付回调通知信息
*/
@ApiModelProperty(value = "支付通知参数内容,微信或者支付宝支付回调通知信息")
private String payNotifyContent;
/**
* 支付时间
*/
@ApiModelProperty(value = "支付时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime payTime;
/**
* 支付账户类型新医路账户XINYILU医路优品YILUYOUPIN
*/
@ApiModelProperty(value = "支付账户类型新医路账户XINYILU医路优品YILUYOUPIN")
private String paymentMerchantType;
/**
* 是否删除01
*/
private Integer delFlag;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("userId", getUserId())
.append("examRegistrationId", getExamRegistrationId())
.append("paymentTitle", getPaymentTitle())
.append("transactionNo", getTransactionNo())
.append("payPrice", getPayPrice())
.append("payType", getPayType())
.append("payChannel", getPayChannel())
.append("wechatTradeState", getWechatTradeState())
.append("alipayTradeState", getAlipayTradeState())
.append("payNotifyContent", getPayNotifyContent())
.append("payTime", getPayTime())
.append("paymentMerchantType", getPaymentMerchantType())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,199 @@
package com.yf.exam.modules.payment.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 退款记录信息对象 refund_info
*
* @author xinyilu
* @date 2022-10-18
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "退款记录信息对象", description = "refund_info")
public class RefundInfo implements Serializable {
private static final long serialVersionUID = -3202049270867033957L;
/**
* 主键id
*/
private Long id;
/**
* 会员id
*/
@ApiModelProperty(value = "会员id")
private String userId;
/**
* 商品订单号
*/
@ApiModelProperty(value = "商品订单号")
private String examRegistrationId;
/**
* 支付退款单号多种支付方式共用
*/
@ApiModelProperty(value = "支付退款单号,多种支付方式共用")
private String refundNo;
/**
* 商户退款单号系统自动生成
*/
@ApiModelProperty(value = "商户退款单号,系统自动生成")
private String outRefundNo;
/**
* 支付交易订单号多种支付方式共用
*/
@ApiModelProperty(value = "支付交易订单号,多种支付方式共用")
private String transactionNo;
/**
* 退款原因
*/
@ApiModelProperty(value = "退款原因")
private String refundReason;
/**
* 退款方式微信WE_CHAT支付宝ALI_PAY
*/
@ApiModelProperty(value = "退款方式微信WE_CHAT支付宝ALI_PAY")
private String refundType;
/**
* 微信退款状态退款成功SUCCESS退款关闭CLOSED退款处理中PROCESSING退款异常ABNORMAL
*/
@ApiModelProperty(value = "微信退款状态退款成功SUCCESS退款关闭CLOSED退款处理中PROCESSING退款异常ABNORMAL")
private String wechatRefundStatus;
/**
* 支付宝退款状态退款成功SUCCESS退款关闭CLOSED退款处理中PROCESSING退款异常ABNORMAL
*/
@ApiModelProperty(value = "支付宝退款状态退款成功SUCCESS退款关闭CLOSED退款处理中PROCESSING退款异常ABNORMAL")
private String alipayRefundStatus;
/**
* 订单金额
*/
@ApiModelProperty(value = "订单金额")
private BigDecimal orderTotalPrice;
/**
* 退款金额
*/
@ApiModelProperty(value = "退款金额")
private BigDecimal refundPrice;
/**
* 退款币种人民币CNY
*/
@ApiModelProperty(value = "退款币种人民币CNY")
private String currency;
/**
* ORIGINAL原路退款BALANCE退回到余额OTHER_BALANCE原账户异常退到其他余额账户OTHER_BANKCARD原银行卡异常退到其他银行卡
*/
@ApiModelProperty(value = "ORIGINAL原路退款BALANCE退回到余额OTHER_BALANCE原账户异常退到其他余额账户OTHER_BANKCARD原银行卡异常退到其他银行卡")
private String channel;
/**
* 退款入账账户
*/
@ApiModelProperty(value = "退款入账账户")
private String userreceivedaccount;
/**
* 退款成功时间
*/
@ApiModelProperty(value = "退款成功时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime successTime;
/**
* 退款回调通知内容微信或者支付款退款通知内容
*/
@ApiModelProperty(value = "退款回调通知内容,微信或者支付款退款通知内容")
private String refundNotifyContent;
/**
* 申请退款返回参数内容
*/
@ApiModelProperty(value = "申请退款返回参数内容")
private String applyRefundReturnContent;
/**
* 退款账户类型新医路账户XINYILU医路优品YILUYOUPIN
*/
@ApiModelProperty(value = "退款账户类型新医路账户XINYILU医路优品YILUYOUPIN")
private String refundMerchantType;
/**
* 是否删除01
*/
private Integer delFlag;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("userId", getUserId())
.append("examRegistrationId", getExamRegistrationId())
.append("refundNo", getRefundNo())
.append("outRefundNo", getOutRefundNo())
.append("transactionNo", getTransactionNo())
.append("refundReason", getRefundReason())
.append("refundType", getRefundType())
.append("wechatRefundStatus", getWechatRefundStatus())
.append("alipayRefundStatus", getAlipayRefundStatus())
.append("orderTotalPrice", getOrderTotalPrice())
.append("refundPrice", getRefundPrice())
.append("currency", getCurrency())
.append("channel", getChannel())
.append("userreceivedaccount", getUserreceivedaccount())
.append("successTime", getSuccessTime())
.append("refundNotifyContent", getRefundNotifyContent())
.append("applyRefundReturnContent", getApplyRefundReturnContent())
.append("refundMerchantType", getRefundMerchantType())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,70 @@
package com.yf.exam.modules.payment.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yf.exam.modules.payment.entity.PaymentInfo;
import java.util.List;
/**
* 支付记录信息Mapper接口
*
* @author xinyilu
* @date 2022-10-18
*/
public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
/**
* 查询支付记录信息
*
* @param id 支付记录信息主键
* @return 支付记录信息
*/
PaymentInfo selectPaymentInfoById(Long id);
/**
* 查询支付记录信息列表
*
* @param paymentInfo 支付记录信息
* @return 支付记录信息集合
*/
List<PaymentInfo> selectPaymentInfoList(PaymentInfo paymentInfo);
/**
* 新增支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int insertPaymentInfo(PaymentInfo paymentInfo);
/**
* 修改支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int updatePaymentInfo(PaymentInfo paymentInfo);
/**
* 删除支付记录信息
*
* @param id 支付记录信息主键
* @return 结果
*/
int deletePaymentInfoById(Long id);
/**
* 批量删除支付记录信息
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deletePaymentInfoByIds(Long[] ids);
/**
* 根据订单查询支付记录表信息
*
* @param orderNo 订单编号
* @return 数量
*/
int getByRegistrationId(String orderNo);
}

View File

@ -0,0 +1,89 @@
package com.yf.exam.modules.payment.mapper;
import com.yf.exam.modules.payment.entity.RefundInfo;
import java.time.LocalDateTime;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
* 退款记录信息Mapper接口
*
* @author xinyilu
* @date 2022-10-18
*/
public interface RefundInfoMapper {
/**
* 查询退款记录信息
*
* @param id 退款记录信息主键
* @return 退款记录信息
*/
RefundInfo selectRefundInfoById(Long id);
/**
* 查询退款记录信息列表
*
* @param refundInfo 退款记录信息
* @return 退款记录信息集合
*/
List<RefundInfo> selectRefundInfoList(RefundInfo refundInfo);
/**
* 新增退款记录信息
*
* @param refundInfo 退款记录信息
* @return 结果
*/
int insertRefundInfo(RefundInfo refundInfo);
/**
* 修改退款记录信息
*
* @param refundInfo 退款记录信息
* @return 结果
*/
int updateRefundInfo(RefundInfo refundInfo);
/**
* 删除退款记录信息
*
* @param id 退款记录信息主键
* @return 结果
*/
int deleteRefundInfoById(Long id);
/**
* 批量删除退款记录信息
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deleteRefundInfoByIds(Long[] ids);
/**
* 修改退款单信息
*
* @param refundInfo 退款单信息
* @return 更新数量
*/
int updateRefundInfoByNo(RefundInfo refundInfo);
/**
* 批量更新支付订单状态
*
* @param orderNo 订单编号
* @param successTime 退款成功时间
* @param weChatRefundStatus 订单状态
* @return 更新数量
*/
int updateBatchRefundStatus(@Param("orderNo") String orderNo, @Param("successTime") LocalDateTime successTime,
@Param("weChatRefundStatus") String weChatRefundStatus);
/**
* 根据订单编号查询支付退款表信息
*
* @param examRegistrationId 订单编号
* @return 数量
*/
int getRefundInfoByexamRegistrationId(String examRegistrationId);
}

View File

@ -0,0 +1,61 @@
package com.yf.exam.modules.payment.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.modules.payment.entity.PaymentInfo;
import java.util.List;
/**
* 支付记录信息Service接口
*
* @author xinyilu
* @date 2022-10-18
*/
public interface IPaymentInfoService extends IService<PaymentInfo> {
/**
* 查询支付记录信息
*
* @param id 支付记录信息主键
* @return 支付记录信息
*/
PaymentInfo selectPaymentInfoById(Long id);
/**
* 查询支付记录信息列表
*
* @param paymentInfo 支付记录信息
* @return 支付记录信息集合
*/
List<PaymentInfo> selectPaymentInfoList(PaymentInfo paymentInfo);
/**
* 新增支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int insertPaymentInfo(PaymentInfo paymentInfo);
/**
* 修改支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int updatePaymentInfo(PaymentInfo paymentInfo);
/**
* 批量删除支付记录信息
*
* @param ids 需要删除的支付记录信息主键集合
* @return 结果
*/
int deletePaymentInfoByIds(Long[] ids);
/**
* 删除支付记录信息信息
*
* @param id 支付记录信息主键
* @return 结果
*/
int deletePaymentInfoById(Long id);
}

View File

@ -1,6 +0,0 @@
package com.yf.exam.modules.payment.service;
public interface PaymentService {
}

View File

@ -0,0 +1,90 @@
package com.yf.exam.modules.payment.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.modules.payment.entity.PaymentInfo;
import com.yf.exam.modules.payment.mapper.PaymentInfoMapper;
import com.yf.exam.modules.payment.service.IPaymentInfoService;
import java.util.Date;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* 支付记录信息Service业务层处理
*
* @author xinyilu
* @date 2022-10-18
*/
@Service
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements IPaymentInfoService {
@Resource
private PaymentInfoMapper paymentInfoMapper;
/**
* 查询支付记录信息
*
* @param id 支付记录信息主键
* @return 支付记录信息
*/
@Override
public PaymentInfo selectPaymentInfoById(Long id) {
return paymentInfoMapper.selectPaymentInfoById(id);
}
/**
* 查询支付记录信息列表
*
* @param paymentInfo 支付记录信息
* @return 支付记录信息
*/
@Override
public List<PaymentInfo> selectPaymentInfoList(PaymentInfo paymentInfo) {
return paymentInfoMapper.selectPaymentInfoList(paymentInfo);
}
/**
* 新增支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
@Override
public int insertPaymentInfo(PaymentInfo paymentInfo) {
paymentInfo.setCreateTime(new Date());
return paymentInfoMapper.insertPaymentInfo(paymentInfo);
}
/**
* 修改支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
@Override
public int updatePaymentInfo(PaymentInfo paymentInfo) {
paymentInfo.setUpdateTime(new Date());
return paymentInfoMapper.updatePaymentInfo(paymentInfo);
}
/**
* 批量删除支付记录信息
*
* @param ids 需要删除的支付记录信息主键
* @return 结果
*/
@Override
public int deletePaymentInfoByIds(Long[] ids) {
return paymentInfoMapper.deletePaymentInfoByIds(ids);
}
/**
* 删除支付记录信息信息
*
* @param id 支付记录信息主键
* @return 结果
*/
@Override
public int deletePaymentInfoById(Long id) {
return paymentInfoMapper.deletePaymentInfoById(id);
}
}

View File

@ -1,135 +0,0 @@
package com.yf.exam.modules.payment.service.impl;
import com.yf.exam.config.WeChatPaymentUrlConfig;
import com.yf.exam.config.XylWeChatPaymentConfig;
import com.yf.exam.modules.payment.service.PaymentService;
import com.yf.exam.modules.utils.WeChatUtil;
import javax.annotation.Resource;
import org.apache.http.impl.client.CloseableHttpClient;
/**
* @description:
* @author: haown
* @create: 2025-07-23 09:44
**/
public class PaymentServiceImpl implements PaymentService {
@Resource(name = "xinYiLuWeChatPayClient")
private CloseableHttpClient xinYiLuWeChatPayClient;
@Resource
private WeChatPaymentUrlConfig weChatPaymentUrlConfig;
@Resource
private WeChatUtil weChatUtil;
@Resource
private XylWeChatPaymentConfig xylWeChatPaymentConfig;
/**
* H5下单成功状态码
*/
private static final int HAVE_BODY_SUCCESS_CODE = 200;
/**
* H5下单成功状态码
*/
private static final int NO_HAVE_BODY_SUCCESS_CODE = 204;
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* 签名方式
*/
private static final String SIGN_TYPE = "RSA";
/**
* 请求h5下单接口方法
*
* @param jsApiParams 下单参数
* @param paymentDTO 输入参数
* @param prepayId JsAp下单接口返回值
* @return 结果
* @throws Exception 异常信息
*/
/*private String requestH5Interface(String jsApiParams, PaymentDTO paymentDTO, String prepayId) throws Exception {
//log.info("JsApi请求下单参数 ====> {}", jsApiParams);
StringEntity stringEntity = new StringEntity(jsApiParams, StandardCharsets.UTF_8);
stringEntity.setContentType("application/json");
HttpPost httpPost = new HttpPost(weChatPaymentUrlConfig.getJsapiPalceOrderUrl());
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = null;
String payAccount = "";
try {
response = xinYiLuWeChatPayClient.execute(httpPost);
payAccount = "山东柏杏新医健康服务有限公司";
//处理响应结果
if (Objects.isNull(response)) {
//log.error("JsApi下单接口执行错误 执行账户为 ====> {}", payAccount);
throw new ServiceException("JsApi下单接口执行异常请联系管理员");
}
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String result = EntityUtils.toString(response.getEntity());
if (statusCode == HAVE_BODY_SUCCESS_CODE) {
//请求成功取出prepay_id的值并放入Redis缓存中prepay_id的有效期为两小时此处缓存一个半小时
Map<String, Object> resultMap = JSONObject.parseObject(result);
prepayId = resultMap.getOrDefault("prepay_id", "").toString();
redisTemplate.opsForValue().set(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo(), prepayId, 5400, TimeUnit.SECONDS);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
//请求成功无返回体
log.info("JsApi下单成功无返回值信息");
} else {
//请求失败
log.error("JsApi下单失败失败原因为 ====> {}", result);
throw new ServiceException("JsApi下单失败失败原因为" + result);
}
} catch (Exception e) {
log.error("JsApi下单失败失败原因 =====> {}", e.getMessage());
throw new ServiceException("JsApi下单失败请联系管理员");
} finally {
if (response != null) {
response.close();
}
}
return prepayId;
}*/
/**
* 构建微信小程序调起支付参数设置
*
* @param prepayId jsapi下单返回参数信息
* @param buySource 购买来源
* @return 参数信息
* @throws Exception 异常信息
*/
//private WeChatAppletSignVO getSignInfo(String prepayId, String buySource) throws Exception {
// //根据购买来源判断使用泉医到家小程序id还是泉医助手小程序id
// String appId = BuySourceEnum.TRAINING.getInfo().equals(buySource) ? nurseAppletChatConfig.getAppletId() : appletChatConfig.getAppletId();
// //随机字符串
// String nonceStr = UUID.randomUUID().toString().replace("-", "");
// //时间戳
// String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// //获取商户私钥
// PrivateKey privateKey = null;
// privateKey = weChatUtil.getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath());
// if (privateKey == null) {
// throw new ServiceException("获取商户私钥失败,请联系管理员!");
// }
// //计算签名信息
// prepayId = "prepay_id=" + prepayId;
// String signatureStr = Stream.of(appId, timestamp, nonceStr, prepayId)
// .collect(Collectors.joining("\n", "", "\n"));
// log.info("计算签名认证信息 =====> {}", signatureStr);
// Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
// sign.initSign(privateKey);
// sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
// String paySign = Base64Utils.encodeToString(sign.sign());
// return WeChatAppletSignVO.builder().appId(appId).timeStamp(timestamp).nonceStr(nonceStr).prepayId(prepayId)
// .signType(SIGN_TYPE).paySign(paySign).build();
//}
}

View File

@ -0,0 +1,250 @@
package com.yf.exam.modules.payment.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description 微信申请退款返回值实体类
* @Author 纪寒
* @Date 2022-10-24 17:50:40
* @Version 1.0
*/
@NoArgsConstructor
@Data
public class WeChatRefundInfoVO implements Serializable {
private static final long serialVersionUID = -7834783600438774698L;
/**
* 退款单号
*/
@JsonProperty("refund_id")
private String refundId;
/**
* 商户退款单号
*/
@JsonProperty("out_refund_no")
private String outRefundNo;
/**
* 微信平台订单号
*/
@JsonProperty("transaction_id")
private String transactionId;
/**
* 商户订单号
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 退款渠道
*/
@JsonProperty("channel")
private String channel;
/**
* 到账金额
*/
@JsonProperty("user_received_account")
private String userReceivedAccount;
/**
* 退款成功时间
*/
@JsonProperty("success_time")
private String successTime;
/**
* 创建时间
*/
@JsonProperty("create_time")
private String createTime;
/**
* 退款状态
*/
@JsonProperty("status")
private String status;
/**
* 金额信息
*/
@JsonProperty("funds_account")
private String fundsAccount;
/**
* 金额详情信息
*/
@JsonProperty("amount")
private AmountDTO amount;
/**
* 优惠退款信息
*/
@JsonProperty("promotion_detail")
private List<PromotionDetailDTO> promotionDetail;
@NoArgsConstructor
@Data
public static class AmountDTO {
/**
* 总金额
*/
@JsonProperty("total")
private Integer total;
/**
* 退款金额
*/
@JsonProperty("refund")
private Integer refund;
/**
* 退款出资账户及金额
*/
@JsonProperty("from")
private List<FromDTO> from;
/**
* 支付金额
*/
@JsonProperty("payer_total")
private Integer payerTotal;
/**
* 退款金额
*/
@JsonProperty("payer_refund")
private Integer payerRefund;
/**
* 应结退款金额
*/
@JsonProperty("settlement_refund")
private Integer settlementRefund;
/**
* 应结订单金额
*/
@JsonProperty("settlement_total")
private Integer settlementTotal;
/**
* 优惠退款金额
*/
@JsonProperty("discount_refund")
private Integer discountRefund;
/**
* 退款币种
*/
@JsonProperty("currency")
private String currency;
@NoArgsConstructor
@Data
public static class FromDTO {
/**
* 金额信息
*/
@JsonProperty("account")
private String account;
/**
* 金额信息
*/
@JsonProperty("amount")
private Integer amount;
}
}
/**
* 优惠退款信息
*/
@NoArgsConstructor
@Data
public static class PromotionDetailDTO {
/**
* 券ID
*/
@JsonProperty("promotion_id")
private String promotionId;
/**
* 优惠范围
*/
@JsonProperty("scope")
private String scope;
/**
* 优惠类型
*/
@JsonProperty("type")
private String type;
/**
* 优惠券面额
*/
@JsonProperty("amount")
private Integer amount;
/**
* 优惠退款金额
*/
@JsonProperty("refund_amount")
private Integer refundAmount;
/**
* 商品列表
*/
@JsonProperty("goods_detail")
private List<GoodsDetailDTO> goodsDetail;
/**
* 商品列表
*/
@NoArgsConstructor
@Data
public static class GoodsDetailDTO {
/**
* 商户侧商品编码
*/
@JsonProperty("merchant_goods_id")
private String merchantGoodsId;
/**
* 微信支付商品编码
*/
@JsonProperty("wechatpay_goods_id")
private String wechatpayGoodsId;
/**
* 商品名称
*/
@JsonProperty("goods_name")
private String goodsName;
/**
* 商品单价
*/
@JsonProperty("unit_price")
private Integer unitPrice;
/**
* 商品退款金额
*/
@JsonProperty("refund_amount")
private Integer refundAmount;
/**
* 商品退货数量
*/
@JsonProperty("refund_quantity")
private Integer refundQuantity;
}
}
}

View File

@ -0,0 +1,101 @@
package com.yf.exam.modules.payment.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description 微信退款回调通知参数实体类
* @Author 纪寒
* @Date 2022-10-25 11:38:39
* @Version 1.0
*/
@NoArgsConstructor
@Data
public class WeChatRefundNotifyVO implements Serializable {
private static final long serialVersionUID = -2553550636560311860L;
/**
* 商户号
*/
@JsonProperty("mchid")
private String mchid;
/**
* 微信支付订单号
*/
@JsonProperty("transaction_id")
private String transactionId;
/**
* 商户订单号
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 微信支付退款单号
*/
@JsonProperty("refund_id")
private String refundId;
/**
* 商户退款单号
*/
@JsonProperty("out_refund_no")
private String outRefundNo;
/**
* 退款状态
*/
@JsonProperty("refund_status")
private String refundStatus;
/**
* 退款成功时间
*/
@JsonProperty("success_time")
private String successTime;
/**
* 退款入账账户
*/
@JsonProperty("user_received_account")
private String userReceivedAccount;
/**
* 退款金额
*/
@JsonProperty("amount")
private AmountDTO amount;
@NoArgsConstructor
@Data
public static class AmountDTO {
/**
* 订单金额
*/
@JsonProperty("total")
private Integer total;
/**
* 退款金额
*/
@JsonProperty("refund")
private Integer refund;
/**
* 用户支付金额
*/
@JsonProperty("payer_total")
private Integer payerTotal;
/**
* 用户退款金额
*/
@JsonProperty("payer_refund")
private Integer payerRefund;
}
}

View File

@ -51,7 +51,7 @@ server:
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 考试系统微信小程序参数配置信息
exam-applet-chat-config:
applet-chat-config:
# 微信小程序
applet-id: wx26b9ecdba54bc588
# 微信小程序密钥
@ -73,7 +73,7 @@ xyl-we-chat-config:
# 山东省公共卫生学会API V3版本密钥 Xyl699003981qazVFR4xsw23edcASDFG
xyl-payment-key: gonggongweishengXUEHUI2025081100
# 山东省公共卫生学会微信支付回调地址 https://quanyidaojia.xinelu.cn
xyl-wechat-notify-url: http://8.131.93.145:54097
xyl-wechat-notify-url: http://8.131.93.145:54012
# 微信支付接口地址包含小程序和App支付接口地址
we-chat-payment-url-config:
# 小程序JSAPI下单接口地址

View File

@ -104,7 +104,8 @@
<select id="getExamList" resultMap="OnlineResultMap">
SELECT * FROM el_exam
where state = 0 and (exam_type = 2 or exam_type = 3) and id not in (select exam_id from el_exam_registration where user_id = #{query.userId})
where state = 0 and (exam_type = 2 or exam_type = 3)
and id not in (select exam_id from el_exam_registration where user_id = #{query.userId} and payment_state = 'PAY')
<if test="query!=null">
<if test="query.openType!=null">
AND open_type = #{query.openType}

View File

@ -105,4 +105,9 @@
order by reg.reg_time desc
</select>
<update id="updatePaymentStateById" parameterType="com.yf.exam.modules.exam.entity.ExamRegistration">
update el_exam_registration
set payment_state = #{paymentState}
where id= #{id}
</update>
</mapper>

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yf.exam.modules.payment.mapper.PaymentInfoMapper">
<resultMap type="com.yf.exam.modules.payment.entity.PaymentInfo" id="PaymentInfoResult">
<result property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="examRegistrationId" column="exam_registration_id"/>
<result property="paymentTitle" column="payment_title"/>
<result property="transactionNo" column="transaction_no"/>
<result property="payPrice" column="pay_price"/>
<result property="payType" column="pay_type"/>
<result property="payChannel" column="pay_channel"/>
<result property="wechatTradeState" column="wechat_trade_state"/>
<result property="alipayTradeState" column="alipay_trade_state"/>
<result property="payNotifyContent" column="pay_notify_content"/>
<result property="payTime" column="pay_time"/>
<result property="paymentMerchantType" column="payment_merchant_type"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectPaymentInfoVo">
select id,
user_id,
exam_registration_id,
payment_title,
transaction_no,
pay_price,
pay_type,
pay_channel,
wechat_trade_state,
alipay_trade_state,
pay_notify_content,
pay_time,
payment_merchant_type,
del_flag,
create_by,
create_time,
update_by,
update_time
from payment_info
</sql>
<select id="selectPaymentInfoList" parameterType="com.yf.exam.modules.payment.entity.PaymentInfo" resultMap="PaymentInfoResult">
<include refid="selectPaymentInfoVo"/>
<where>
<if test="userId != null ">
and user_id = #{userId}
</if>
<if test="examRegistrationId != null and examRegistrationId != ''">
and exam_registration_id = #{examRegistrationId}
</if>
<if test="paymentTitle != null and paymentTitle != ''">
and payment_title = #{paymentTitle}
</if>
<if test="transactionNo != null and transactionNo != ''">
and transaction_no = #{transactionNo}
</if>
<if test="payPrice != null ">
and pay_price = #{payPrice}
</if>
<if test="payType != null and payType != ''">
and pay_type = #{payType}
</if>
<if test="payChannel != null and payChannel != ''">
and pay_channel = #{payChannel}
</if>
<if test="wechatTradeState != null and wechatTradeState != ''">
and wechat_trade_state = #{wechatTradeState}
</if>
<if test="alipayTradeState != null and alipayTradeState != ''">
and alipay_trade_state = #{alipayTradeState}
</if>
<if test="payNotifyContent != null and payNotifyContent != ''">
and pay_notify_content = #{payNotifyContent}
</if>
<if test="payTime != null ">
and pay_time = #{payTime}
</if>
<if test="paymentMerchantType != null and paymentMerchantType != ''">
and payment_merchant_type = #{paymentMerchantType}
</if>
</where>
</select>
<select id="selectPaymentInfoById" parameterType="Long"
resultMap="PaymentInfoResult">
<include refid="selectPaymentInfoVo"/>
where id = #{id}
</select>
<insert id="insertPaymentInfo" parameterType="com.yf.exam.modules.payment.entity.PaymentInfo" useGeneratedKeys="true"
keyProperty="id">
insert into payment_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,
</if>
<if test="examRegistrationId != null">exam_registration_id,
</if>
<if test="paymentTitle != null">payment_title,
</if>
<if test="transactionNo != null">transaction_no,
</if>
<if test="payPrice != null">pay_price,
</if>
<if test="payType != null">pay_type,
</if>
<if test="payChannel != null">pay_channel,
</if>
<if test="wechatTradeState != null">wechat_trade_state,
</if>
<if test="alipayTradeState != null">alipay_trade_state,
</if>
<if test="payNotifyContent != null">pay_notify_content,
</if>
<if test="payTime != null">pay_time,
</if>
<if test="paymentMerchantType != null">payment_merchant_type,
</if>
<if test="delFlag != null">del_flag,
</if>
<if test="createBy != null">create_by,
</if>
<if test="createTime != null">create_time,
</if>
<if test="updateBy != null">update_by,
</if>
<if test="updateTime != null">update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},
</if>
<if test="examRegistrationId != null">#{examRegistrationId},
</if>
<if test="paymentTitle != null">#{paymentTitle},
</if>
<if test="transactionNo != null">#{transactionNo},
</if>
<if test="payPrice != null">#{payPrice},
</if>
<if test="payType != null">#{payType},
</if>
<if test="payChannel != null">#{payChannel},
</if>
<if test="wechatTradeState != null">#{wechatTradeState},
</if>
<if test="alipayTradeState != null">#{alipayTradeState},
</if>
<if test="payNotifyContent != null">#{payNotifyContent},
</if>
<if test="payTime != null">#{payTime},
</if>
<if test="paymentMerchantType != null">#{paymentMerchantType},
</if>
<if test="delFlag != null">#{delFlag},
</if>
<if test="createBy != null">#{createBy},
</if>
<if test="createTime != null">#{createTime},
</if>
<if test="updateBy != null">#{updateBy},
</if>
<if test="updateTime != null">#{updateTime},
</if>
</trim>
</insert>
<update id="updatePaymentInfo" parameterType="com.yf.exam.modules.payment.entity.PaymentInfo">
update payment_info
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id =
#{userId},
</if>
<if test="examRegistrationId != null">exam_registration_id =
#{examRegistrationId},
</if>
<if test="paymentTitle != null">payment_title =
#{paymentTitle},
</if>
<if test="transactionNo != null">transaction_no =
#{transactionNo},
</if>
<if test="payPrice != null">pay_price =
#{payPrice},
</if>
<if test="payType != null">pay_type =
#{payType},
</if>
<if test="payChannel != null">pay_channel =
#{payChannel},
</if>
<if test="wechatTradeState != null">wechat_trade_state =
#{wechatTradeState},
</if>
<if test="alipayTradeState != null">alipay_trade_state =
#{alipayTradeState},
</if>
<if test="payNotifyContent != null">pay_notify_content =
#{payNotifyContent},
</if>
<if test="payTime != null">pay_time =
#{payTime},
</if>
<if test="paymentMerchantType != null">payment_merchant_type =
#{paymentMerchantType},
</if>
<if test="delFlag != null">del_flag =
#{delFlag},
</if>
<if test="createBy != null">create_by =
#{createBy},
</if>
<if test="createTime != null">create_time =
#{createTime},
</if>
<if test="updateBy != null">update_by =
#{updateBy},
</if>
<if test="updateTime != null">update_time =
#{updateTime},
</if>
</trim>
where id = #{id}
</update>
<delete id="deletePaymentInfoById" parameterType="Long">
delete
from payment_info
where id = #{id}
</delete>
<delete id="deletePaymentInfoByIds" parameterType="String">
delete from payment_info where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="getByRegistrationId" parameterType="string" resultType="int">
select count(1) paymentCount
from payment_info
where exam_registration_id = #{examRegistrationId}
</select>
</mapper>

View File

@ -0,0 +1,344 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yf.exam.modules.payment.mapper.RefundInfoMapper">
<resultMap type="com.yf.exam.modules.payment.entity.RefundInfo" id="RefundInfoResult">
<result property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="examRegistrationId" column="exam_registration_id"/>
<result property="refundNo" column="refund_no"/>
<result property="outRefundNo" column="out_refund_no"/>
<result property="transactionNo" column="transaction_no"/>
<result property="refundReason" column="refund_reason"/>
<result property="refundType" column="refund_type"/>
<result property="wechatRefundStatus" column="wechat_refund_status"/>
<result property="alipayRefundStatus" column="alipay_refund_status"/>
<result property="orderTotalPrice" column="order_total_price"/>
<result property="refundPrice" column="refund_price"/>
<result property="currency" column="currency"/>
<result property="channel" column="channel"/>
<result property="userreceivedaccount" column="userReceivedAccount"/>
<result property="successTime" column="success_time"/>
<result property="refundNotifyContent" column="refund_notify_content"/>
<result property="applyRefundReturnContent" column="apply_refund_return_content"/>
<result property="refundMerchantType" column="refund_merchant_type"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectRefundInfoVo">
select id,
user_id,
exam_registration_id,
refund_no,
out_refund_no,
transaction_no,
refund_reason,
refund_type,
wechat_refund_status,
alipay_refund_status,
order_total_price,
refund_price,
currency,
channel,
userReceivedAccount,
success_time,
refund_notify_content,
apply_refund_return_content,
refund_merchant_type,
del_flag,
create_by,
create_time,
update_by,
update_time
from refund_info
</sql>
<select id="selectRefundInfoList" parameterType="com.yf.exam.modules.payment.entity.RefundInfo" resultMap="RefundInfoResult">
<include refid="selectRefundInfoVo"/>
<where>
<if test="userId != null ">
and user_id = #{userId}
</if>
<if test="examRegistrationId != null and examRegistrationId != ''">
and exam_registration_id = #{examRegistrationId}
</if>
<if test="refundNo != null and refundNo != ''">
and refund_no = #{refundNo}
</if>
<if test="outRefundNo != null and outRefundNo != ''">
and out_refund_no = #{outRefundNo}
</if>
<if test="transactionNo != null and transactionNo != ''">
and transaction_no = #{transactionNo}
</if>
<if test="refundReason != null and refundReason != ''">
and refund_reason = #{refundReason}
</if>
<if test="refundType != null and refundType != ''">
and refund_type = #{refundType}
</if>
<if test="wechatRefundStatus != null and wechatRefundStatus != ''">
and wechat_refund_status = #{wechatRefundStatus}
</if>
<if test="alipayRefundStatus != null and alipayRefundStatus != ''">
and alipay_refund_status = #{alipayRefundStatus}
</if>
<if test="orderTotalPrice != null ">
and order_total_price = #{orderTotalPrice}
</if>
<if test="refundPrice != null ">
and refund_price = #{refundPrice}
</if>
<if test="currency != null and currency != ''">
and currency = #{currency}
</if>
<if test="channel != null and channel != ''">
and channel = #{channel}
</if>
<if test="userreceivedaccount != null and userreceivedaccount != ''">
and userReceivedAccount = #{userreceivedaccount}
</if>
<if test="successTime != null ">
and success_time = #{successTime}
</if>
<if test="refundNotifyContent != null and refundNotifyContent != ''">
and refund_notify_content = #{refundNotifyContent}
</if>
<if test="applyRefundReturnContent != null and applyRefundReturnContent != ''">
and apply_refund_return_content = #{applyRefundReturnContent}
</if>
<if test="refundMerchantType != null and refundMerchantType != ''">
and refund_merchant_type = #{refundMerchantType}
</if>
</where>
</select>
<select id="selectRefundInfoById" parameterType="Long"
resultMap="RefundInfoResult">
<include refid="selectRefundInfoVo"/>
where id = #{id}
</select>
<insert id="insertRefundInfo" parameterType="com.yf.exam.modules.payment.entity.RefundInfo" useGeneratedKeys="true"
keyProperty="id">
insert into refund_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,
</if>
<if test="examRegistrationId != null">exam_registration_id,
</if>
<if test="refundNo != null">refund_no,
</if>
<if test="outRefundNo != null">out_refund_no,
</if>
<if test="transactionNo != null">transaction_no,
</if>
<if test="refundReason != null">refund_reason,
</if>
<if test="refundType != null">refund_type,
</if>
<if test="wechatRefundStatus != null">wechat_refund_status,
</if>
<if test="alipayRefundStatus != null">alipay_refund_status,
</if>
<if test="orderTotalPrice != null">order_total_price,
</if>
<if test="refundPrice != null">refund_price,
</if>
<if test="currency != null">currency,
</if>
<if test="channel != null">channel,
</if>
<if test="userreceivedaccount != null">userReceivedAccount,
</if>
<if test="successTime != null">success_time,
</if>
<if test="refundNotifyContent != null">refund_notify_content,
</if>
<if test="applyRefundReturnContent != null">apply_refund_return_content,
</if>
<if test="refundMerchantType != null">refund_merchant_type,
</if>
<if test="delFlag != null">del_flag,
</if>
<if test="createBy != null">create_by,
</if>
<if test="createTime != null">create_time,
</if>
<if test="updateBy != null">update_by,
</if>
<if test="updateTime != null">update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},
</if>
<if test="examRegistrationId != null">#{examRegistrationId},
</if>
<if test="refundNo != null">#{refundNo},
</if>
<if test="outRefundNo != null">#{outRefundNo},
</if>
<if test="transactionNo != null">#{transactionNo},
</if>
<if test="refundReason != null">#{refundReason},
</if>
<if test="refundType != null">#{refundType},
</if>
<if test="wechatRefundStatus != null">#{wechatRefundStatus},
</if>
<if test="alipayRefundStatus != null">#{alipayRefundStatus},
</if>
<if test="orderTotalPrice != null">#{orderTotalPrice},
</if>
<if test="refundPrice != null">#{refundPrice},
</if>
<if test="currency != null">#{currency},
</if>
<if test="channel != null">#{channel},
</if>
<if test="userreceivedaccount != null">#{userreceivedaccount},
</if>
<if test="successTime != null">#{successTime},
</if>
<if test="refundNotifyContent != null">#{refundNotifyContent},
</if>
<if test="applyRefundReturnContent != null">#{applyRefundReturnContent},
</if>
<if test="refundMerchantType != null">#{refundMerchantType},
</if>
<if test="delFlag != null">#{delFlag},
</if>
<if test="createBy != null">#{createBy},
</if>
<if test="createTime != null">#{createTime},
</if>
<if test="updateBy != null">#{updateBy},
</if>
<if test="updateTime != null">#{updateTime},
</if>
</trim>
</insert>
<update id="updateRefundInfo" parameterType="com.yf.exam.modules.payment.entity.RefundInfo">
update refund_info
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id =
#{userId},
</if>
<if test="examRegistrationId != null">exam_registration_id =
#{examRegistrationId},
</if>
<if test="refundNo != null">refund_no =
#{refundNo},
</if>
<if test="outRefundNo != null">out_refund_no =
#{outRefundNo},
</if>
<if test="transactionNo != null">transaction_no =
#{transactionNo},
</if>
<if test="refundReason != null">refund_reason =
#{refundReason},
</if>
<if test="refundType != null">refund_type =
#{refundType},
</if>
<if test="wechatRefundStatus != null">wechat_refund_status =
#{wechatRefundStatus},
</if>
<if test="alipayRefundStatus != null">alipay_refund_status =
#{alipayRefundStatus},
</if>
<if test="orderTotalPrice != null">order_total_price =
#{orderTotalPrice},
</if>
<if test="refundPrice != null">refund_price =
#{refundPrice},
</if>
<if test="currency != null">currency =
#{currency},
</if>
<if test="channel != null">channel =
#{channel},
</if>
<if test="userreceivedaccount != null">userReceivedAccount =
#{userreceivedaccount},
</if>
<if test="successTime != null">success_time =
#{successTime},
</if>
<if test="refundNotifyContent != null">refund_notify_content =
#{refundNotifyContent},
</if>
<if test="applyRefundReturnContent != null">apply_refund_return_content =
#{applyRefundReturnContent},
</if>
<if test="refundMerchantType != null">refund_merchant_type =
#{refundMerchantType},
</if>
<if test="delFlag != null">del_flag =
#{delFlag},
</if>
<if test="createBy != null">create_by =
#{createBy},
</if>
<if test="createTime != null">create_time =
#{createTime},
</if>
<if test="updateBy != null">update_by =
#{updateBy},
</if>
<if test="updateTime != null">update_time =
#{updateTime},
</if>
</trim>
where id = #{id}
</update>
<delete id="deleteRefundInfoById" parameterType="Long">
delete
from refund_info
where id = #{id}
</delete>
<delete id="deleteRefundInfoByIds" parameterType="String">
delete from refund_info where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<update id="updateRefundInfoByNo" parameterType="com.yf.exam.modules.payment.entity.RefundInfo">
update refund_info
set wechat_refund_status = #{wechatRefundStatus},
success_time = #{successTime},
refund_notify_content = #{refundNotifyContent},
update_time = now()
where exam_registration_id = #{examRegistrationId}
and out_refund_no = #{outRefundNo}
and wechat_refund_status &lt;&gt; #{wechatRefundStatus}
</update>
<update id="updateBatchRefundStatus">
UPDATE refund_info
SET wechat_refund_status = #{weChatRefundStatus},
success_time = #{successTime},
update_time = now()
WHERE del_flag = 0
and exam_registration_id = #{examRegistrationId}
and wechat_refund_status &lt;&gt; #{weChatRefundStatus}
</update>
<select id="getRefundInfoByexamRegistrationId" parameterType="string" resultType="int">
select count(1) refundCount
from refund_info
where exam_registration_id = #{examRegistrationId}
</select>
</mapper>