diff --git a/exam-admin/pom.xml b/exam-admin/pom.xml
index 0b62a93..9c8d09c 100644
--- a/exam-admin/pom.xml
+++ b/exam-admin/pom.xml
@@ -191,6 +191,29 @@
httpclient
4.5.13
+
+ org.springframework.integration
+ spring-integration-redis
+ 5.5.12
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.12.6
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.12.6
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.12.6
+
diff --git a/exam-admin/src/main/java/com/yf/exam/config/RedisDistributedLockUtilsConfig.java b/exam-admin/src/main/java/com/yf/exam/config/RedisDistributedLockUtilsConfig.java
new file mode 100644
index 0000000..6a9001b
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/config/RedisDistributedLockUtilsConfig.java
@@ -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());
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/constant/Constants.java b/exam-admin/src/main/java/com/yf/exam/constant/Constants.java
index b450366..e4e6bf0 100644
--- a/exam-admin/src/main/java/com/yf/exam/constant/Constants.java
+++ b/exam-admin/src/main/java/com/yf/exam/constant/Constants.java
@@ -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:";
+
/**
* 文件上传路径
*/
diff --git a/exam-admin/src/main/java/com/yf/exam/core/api/controller/BaseController.java b/exam-admin/src/main/java/com/yf/exam/core/api/controller/BaseController.java
index 22fcf09..2ceec8a 100644
--- a/exam-admin/src/main/java/com/yf/exam/core/api/controller/BaseController.java
+++ b/exam-admin/src/main/java/com/yf/exam/core/api/controller/BaseController.java
@@ -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 apiRest = message(ex.getCode(), ex.getMsg(), null);
return apiRest;
}
+
+ /**
+ * 响应返回结果
+ *
+ * @param rows 影响行数
+ * @return 操作结果
+ */
+ protected AjaxResult toAjax(int rows) {
+ return rows > 0 ? AjaxResult.success() : AjaxResult.error();
+ }
}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/enums/ConfirmRefundStatusEnum.java b/exam-admin/src/main/java/com/yf/exam/core/enums/ConfirmRefundStatusEnum.java
new file mode 100644
index 0000000..800ceb1
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/enums/ConfirmRefundStatusEnum.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/enums/GooodsOrderStatusEnum.java b/exam-admin/src/main/java/com/yf/exam/core/enums/GooodsOrderStatusEnum.java
new file mode 100644
index 0000000..2688fe8
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/enums/GooodsOrderStatusEnum.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/enums/PayTypeEnum.java b/exam-admin/src/main/java/com/yf/exam/core/enums/PayTypeEnum.java
new file mode 100644
index 0000000..088d2b3
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/enums/PayTypeEnum.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/enums/RefundStatusEnum.java b/exam-admin/src/main/java/com/yf/exam/core/enums/RefundStatusEnum.java
new file mode 100644
index 0000000..18568f7
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/enums/RefundStatusEnum.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/enums/WeChatTradeStateEnum.java b/exam-admin/src/main/java/com/yf/exam/core/enums/WeChatTradeStateEnum.java
new file mode 100644
index 0000000..4933633
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/enums/WeChatTradeStateEnum.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/core/utils/RedisDistributedLockUtils.java b/exam-admin/src/main/java/com/yf/exam/core/utils/RedisDistributedLockUtils.java
new file mode 100644
index 0000000..4133431
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/core/utils/RedisDistributedLockUtils.java
@@ -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);
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamController.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamController.java
new file mode 100644
index 0000000..725c146
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamController.java
@@ -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 list = examService.getExamList(reqDTO);
+ return super.success(list);
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamRegistrationController.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamRegistrationController.java
new file mode 100644
index 0000000..9f24b46
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletExamRegistrationController.java
@@ -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 list = baseService.getRegExamList(examRegistrationDTO);
+ return super.success(list);
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java
index 50a37d9..c7f1ea5 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/AppletLoginController.java
@@ -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));
}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/WeChatPaymentController.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/WeChatPaymentController.java
new file mode 100644
index 0000000..5fb93f1
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/controller/WeChatPaymentController.java
@@ -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);
+ }
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/PaymentDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/PaymentDTO.java
new file mode 100644
index 0000000..2c0264f
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/PaymentDTO.java
@@ -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;
+
+ /**
+ * 支付渠道,手机App:MOBILE_APP,微信小程序:WECHAT_APPLET,支付宝小程序:ALI_PAY_APPLE
+ */
+ @NotBlank(message = "支付渠道不能为空!")
+ @ApiModelProperty(value = "支付渠道,手机App:MOBILE_APP,微信小程序:WECHAT_APPLET,支付宝小程序:ALI_PAY_APPLE", required=true)
+ private String orderChannel;
+
+ /**
+ * 支付金额
+ */
+ @NotNull(message = "支付金额不能为空!")
+ @ApiModelProperty(value = "支付金额", required=true)
+ private BigDecimal paymentPrice;
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/RefundDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/RefundDTO.java
new file mode 100644
index 0000000..c4b26a3
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/dto/RefundDTO.java
@@ -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;
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPayNotifyService.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPayNotifyService.java
new file mode 100644
index 0000000..e59d738
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPayNotifyService.java
@@ -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;
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPaymentService.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPaymentService.java
new file mode 100644
index 0000000..f5c7d57
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatPaymentService.java
@@ -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;
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatRefundService.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatRefundService.java
new file mode 100644
index 0000000..c0b7132
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/WeChatRefundService.java
@@ -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;
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java
index b43caed..267b84a 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/AppletLoginServiceImpl.java
@@ -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 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);
}
/**
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPayNotifyServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPayNotifyServiceImpl.java
new file mode 100644
index 0000000..5fcbcc1
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPayNotifyServiceImpl.java
@@ -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 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 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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPaymentServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPaymentServiceImpl.java
new file mode 100644
index 0000000..2fde217
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatPaymentServiceImpl.java
@@ -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 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 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 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 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 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 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();
+ }
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatRefundServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatRefundServiceImpl.java
new file mode 100644
index 0000000..22cad10
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/service/impl/WeChatRefundServiceImpl.java
@@ -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 paramMap = new LinkedHashMap<>();
+ //订单编号
+ paramMap.put("out_trade_no", refundDTO.getId());
+ //商户退款单号
+ paramMap.put("out_refund_no", outRefundNo);
+ //退款金额参数
+ Map 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;
+ }
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/OrderStatusInfoVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/OrderStatusInfoVO.java
new file mode 100644
index 0000000..ae97ff2
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/OrderStatusInfoVO.java
@@ -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;
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatAppletSignVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatAppletSignVO.java
similarity index 95%
rename from exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatAppletSignVO.java
rename to exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatAppletSignVO.java
index d0338ca..d382f7b 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatAppletSignVO.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatAppletSignVO.java
@@ -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;
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatPayNotifyPlaintextVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatPayNotifyPlaintextVO.java
new file mode 100644
index 0000000..4effef6
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatPayNotifyPlaintextVO.java
@@ -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 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:扫码支付
+ * APP:APP支付
+ * MICROPAY:付款码支付
+ * MWEB:H5支付
+ * 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 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;
+ }
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatQueryOrderVO.java b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatQueryOrderVO.java
new file mode 100644
index 0000000..0cc7ba2
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/applet/vo/WeChatQueryOrderVO.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java
index d458d3a..e79da94 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java
@@ -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 list = baseService.getExamList(reqDTO);
- return super.success(list);
- }
+
}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamRegistrationController.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamRegistrationController.java
index 54919e2..6a23e54 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamRegistrationController.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/controller/ExamRegistrationController.java
@@ -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());
}
/**
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/ExamRegistrationDTO.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/ExamRegistrationDTO.java
index 5e2ba58..61b9543 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/ExamRegistrationDTO.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/ExamRegistrationDTO.java
@@ -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;
+
/**
* 交费开始时间
* */
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/response/ExamRegistrationVO.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/response/ExamRegistrationVO.java
index b3353be..5528eea 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/response/ExamRegistrationVO.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/dto/response/ExamRegistrationVO.java
@@ -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;
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/entity/ExamRegistration.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/entity/ExamRegistration.java
index 7f5abf9..1269d43 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/entity/ExamRegistration.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/entity/ExamRegistration.java
@@ -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;
+
/**
* 创建时间
*/
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/mapper/ExamRegistrationMapper.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/mapper/ExamRegistrationMapper.java
index 452c155..2c2c08f 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/mapper/ExamRegistrationMapper.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/mapper/ExamRegistrationMapper.java
@@ -24,4 +24,6 @@ public interface ExamRegistrationMapper extends BaseMapper {
void updateFinishState(ExamRegistration examRegistration);
IPage getRegUserList(Page page, @Param("query") ExamRegistrationDTO examRegistrationDTO);
+
+ int updatePaymentStateById(String paymentState, String id);
}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/service/ExamRegistrationService.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/service/ExamRegistrationService.java
index d2c1a0d..c904495 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/service/ExamRegistrationService.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/service/ExamRegistrationService.java
@@ -22,7 +22,7 @@ public interface ExamRegistrationService extends IService {
* 保存报名信息
* @param reqDTO
*/
- void save(ExamRegistrationDTO reqDTO);
+ String save(ExamRegistrationDTO reqDTO);
/**
* @description 查询已报名的考试列表
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRegistrationServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRegistrationServiceImpl.java
index e3ce0c4..aad2f81 100644
--- a/exam-admin/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRegistrationServiceImpl.java
+++ b/exam-admin/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRegistrationServiceImpl.java
@@ -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 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 list = getRegExamList(examRegistrationDTO);
if (CollectionUtils.isNotEmpty(list)) {
throw new ServiceException("您已报名过该考试,请勿重复报名");
@@ -55,8 +57,10 @@ public class ExamRegistrationServiceImpl extends ServiceImpl 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));
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/PaymentInfo.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/PaymentInfo.java
new file mode 100644
index 0000000..0207ac0
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/PaymentInfo.java
@@ -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 {
+ /**
+ * 主键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;
+
+ /**
+ * 支付渠道,手机App:MOBILE_APP,微信小程序:WECHAT_APPLET,支付宝小程序:ALI_PAY_APPLET
+ */
+ @ApiModelProperty(value = "支付渠道,手机App:MOBILE_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;
+
+ /**
+ * 是否删除,0:否,1:是
+ */
+ 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();
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/RefundInfo.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/RefundInfo.java
new file mode 100644
index 0000000..b020a80
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/entity/RefundInfo.java
@@ -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;
+
+ /**
+ * 是否删除,0:否,1:是
+ */
+ 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();
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/PaymentInfoMapper.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/PaymentInfoMapper.java
new file mode 100644
index 0000000..9c704c1
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/PaymentInfoMapper.java
@@ -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 {
+ /**
+ * 查询支付记录信息
+ *
+ * @param id 支付记录信息主键
+ * @return 支付记录信息
+ */
+ PaymentInfo selectPaymentInfoById(Long id);
+
+ /**
+ * 查询支付记录信息列表
+ *
+ * @param paymentInfo 支付记录信息
+ * @return 支付记录信息集合
+ */
+ List 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);
+
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/RefundInfoMapper.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/RefundInfoMapper.java
new file mode 100644
index 0000000..a1f9816
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/mapper/RefundInfoMapper.java
@@ -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 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);
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/IPaymentInfoService.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/IPaymentInfoService.java
new file mode 100644
index 0000000..e4529cf
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/IPaymentInfoService.java
@@ -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 {
+ /**
+ * 查询支付记录信息
+ *
+ * @param id 支付记录信息主键
+ * @return 支付记录信息
+ */
+ PaymentInfo selectPaymentInfoById(Long id);
+
+ /**
+ * 查询支付记录信息列表
+ *
+ * @param paymentInfo 支付记录信息
+ * @return 支付记录信息集合
+ */
+ List 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);
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/PaymentService.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/PaymentService.java
deleted file mode 100644
index 6c89e93..0000000
--- a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/PaymentService.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.yf.exam.modules.payment.service;
-
-public interface PaymentService {
-
-
-}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentInfoServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentInfoServiceImpl.java
new file mode 100644
index 0000000..7dd2e4c
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentInfoServiceImpl.java
@@ -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 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 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);
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentServiceImpl.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentServiceImpl.java
deleted file mode 100644
index c016c60..0000000
--- a/exam-admin/src/main/java/com/yf/exam/modules/payment/service/impl/PaymentServiceImpl.java
+++ /dev/null
@@ -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 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();
- //}
-
-}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundInfoVO.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundInfoVO.java
new file mode 100644
index 0000000..2404658
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundInfoVO.java
@@ -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 promotionDetail;
+
+ @NoArgsConstructor
+ @Data
+ public static class AmountDTO {
+ /**
+ * 总金额
+ */
+ @JsonProperty("total")
+ private Integer total;
+
+ /**
+ * 退款金额
+ */
+ @JsonProperty("refund")
+ private Integer refund;
+
+ /**
+ * 退款出资账户及金额
+ */
+ @JsonProperty("from")
+ private List 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 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;
+ }
+ }
+}
diff --git a/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundNotifyVO.java b/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundNotifyVO.java
new file mode 100644
index 0000000..d80b74b
--- /dev/null
+++ b/exam-admin/src/main/java/com/yf/exam/modules/payment/vo/WeChatRefundNotifyVO.java
@@ -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;
+ }
+}
diff --git a/exam-admin/src/main/resources/application.yml b/exam-admin/src/main/resources/application.yml
index c18b736..003af38 100644
--- a/exam-admin/src/main/resources/application.yml
+++ b/exam-admin/src/main/resources/application.yml
@@ -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下单接口地址
diff --git a/exam-admin/src/main/resources/mapper/exam/ExamMapper.xml b/exam-admin/src/main/resources/mapper/exam/ExamMapper.xml
index bee5dbd..1180f2d 100644
--- a/exam-admin/src/main/resources/mapper/exam/ExamMapper.xml
+++ b/exam-admin/src/main/resources/mapper/exam/ExamMapper.xml
@@ -104,7 +104,8 @@
+
+ update el_exam_registration
+ set payment_state = #{paymentState}
+ where id= #{id}
+
diff --git a/exam-admin/src/main/resources/mapper/payment/PaymentInfoMapper.xml b/exam-admin/src/main/resources/mapper/payment/PaymentInfoMapper.xml
new file mode 100644
index 0000000..7fed418
--- /dev/null
+++ b/exam-admin/src/main/resources/mapper/payment/PaymentInfoMapper.xml
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ insert into payment_info
+
+ 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,
+
+
+
+ #{userId},
+
+ #{examRegistrationId},
+
+ #{paymentTitle},
+
+ #{transactionNo},
+
+ #{payPrice},
+
+ #{payType},
+
+ #{payChannel},
+
+ #{wechatTradeState},
+
+ #{alipayTradeState},
+
+ #{payNotifyContent},
+
+ #{payTime},
+
+ #{paymentMerchantType},
+
+ #{delFlag},
+
+ #{createBy},
+
+ #{createTime},
+
+ #{updateBy},
+
+ #{updateTime},
+
+
+
+
+
+ update payment_info
+
+ user_id =
+ #{userId},
+
+ exam_registration_id =
+ #{examRegistrationId},
+
+ payment_title =
+ #{paymentTitle},
+
+ transaction_no =
+ #{transactionNo},
+
+ pay_price =
+ #{payPrice},
+
+ pay_type =
+ #{payType},
+
+ pay_channel =
+ #{payChannel},
+
+ wechat_trade_state =
+ #{wechatTradeState},
+
+ alipay_trade_state =
+ #{alipayTradeState},
+
+ pay_notify_content =
+ #{payNotifyContent},
+
+ pay_time =
+ #{payTime},
+
+ payment_merchant_type =
+ #{paymentMerchantType},
+
+ del_flag =
+ #{delFlag},
+
+ create_by =
+ #{createBy},
+
+ create_time =
+ #{createTime},
+
+ update_by =
+ #{updateBy},
+
+ update_time =
+ #{updateTime},
+
+
+ where id = #{id}
+
+
+
+ delete
+ from payment_info
+ where id = #{id}
+
+
+
+ delete from payment_info where id in
+
+ #{id}
+
+
+
+
+
diff --git a/exam-admin/src/main/resources/mapper/payment/RefundInfoMapper.xml b/exam-admin/src/main/resources/mapper/payment/RefundInfoMapper.xml
new file mode 100644
index 0000000..c437579
--- /dev/null
+++ b/exam-admin/src/main/resources/mapper/payment/RefundInfoMapper.xml
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ insert into refund_info
+
+ 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,
+
+
+
+ #{userId},
+
+ #{examRegistrationId},
+
+ #{refundNo},
+
+ #{outRefundNo},
+
+ #{transactionNo},
+
+ #{refundReason},
+
+ #{refundType},
+
+ #{wechatRefundStatus},
+
+ #{alipayRefundStatus},
+
+ #{orderTotalPrice},
+
+ #{refundPrice},
+
+ #{currency},
+
+ #{channel},
+
+ #{userreceivedaccount},
+
+ #{successTime},
+
+ #{refundNotifyContent},
+
+ #{applyRefundReturnContent},
+
+ #{refundMerchantType},
+
+ #{delFlag},
+
+ #{createBy},
+
+ #{createTime},
+
+ #{updateBy},
+
+ #{updateTime},
+
+
+
+
+
+ update refund_info
+
+ user_id =
+ #{userId},
+
+ exam_registration_id =
+ #{examRegistrationId},
+
+ refund_no =
+ #{refundNo},
+
+ out_refund_no =
+ #{outRefundNo},
+
+ transaction_no =
+ #{transactionNo},
+
+ refund_reason =
+ #{refundReason},
+
+ refund_type =
+ #{refundType},
+
+ wechat_refund_status =
+ #{wechatRefundStatus},
+
+ alipay_refund_status =
+ #{alipayRefundStatus},
+
+ order_total_price =
+ #{orderTotalPrice},
+
+ refund_price =
+ #{refundPrice},
+
+ currency =
+ #{currency},
+
+ channel =
+ #{channel},
+
+ userReceivedAccount =
+ #{userreceivedaccount},
+
+ success_time =
+ #{successTime},
+
+ refund_notify_content =
+ #{refundNotifyContent},
+
+ apply_refund_return_content =
+ #{applyRefundReturnContent},
+
+ refund_merchant_type =
+ #{refundMerchantType},
+
+ del_flag =
+ #{delFlag},
+
+ create_by =
+ #{createBy},
+
+ create_time =
+ #{createTime},
+
+ update_by =
+ #{updateBy},
+
+ update_time =
+ #{updateTime},
+
+
+ where id = #{id}
+
+
+
+ delete
+ from refund_info
+ where id = #{id}
+
+
+
+ delete from refund_info where id in
+
+ #{id}
+
+
+
+
+ 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 <> #{wechatRefundStatus}
+
+
+
+ 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 <> #{weChatRefundStatus}
+
+
+
+