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} + + + +