From 3b6c9994dd906cece1e5209e3f7e4b60a141d753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E5=AF=92?= <2533659732@qq.com> Date: Mon, 16 Oct 2023 17:00:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=95=86=E5=9F=8E=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=BB=A3=E7=A0=81=E7=A7=BB=E6=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/xinelu/common/enums/PayTypeEnum.java | 30 + .../common/enums/PaymentMerchantTypeEnum.java | 30 + .../xinelu/common/enums/RefundStatusEnum.java | 41 ++ .../xinelu/common/enums/RefundTypeEnum.java | 30 + .../common/enums/WeChatTradeStateEnum.java | 52 ++ .../xinelu/common/utils/http/HttpUtils.java | 32 + .../WeChatPaymentController.java | 159 +++++ .../service/goodstock/GoodsStockService.java | 27 + .../goodstock/impl/GoodsStockServiceImpl.java | 102 ++++ .../WeChatPayNotifyService.java | 53 ++ .../WeChatPaymentService.java | 82 +++ .../WeChatRefundService.java | 41 ++ .../impl/WeChatPayNotifyServiceImpl.java | 486 +++++++++++++++ .../impl/WeChatPaymentServiceImpl.java | 565 ++++++++++++++++++ .../impl/WeChatRefundServiceImpl.java | 451 ++++++++++++++ .../com/xinelu/applet/utils/WeChatUtil.java | 35 ++ .../vo/wechatpaymentinfo/dto/PaymentDTO.java | 67 +++ .../vo/wechatpaymentinfo/dto/RefundDTO.java | 57 ++ .../vo/OrderStatusInfoVO.java | 22 + .../wechatpaymentinfo/vo/WeChatAppSignVO.java | 34 ++ .../vo/WeChatAppletSignVO.java | 30 + .../vo/WeChatPayNotifyCiphertextVO.java | 57 ++ .../vo/WeChatPayNotifyPlaintextVO.java | 146 +++++ .../vo/WeChatQueryOrderVO.java | 82 +++ .../vo/WeChatRefundInfoVO.java | 140 +++++ .../vo/WeChatRefundNotifyVO.java | 61 ++ .../domain/paymentinfo/PaymentInfo.java | 156 +++++ .../manage/domain/refundinfo/RefundInfo.java | 205 +++++++ .../mapper/paymentinfo/PaymentInfoMapper.java | 70 +++ .../mapper/refundinfo/RefundInfoMapper.java | 90 +++ .../manage/paymentinfo/PaymentInfoMapper.xml | 263 ++++++++ .../manage/refundinfo/RefundInfoMapper.xml | 356 +++++++++++ xinelu-quartz/pom.xml | 9 + .../controller/CouponTaskController.java | 30 + .../controller/PaymentInfoTaskController.java | 42 ++ .../controller/RefundInfoTaskController.java | 32 + .../quartz/service/CouponTaskService.java | 15 + .../service/PaymentInfoTaskService.java | 24 + .../quartz/service/RefundInfoTaskService.java | 18 + .../service/impl/CouponTaskServiceImpl.java | 64 ++ .../impl/PaymentInfoTaskServiceImpl.java | 386 ++++++++++++ .../impl/RefundInfoTaskServiceImpl.java | 164 +++++ .../com/xinelu/quartz/task/CouponTask.java | 28 + .../xinelu/quartz/task/PaymentInfoTask.java | 45 ++ .../xinelu/quartz/task/RefundInfoTask.java | 33 + 45 files changed, 4942 insertions(+) create mode 100644 xinelu-common/src/main/java/com/xinelu/common/enums/PayTypeEnum.java create mode 100644 xinelu-common/src/main/java/com/xinelu/common/enums/PaymentMerchantTypeEnum.java create mode 100644 xinelu-common/src/main/java/com/xinelu/common/enums/RefundStatusEnum.java create mode 100644 xinelu-common/src/main/java/com/xinelu/common/enums/RefundTypeEnum.java create mode 100644 xinelu-common/src/main/java/com/xinelu/common/enums/WeChatTradeStateEnum.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/wechatpaymentinfo/WeChatPaymentController.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/GoodsStockService.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/impl/GoodsStockServiceImpl.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPayNotifyService.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPaymentService.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatRefundService.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPayNotifyServiceImpl.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPaymentServiceImpl.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatRefundServiceImpl.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/utils/WeChatUtil.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/PaymentDTO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/RefundDTO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/OrderStatusInfoVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppSignVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppletSignVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyCiphertextVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyPlaintextVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatQueryOrderVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundInfoVO.java create mode 100644 xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundNotifyVO.java create mode 100644 xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/paymentinfo/PaymentInfo.java create mode 100644 xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/refundinfo/RefundInfo.java create mode 100644 xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/paymentinfo/PaymentInfoMapper.java create mode 100644 xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/refundinfo/RefundInfoMapper.java create mode 100644 xinelu-nurse-manage/src/main/resources/mapper/manage/paymentinfo/PaymentInfoMapper.xml create mode 100644 xinelu-nurse-manage/src/main/resources/mapper/manage/refundinfo/RefundInfoMapper.xml create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/controller/CouponTaskController.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/controller/PaymentInfoTaskController.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/controller/RefundInfoTaskController.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/CouponTaskService.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/PaymentInfoTaskService.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/RefundInfoTaskService.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/CouponTaskServiceImpl.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/PaymentInfoTaskServiceImpl.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/RefundInfoTaskServiceImpl.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/task/CouponTask.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/task/PaymentInfoTask.java create mode 100644 xinelu-quartz/src/main/java/com/xinelu/quartz/task/RefundInfoTask.java diff --git a/xinelu-common/src/main/java/com/xinelu/common/enums/PayTypeEnum.java b/xinelu-common/src/main/java/com/xinelu/common/enums/PayTypeEnum.java new file mode 100644 index 0000000..4679dbd --- /dev/null +++ b/xinelu-common/src/main/java/com/xinelu/common/enums/PayTypeEnum.java @@ -0,0 +1,30 @@ +package com.xinelu.common.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/xinelu-common/src/main/java/com/xinelu/common/enums/PaymentMerchantTypeEnum.java b/xinelu-common/src/main/java/com/xinelu/common/enums/PaymentMerchantTypeEnum.java new file mode 100644 index 0000000..9a2f4d6 --- /dev/null +++ b/xinelu-common/src/main/java/com/xinelu/common/enums/PaymentMerchantTypeEnum.java @@ -0,0 +1,30 @@ +package com.xinelu.common.enums; + +import lombok.Getter; + +/** + * @Description 支付账户枚举 + * @Author 纪寒 + * @Date 2022-10-20 17:48:32 + * @Version 1.0 + */ +@Getter +public enum PaymentMerchantTypeEnum { + + /** + * 新医路 + */ + XINYILU("XINYILU"), + + /** + * 医路优品 + */ + YILUYOUPIN("YILUYOUPIN"), + ; + + final private String info; + + PaymentMerchantTypeEnum(String info) { + this.info = info; + } +} diff --git a/xinelu-common/src/main/java/com/xinelu/common/enums/RefundStatusEnum.java b/xinelu-common/src/main/java/com/xinelu/common/enums/RefundStatusEnum.java new file mode 100644 index 0000000..7df7d78 --- /dev/null +++ b/xinelu-common/src/main/java/com/xinelu/common/enums/RefundStatusEnum.java @@ -0,0 +1,41 @@ +package com.xinelu.common.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/xinelu-common/src/main/java/com/xinelu/common/enums/RefundTypeEnum.java b/xinelu-common/src/main/java/com/xinelu/common/enums/RefundTypeEnum.java new file mode 100644 index 0000000..bc049d3 --- /dev/null +++ b/xinelu-common/src/main/java/com/xinelu/common/enums/RefundTypeEnum.java @@ -0,0 +1,30 @@ +package com.xinelu.common.enums; + +import lombok.Getter; + +/** + * @Description 退款类型枚举 + * @Author 纪寒 + * @Date 2022-10-18 15:16:02 + * @Version 1.0 + */ +@Getter +public enum RefundTypeEnum { + + /** + * 微信 + */ + WE_CHAT("WE_CHAT"), + + /** + * 支付宝 + */ + ALI_PAY("ALI_PAY"), + ; + + final private String info; + + RefundTypeEnum(String info) { + this.info = info; + } +} diff --git a/xinelu-common/src/main/java/com/xinelu/common/enums/WeChatTradeStateEnum.java b/xinelu-common/src/main/java/com/xinelu/common/enums/WeChatTradeStateEnum.java new file mode 100644 index 0000000..009a8b5 --- /dev/null +++ b/xinelu-common/src/main/java/com/xinelu/common/enums/WeChatTradeStateEnum.java @@ -0,0 +1,52 @@ +package com.xinelu.common.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/xinelu-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java b/xinelu-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java index 216d1d1..f22b454 100644 --- a/xinelu-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java +++ b/xinelu-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java @@ -16,6 +16,7 @@ import org.apache.http.HttpEntity; import org.apache.http.impl.client.CloseableHttpClient; import javax.net.ssl.*; +import javax.servlet.http.HttpServletRequest; import java.io.*; import java.net.ConnectException; import java.net.SocketTimeoutException; @@ -253,4 +254,35 @@ public class HttpUtils { } return result; } + + /** + * 将通知参数转化为字符串 + * + * @param request 请求信息 + * @return 请求参数信息 + */ + public static String readRequestData(HttpServletRequest request) { + BufferedReader br = null; + try { + StringBuilder result = new StringBuilder(); + br = request.getReader(); + for (String line; (line = br.readLine()) != null; ) { + if (result.length() > 0) { + result.append("\n"); + } + result.append(line); + } + return result.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } } diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/wechatpaymentinfo/WeChatPaymentController.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/wechatpaymentinfo/WeChatPaymentController.java new file mode 100644 index 0000000..e40adef --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/wechatpaymentinfo/WeChatPaymentController.java @@ -0,0 +1,159 @@ +package com.xinelu.applet.controller.wechatpaymentinfo; + +import com.xinelu.applet.service.wechatpaymentinfo.WeChatPayNotifyService; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatPaymentService; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatRefundService; +import com.xinelu.applet.vo.wechatpaymentinfo.dto.PaymentDTO; +import com.xinelu.applet.vo.wechatpaymentinfo.dto.RefundDTO; +import com.xinelu.common.annotation.MobileRequestAuthorization; +import com.xinelu.common.core.controller.BaseController; +import com.xinelu.common.core.domain.AjaxResult; +import com.xinelu.common.custominterface.Insert; +import com.xinelu.common.enums.BuySourceEnum; +import com.xinelu.common.exception.ServiceException; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +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.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.math.BigDecimal; +import java.util.Objects; + +/** + * @Description 微信小程序和App支付控制器 + * @Author 纪寒 + * @Date 2022-10-18 14:47:11 + * @Version 1.0 + */ +@RestController +@RequestMapping("/nurseApp/weChatPayment") +public class WeChatPaymentController extends BaseController { + + @Resource + private WeChatPaymentService weChatPaymentService; + @Resource + private WeChatPayNotifyService weChatPayNotifyService; + @Resource + private WeChatRefundService weChatRefundService; + + /** + * 微信小程序购买商品和健康咨询支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付返回结果 + * @throws Exception 异常信息 + */ + @MobileRequestAuthorization + @PostMapping("/appletGoodsOrderPay") + public AjaxResult appletGoodsOrderPay(@Validated(Insert.class) @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.appletGoodsOrderPay(paymentDTO); + } + + /** + * 微信小程序预约服务订单支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付返回结果信息 + * @throws Exception 异常信息 + */ + @MobileRequestAuthorization + @PostMapping("/appletAppointmentOrderPay") + public AjaxResult appletAppointmentOrderPay(@Validated(Insert.class) @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 (!BuySourceEnum.NURSE_STATION.getInfo().equals(paymentDTO.getBuySource())) { + return AjaxResult.error("当前订单非预约服务项,请重新预约!"); + } + if (Objects.nonNull(paymentDTO.getPaymentPrice()) && paymentDTO.getPaymentPrice().compareTo(BigDecimal.ZERO) < 0) { + return AjaxResult.error("订单支付金额不能为负数,无法支付!"); + } + return weChatPaymentService.appletAppointmentOrderPay(paymentDTO); + } + + /** + * 新医路微信支付回调通知接口 + * + * @param request 请求头信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @PostMapping("/xylWeChatPayNotify") + public String xylWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + return weChatPayNotifyService.xylWeChatPayNotify(request, response); + } + + /** + * 医路优品微信支付回调通知接口 + * + * @param request 请求头信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @PostMapping("/ylypWeChatPayNotify") + public String ylypWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + return weChatPayNotifyService.ylypWeChatPayNotify(request, response); + } + + /** + * 微信确认退款接口 + * + * @param refundDTO 退款参数 + * @return 退款申请结果 + * @throws Exception 异常信息 + */ + @PostMapping("/weChatRefundOrderApply") + public AjaxResult weChatRefundOrderApply(@Validated(Insert.class) @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); + } + + /** + * 医路优品微信退款回调通知接口 + * + * @param request 请求头信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @PostMapping("/ylypWeChatRefundNotify") + public String ylypWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + return weChatPayNotifyService.ylypWeChatRefundNotify(request, response); + } + +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/GoodsStockService.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/GoodsStockService.java new file mode 100644 index 0000000..e781d9b --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/GoodsStockService.java @@ -0,0 +1,27 @@ +package com.xinelu.applet.service.goodstock; + +/** + * @Description 库存公共服务业务层 + * @Author 纪寒 + * @Date 2022-10-24 13:56:40 + * @Version 1.0 + */ +public interface GoodsStockService { + + /** + * 新增库存数量服务 + * + * @param orderNo 订单编号 + * @param goodsAttributeDetailsId 商品属性明细id + * @param stockNum 商品数量 + */ + void addGoodsStockInfo(String orderNo, Long goodsAttributeDetailsId, Integer stockNum); + + /** + * 减少库存数量服务 + * + * @param orderNo 订单编号 + * @param goodsAttributeDetailsId 商品属性明细id + */ + void reduceGoodsStockInfo(String orderNo, Long goodsAttributeDetailsId); +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/impl/GoodsStockServiceImpl.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/impl/GoodsStockServiceImpl.java new file mode 100644 index 0000000..8b919e0 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/goodstock/impl/GoodsStockServiceImpl.java @@ -0,0 +1,102 @@ +package com.xinelu.applet.service.goodstock.impl; + +import com.xinelu.applet.service.goodstock.GoodsStockService; +import com.xinelu.common.constant.Constants; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.common.utils.RedisDistributedLockUtils; +import com.xinelu.manage.domain.goodsOrderDetails.GoodsOrderDetails; +import com.xinelu.manage.mapper.goodsAttributeDetails.GoodsAttributeDetailsMapper; +import com.xinelu.manage.mapper.goodsOrderDetails.GoodsOrderDetailsMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Objects; + +/** + * @Description 商品库存公共服务业务层实现类 + * @Author 纪寒 + * @Date 2022-10-24 13:57:28 + * @Version 1.0 + */ +@Slf4j +@Service +public class GoodsStockServiceImpl implements GoodsStockService { + + @Resource + private RedisDistributedLockUtils redisDistributedLockUtils; + @Resource + private GoodsOrderDetailsMapper goodsOrderDetailsMapper; + @Resource + private GoodsAttributeDetailsMapper goodsAttributeDetailsMapper; + + /** + * 新增库存数量服务 + * + * @param orderNo 订单编号 + * @param goodsAttributeDetailsId 商品属性明细id + * @param stockNum 商品数量 + */ + @Override + public void addGoodsStockInfo(String orderNo, Long goodsAttributeDetailsId, Integer stockNum) { + if (StringUtils.isBlank(orderNo)) { + throw new ServiceException("订单编号不能为空!"); + } + if (Objects.isNull(goodsAttributeDetailsId)) { + throw new ServiceException("商品属性明细id不能为空!"); + } + String orderNoLockKey = Constants.ADD_GOODS_STOCK_KEY + orderNo + "_" + goodsAttributeDetailsId; + boolean tryLock = redisDistributedLockUtils.tryLock(orderNoLockKey, 5); + if (!tryLock) { + return; + } + try { + int updateCount = goodsAttributeDetailsMapper.addGoodsStockCount(goodsAttributeDetailsId, stockNum); + if (updateCount < 1) { + log.info("新增库存数量失败,订单编号:{},商品属性明细id:{},数量数量:{}", orderNo, goodsAttributeDetailsId, stockNum); + } + } catch (Exception e) { + log.error("新增库存信息失败,失败原因为 ====> {}", e.getMessage()); + throw new ServiceException(e.getMessage()); + } finally { + redisDistributedLockUtils.unlock(orderNoLockKey); + } + } + + /** + * 减少库存数量服务 + * + * @param orderNo 订单编号 + * @param goodsAttributeDetailsId 商品属性明细id + */ + @Override + public void reduceGoodsStockInfo(String orderNo, Long goodsAttributeDetailsId) { + if (StringUtils.isBlank(orderNo)) { + throw new ServiceException("订单编号不能为空!"); + } + if (Objects.isNull(goodsAttributeDetailsId)) { + throw new ServiceException("商品属性明细id不能为空!"); + } + GoodsOrderDetails goodsOrderDetailInfo = goodsOrderDetailsMapper.getGoodsOrderDetailInfo(orderNo, goodsAttributeDetailsId); + if (Objects.isNull(goodsOrderDetailInfo)) { + return; + } + String orderNoLockKey = Constants.REDUCE_GOODS_STOCK_KEY + orderNo + "_" + goodsAttributeDetailsId; + boolean tryLock = redisDistributedLockUtils.tryLock(orderNoLockKey, 5); + if (!tryLock) { + return; + } + try { + int updateCount = goodsAttributeDetailsMapper.reduceGoodsStockCount(goodsAttributeDetailsId, goodsOrderDetailInfo.getGoodsCount()); + if (updateCount < 1) { + log.info("减少库存数量失败,订单编号:{},商品属性明细id:{},数量数量:{}", orderNo, goodsAttributeDetailsId, goodsOrderDetailInfo.getGoodsCount()); + } + } catch (Exception e) { + log.error("减少库存信息失败,失败原因为 ====> {}", e.getMessage()); + throw new ServiceException(e.getMessage()); + } finally { + redisDistributedLockUtils.unlock(orderNoLockKey); + } + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPayNotifyService.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPayNotifyService.java new file mode 100644 index 0000000..1ede360 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPayNotifyService.java @@ -0,0 +1,53 @@ +package com.xinelu.applet.service.wechatpaymentinfo; + +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 ylypWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception; + + /** + * 新医路退款回调接口 + * + * @param request 请求信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + String xylWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception; + + /** + * 医路优品退款回调接口 + * + * @param request 请求信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + String ylypWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPaymentService.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPaymentService.java new file mode 100644 index 0000000..75bdf05 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatPaymentService.java @@ -0,0 +1,82 @@ +package com.xinelu.applet.service.wechatpaymentinfo; + +import com.xinelu.applet.vo.wechatpaymentinfo.dto.PaymentDTO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.OrderStatusInfoVO; +import com.xinelu.common.core.domain.AjaxResult; + +/** + * @Description 微信小程序和App支付业务层 + * @Author 纪寒 + * @Date 2022-10-18 15:02:27 + * @Version 1.0 + */ +public interface WeChatPaymentService { + + /** + * 微信小程序购买商品和健康咨询支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付结果 + * @throws Exception 异常信息 + */ + AjaxResult appletGoodsOrderPay(PaymentDTO paymentDTO) throws Exception; + + /** + * 微信小程序预约服务订单支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付结果 + * @throws Exception 异常信息 + */ + AjaxResult appletAppointmentOrderPay(PaymentDTO paymentDTO) throws Exception; + + /** + * 取消订单接口 + * + * @param orderNo 点单编号 + * @param buySource 购买来源 + * @return 取消结果信息 + * @throws Exception 异常信息 + */ + AjaxResult cancelOrderInfo(String orderNo, String buySource) throws Exception; + + /** + * 查询订单状态信息 + * + * @param orderNo 点单编号 + * @param buySource 购买来源 + * @return 结果信息 + * @throws Exception 异常信息 + */ + AjaxResult getOrderStatusInfo(String orderNo, String buySource) throws Exception; + + /** + * 微信关闭订单方法 + * + * @param orderNo 订单编号 + * @param buySource 购买来源 + * @return 状态码 + * @throws Exception 异常信息 + */ + int closeWeChatOrderInfo(String orderNo, String buySource) throws Exception; + + /** + * 查询预约服务订单信息 + * + * @param orderNo 订单编号 + * @param vo 返回值信息 + * @return 订单状态标识 + */ + OrderStatusInfoVO queryAppointmentOrderStatus(String orderNo, OrderStatusInfoVO vo); + + /** + * 查询上商品订单信息 + * + * @param orderNo 订单编号 + * @param buySource 购买来源 + * @param vo 返回值信息 + * @return 订单状态标识 + * @throws Exception 异常信息 + */ + OrderStatusInfoVO queryGoodsOrderStatus(String orderNo, OrderStatusInfoVO vo, String buySource) throws Exception; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatRefundService.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatRefundService.java new file mode 100644 index 0000000..966dd61 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/WeChatRefundService.java @@ -0,0 +1,41 @@ +package com.xinelu.applet.service.wechatpaymentinfo; + +import com.xinelu.applet.vo.wechatpaymentinfo.dto.RefundDTO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatRefundInfoVO; +import com.xinelu.common.core.domain.AjaxResult; + +/** + * @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/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPayNotifyServiceImpl.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPayNotifyServiceImpl.java new file mode 100644 index 0000000..c530ea7 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPayNotifyServiceImpl.java @@ -0,0 +1,486 @@ +package com.xinelu.applet.service.wechatpaymentinfo.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.xinelu.applet.service.wechatpaymentinfo.WeChatPayNotifyService; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatPayNotifyPlaintextVO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatRefundNotifyVO; +import com.xinelu.common.config.XylWeChatPaymentConfig; +import com.xinelu.common.config.YlypWeChatPaymentConfig; +import com.xinelu.common.constant.Constants; +import com.xinelu.common.enums.*; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.common.utils.RedisDistributedLockUtils; +import com.xinelu.common.utils.http.HttpUtils; +import com.xinelu.manage.domain.appointmentorder.AppointmentOrder; +import com.xinelu.manage.domain.goodsOrder.GoodsOrder; +import com.xinelu.manage.domain.paymentinfo.PaymentInfo; +import com.xinelu.manage.domain.refundinfo.RefundInfo; +import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper; +import com.xinelu.manage.mapper.goodsAttributeDetails.GoodsAttributeDetailsMapper; +import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper; +import com.xinelu.manage.mapper.patientcouponreceive.PatientCouponReceiveMapper; +import com.xinelu.manage.mapper.paymentinfo.PaymentInfoMapper; +import com.xinelu.manage.mapper.refundinfo.RefundInfoMapper; +import com.xinelu.manage.vo.AppointOrderAndDetailsInfo; +import com.xinelu.manage.vo.appointmentorder.AppointmentOrderInfoVO; +import com.xinelu.manage.vo.goods.GoodsOrderAndDetailsInfo; +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; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @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 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 GoodsAttributeDetailsMapper goodsAttributeDetailsMapper; + @Resource + private RefundInfoMapper refundInfoMapper; + @Resource(name = "xylVerifier") + private Verifier xylVerifier; + @Resource(name = "ylypVerifier") + private Verifier ylypVerifier; + @Resource + private RedisTemplate redisTemplate; + @Resource + private PatientCouponReceiveMapper patientCouponReceiveMapper; + + /** + * 新医路账户标识 + */ + private static final String XINYILU_ACCOUNT = "XINYILU"; + + /** + * 新医路账户标识 + */ + 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, PaymentMerchantTypeEnum.XINYILU.getInfo(), PAY); + } + + /** + * 医路优品支付回调接口 + * + * @param request 请求信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @Override + public String ylypWeChatPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + log.info("医路优品微信支付回调开始执行"); + return weChatPayNotifyInfo(request, response, PaymentMerchantTypeEnum.YILUYOUPIN.getInfo(), PAY); + } + + /** + * 新医路退款回调接口 + * + * @param request 请求信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @Override + public String xylWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + log.info("新医路微信退款回调开始执行"); + return weChatPayNotifyInfo(request, response, PaymentMerchantTypeEnum.XINYILU.getInfo(), REFUND); + } + + /** + * 医路优品退款回调接口 + * + * @param request 请求信息 + * @param response 响应信息 + * @return 应答信息,避免微信平台重复发送回调通知 + * @throws Exception 异常信息 + */ + @Override + public String ylypWeChatRefundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { + log.info("医路优品微信退款回调开始执行"); + return weChatPayNotifyInfo(request, response, PaymentMerchantTypeEnum.YILUYOUPIN.getInfo(), REFUND); + } + + /** + * 微信支付回调通知公共方法 + * + * @param request 请求信息 + * @param response 响应信息 + * @param accountFlag 账户标识,用于区分是新医路还是医路优品 + * @param refundAndPaymentFlag 支付和回调标识 + * @return 返回信息 + * @throws Exception 异常信息 + */ + private String weChatPayNotifyInfo(HttpServletRequest request, HttpServletResponse response, + String accountFlag, String refundAndPaymentFlag) throws Exception { + Map resultMap = new HashMap<>(); + String body = HttpUtils.readRequestData(request); + if (StringUtils.isBlank(body)) { + log.error(accountFlag + "微信回调通知失败,请求体 ====> {}", body); + response.setStatus(500); + resultMap.put("code", "ERROR"); + resultMap.put("message", "通知验签失败"); + return JSON.toJSONString(resultMap); + } + String nonce = request.getHeader("Wechatpay-Nonce"); + String timestamp = request.getHeader("Wechatpay-Timestamp"); + String signature = request.getHeader("Wechatpay-Signature"); + String serialNo = request.getHeader("Wechatpay-Serial"); + Notification notification = null; + if (XINYILU_ACCOUNT.equals(accountFlag)) { + 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 (YILUYOUPIN_ACCOUNT.equals(accountFlag)) { + NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNo) + .withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build(); + NotificationHandler handler = new NotificationHandler(ylypVerifier, ylypWeChatPaymentConfig.getYlypPaymentKey().getBytes(StandardCharsets.UTF_8)); + notification = handler.parse(notificationRequest); + } + if (Objects.isNull(notification)) { + log.error(accountFlag + "微信通知验签失败,请求体 ====> {}", 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 = ""; + if (XINYILU_ACCOUNT.equals(accountFlag)) { + 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 (YILUYOUPIN_ACCOUNT.equals(accountFlag)) { + AesUtil aesUtil = new AesUtil(ylypWeChatPaymentConfig.getYlypPaymentKey().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) { + 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 { + //商品订单信息 + GoodsOrder goodsOrderInfo = goodsOrderMapper.getGoodsOrderByOrderNo(notifyPlaintext.getOutTradeNo()); + if (Objects.nonNull(goodsOrderInfo)) { + insertPaymentInfo(notifyPlaintext, goodsOrderInfo, null, plainText); + if (WeChatTradeStateEnum.SUCCESS.getInfo().equals(notifyPlaintext.getTradeState())) { + goodsOrderMapper.updateGoodsOrderStatus(GooodsOrderStatusEnum.WAIT_RECEIVED_GOODS.getInfo(), goodsOrderInfo.getOrderNo()); + } else { + log.info("商品订单微信订单状态异常,订单状态为 ====> {}", notifyPlaintext.getTradeState()); + } + transactionManager.commit(transactionStatus); + return; + } + AppointmentOrderInfoVO appointmentOrderInfo = appointmentOrderMapper.getAppointmentOrderInfoByOrderNo(notifyPlaintext.getOutTradeNo()); + if (Objects.isNull(appointmentOrderInfo)) { + return; + } + //预约服务订单信息 + insertPaymentInfo(notifyPlaintext, null, appointmentOrderInfo, plainText); + if (WeChatTradeStateEnum.SUCCESS.getInfo().equals(notifyPlaintext.getTradeState())) { + appointmentOrderMapper.updateAppointmentOrderStatus(OrderStatusEnum.WAIT_DISPATCH.getInfo(), appointmentOrderInfo.getOrderNo()); + } else { + log.info("预约服务微信订单状态异常,订单状态为 ====> {}", notifyPlaintext.getTradeState()); + } + transactionManager.commit(transactionStatus); + } catch (Exception e) { + transactionManager.rollback(transactionStatus); + log.error("微信支付回调失败,失败原因:{}", e.getMessage()); + throw e; + } finally { + redisDistributedLockUtils.unlock(orderNoLockKey); + log.info("支付回调释放锁完成..........."); + } + } + + /** + * 记录支付日志信息 + * + * @param notifyPlaintext 回调信息 + * @param goodsOrderInfo 商品订单信息 + * @param appointmentOrder 预约订单信息 + * @param plainText 回调明文 + */ + private void insertPaymentInfo(WeChatPayNotifyPlaintextVO notifyPlaintext, GoodsOrder goodsOrderInfo, + AppointmentOrder appointmentOrder, String plainText) { + int paymentInfoCount = paymentInfoMapper.getPaymentInfoByOrderNo(notifyPlaintext.getOutTradeNo()); + if (paymentInfoCount <= 0) { + PaymentInfo paymentInfo = buildPaymentInfo(notifyPlaintext, goodsOrderInfo, appointmentOrder, plainText); + int insertCount = paymentInfoMapper.insertPaymentInfo(paymentInfo); + if (insertCount <= 0) { + log.error("记录支付日志出错,参数为 ====> [{}]", paymentInfo); + throw new ServiceException("记录支付日志出错,请联系管理员!"); + } + } + } + + /** + * 构建支付信息参数 + * + * @param notifyPlaintext 通知明文信息 + * @param goodsOrderInfo 商品订单信息 + * @param appointmentOrderInfo 预约订单信息 + */ + private PaymentInfo buildPaymentInfo(WeChatPayNotifyPlaintextVO notifyPlaintext, GoodsOrder goodsOrderInfo, + AppointmentOrder appointmentOrderInfo, String plainText) { + PaymentInfo paymentInfo = new PaymentInfo(); + if (Objects.nonNull(goodsOrderInfo)) { + paymentInfo.setPatientId(Objects.isNull(goodsOrderInfo.getPatientId()) ? null : goodsOrderInfo.getPatientId()); + paymentInfo.setOrderNo(StringUtils.isBlank(goodsOrderInfo.getOrderNo()) ? "" : goodsOrderInfo.getOrderNo()); + paymentInfo.setPayChannel(StringUtils.isBlank(goodsOrderInfo.getOrderChannel()) ? "" : goodsOrderInfo.getOrderChannel()); + } else if (Objects.nonNull(appointmentOrderInfo)) { + paymentInfo.setPatientId(Objects.isNull(appointmentOrderInfo.getPatientId()) ? null : appointmentOrderInfo.getPatientId()); + paymentInfo.setOrderNo(StringUtils.isBlank(appointmentOrderInfo.getOrderNo()) ? "" : appointmentOrderInfo.getOrderNo()); + paymentInfo.setPayChannel(StringUtils.isBlank(appointmentOrderInfo.getOrderChannel()) ? "" : appointmentOrderInfo.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)); + if (notifyPlaintext.getMchid().equals(xylWeChatPaymentConfig.getXylMchId())) { + paymentInfo.setPaymentMerchantType(PaymentMerchantTypeEnum.XINYILU.getInfo()); + } + if (notifyPlaintext.getMchid().equals(ylypWeChatPaymentConfig.getYlypMchId())) { + paymentInfo.setPaymentMerchantType(PaymentMerchantTypeEnum.YILUYOUPIN.getInfo()); + } + paymentInfo.setDelFlag(0); + paymentInfo.setCreateTime(LocalDateTime.now()); + return paymentInfo; + } + + /** + * 修改订单状态以及减少库存信息 + * + * @param plainText 支付验签以后的通知内容 + */ + private void processRefundInfo(String 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 { + GoodsOrderAndDetailsInfo detailsOrderInfo = goodsOrderMapper.getGoodsAndDetailsOrderInfo(refundNotifyVO.getOutTradeNo()); + AppointOrderAndDetailsInfo appointmentOrderInfo = appointmentOrderMapper.getAppointOrderAndDetailsInfo(refundNotifyVO.getOutTradeNo()); + if (Objects.isNull(detailsOrderInfo) && Objects.isNull(appointmentOrderInfo)) { + return; + } + if (Objects.nonNull(appointmentOrderInfo)) { + updateAppointmentInfo(appointmentOrderInfo, plainText, refundNotifyVO); + if (Objects.nonNull(appointmentOrderInfo.getAppointmentLimitCount()) && StringUtils.equals(AppointmentOrderTypeEnum.OTHER.getInfo(), appointmentOrderInfo.getOrderType())) { + String appointLimitCountKey = Constants.PRE_APPOINTMENT_LIMIT_COUNT_KEY + appointmentOrderInfo.getNurseStationItemId() + "_" + appointmentOrderInfo.getServiceDate() + "_" + appointmentOrderInfo.getServiceStartTime(); + redisAtomicLong = new RedisAtomicLong(appointLimitCountKey, Objects.requireNonNull(redisTemplate.getConnectionFactory())); + if (redisAtomicLong.get() >= 0 && redisAtomicLong.get() < appointmentOrderInfo.getAppointmentLimitCount()) { + redisAtomicLong.incrementAndGet(); + } + } + } + if (Objects.nonNull(detailsOrderInfo)) { + updateGoodsInfo(detailsOrderInfo, 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 appointmentOrderInfo 预约订单信息 + * @param plainText 明文 + * @param refundNotifyVO 退款回调信息 + */ + private void updateAppointmentInfo(AppointmentOrder appointmentOrderInfo, String plainText, WeChatRefundNotifyVO refundNotifyVO) { + if (RefundStatusEnum.SUCCESS.getInfo().equals(refundNotifyVO.getRefundStatus()) + && StringUtils.isNotBlank(appointmentOrderInfo.getOrderStatus()) + && !(OrderStatusEnum.REFUNDED.getInfo().equals(appointmentOrderInfo.getOrderStatus()))) { + int updateCount = appointmentOrderMapper.updateAppointmentOrderStatus(OrderStatusEnum.REFUNDED.getInfo(), appointmentOrderInfo.getOrderNo()); + 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 detailsOrderInfo 商品订单信息 + * @param plainText 明文 + * @param refundNotifyVO 退款回调信息 + */ + private void updateGoodsInfo(GoodsOrderAndDetailsInfo detailsOrderInfo, String plainText, WeChatRefundNotifyVO refundNotifyVO) { + if (RefundStatusEnum.SUCCESS.getInfo().equals(refundNotifyVO.getRefundStatus()) + && StringUtils.isNotBlank(detailsOrderInfo.getOrderStatus()) + && !(GooodsOrderStatusEnum.REFUNDED.getInfo().equals(detailsOrderInfo.getOrderStatus()))) { + int updateGoodsOrderCount = goodsOrderMapper.updateGoodsOrderStatus(OrderStatusEnum.REFUNDED.getInfo(), detailsOrderInfo.getOrderNo()); + if (updateGoodsOrderCount < 1) { + log.info("微信退款通知,修改商品单状态失败,订单编号 =====> {}", refundNotifyVO.getOutTradeNo()); + } + if (StringUtils.isNotBlank(detailsOrderInfo.getOrderType()) && !OrderTypeEnum.HEALTH_CONSULTATION.getInfo().equals(detailsOrderInfo.getOrderType())) { + if (!GooodsOrderStatusEnum.REFUNDED.getInfo().equals(detailsOrderInfo.getOrderStatus())) { + int updateStockCount = goodsAttributeDetailsMapper.addGoodsStockCount(detailsOrderInfo.getGoodsAttributeDetailsId(), detailsOrderInfo.getGoodsCount()); + if (updateStockCount < 1) { + log.info("新增库存数量失败,订单编号:{},商品属性明细id:{},数量数量:{}", detailsOrderInfo.getOrderNo(), detailsOrderInfo.getGoodsAttributeDetailsId(), detailsOrderInfo.getGoodsCount()); + } + } + if (Objects.nonNull(detailsOrderInfo.getCouponId()) && Objects.nonNull(detailsOrderInfo.getPatientId())) { + patientCouponReceiveMapper.updatePatientCouponUseStatus(detailsOrderInfo.getPatientId(), detailsOrderInfo.getCouponId(), CouponUseStatusEnum.NOT_USED.getInfo()); + } + } + 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.setOrderNo(StringUtils.isBlank(refundNotifyVO.getOutTradeNo()) ? "" : refundNotifyVO.getOutTradeNo()); + refundInfo.setOutRefundNo(StringUtils.isBlank(refundNotifyVO.getOutRefundNo()) ? "" : refundNotifyVO.getOutRefundNo()); + return refundInfo; + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPaymentServiceImpl.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPaymentServiceImpl.java new file mode 100644 index 0000000..fcb03dd --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatPaymentServiceImpl.java @@ -0,0 +1,565 @@ +package com.xinelu.applet.service.wechatpaymentinfo.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatPaymentService; +import com.xinelu.applet.utils.WeChatUtil; +import com.xinelu.applet.vo.wechatpaymentinfo.dto.PaymentDTO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.OrderStatusInfoVO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatAppletSignVO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatQueryOrderVO; +import com.xinelu.common.config.AppletChatConfig; +import com.xinelu.common.config.WeChatPaymentUrlConfig; +import com.xinelu.common.config.XylWeChatPaymentConfig; +import com.xinelu.common.config.YlypWeChatPaymentConfig; +import com.xinelu.common.constant.Constants; +import com.xinelu.common.core.domain.AjaxResult; +import com.xinelu.common.enums.BuySourceEnum; +import com.xinelu.common.enums.GooodsOrderStatusEnum; +import com.xinelu.common.enums.OrderStatusEnum; +import com.xinelu.common.enums.WeChatTradeStateEnum; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.manage.domain.appointmentorder.AppointmentOrder; +import com.xinelu.manage.domain.goodsOrder.GoodsOrder; +import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper; +import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper; +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; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.Signature; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @Description 微信小程序和App支付业务层实现类 + * @Author 纪寒 + * @Date 2022-10-18 15:02:40 + * @Version 1.0 + */ +@Service +@Slf4j +public class WeChatPaymentServiceImpl implements WeChatPaymentService { + + @Resource + private GoodsOrderMapper goodsOrderMapper; + @Resource + private AppointmentOrderMapper appointmentOrderMapper; + @Resource + private AppletChatConfig appletChatConfig; + @Resource + private XylWeChatPaymentConfig xylWeChatPaymentConfig; + @Resource + private YlypWeChatPaymentConfig ylypWeChatPaymentConfig; + @Resource(name = "xinYiLuWeChatPayClient") + private CloseableHttpClient xinYiLuWeChatPayClient; + @Resource(name = "yiLuYouPinWeChatPayClient") + private CloseableHttpClient yiLuYouPinWeChatPayClient; + @Resource + private WeChatPaymentUrlConfig weChatPaymentUrlConfig; + @Resource + private RedisTemplate redisTemplate; + @Resource + private WeChatUtil weChatUtil; + + /** + * 新医路支付商品描述 + */ + private static final String XIN_YI_LU_DESCRIPTION = "山东新医路信息科技有限公司"; + + /** + * 医路优品支付商品描述 + */ + private static final String YI_LU_YOU_PIN_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 = "/nurseApp/weChatPayment/xylWeChatPayNotify"; + + /** + * 医路优品支付回调接口地址 + */ + private static final String YINLUYOUPIN_WE_CHAT_NOTIFY_URL = "/nurseApp/weChatPayment/ylypWeChatPayNotify"; + + /** + * 微信小程序购买商品和健康咨询支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付结果 + * @throws Exception 常信息 + */ + @Override + public AjaxResult appletGoodsOrderPay(PaymentDTO paymentDTO) throws Exception { + String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo()); + if (StringUtils.isBlank(prepayId)) { + GoodsOrder goodsOrderInfo = goodsOrderMapper.getGoodsOrderByOrderNo(paymentDTO.getOrderNo()); + if (Objects.isNull(goodsOrderInfo) || StringUtils.isBlank(goodsOrderInfo.getOrderStatus())) { + return AjaxResult.error("未查询到当前订单信息,请选择正确的订单信息!"); + } + if (!goodsOrderInfo.getOrderStatus().equals(GooodsOrderStatusEnum.WAIT_PAY.getInfo())) { + return AjaxResult.error("订单状态异常,无法支付,请联系管理员!"); + } + if (StringUtils.isBlank(goodsOrderInfo.getBuySource()) + || !paymentDTO.getBuySource().equals(goodsOrderInfo.getBuySource())) { + log.info("购买来源不一致,请进行确认!"); + return AjaxResult.error("订单状态异常,无法支付,请联系管理员!"); + } + String jsApiParams = this.buildGoodsOrderJsApiParams(paymentDTO, goodsOrderInfo); + prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId); + } else { + prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo()); + } + return AjaxResult.success(getSignInfo(prepayId, paymentDTO.getBuySource())); + } + + /** + * 微信小程序预约服务订单支付接口 + * + * @param paymentDTO 输入参数 + * @return 支付结果 + * @throws Exception 异常信息 + */ + @Override + public AjaxResult appletAppointmentOrderPay(PaymentDTO paymentDTO) throws Exception { + String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo()); + if (StringUtils.isBlank(prepayId)) { + AppointmentOrder appointmentOrderInfo = appointmentOrderMapper.getAppointmentOrderByOrderNo(paymentDTO.getOrderNo()); + if (Objects.isNull(appointmentOrderInfo) || StringUtils.isBlank(appointmentOrderInfo.getOrderStatus())) { + return AjaxResult.error("未查询到当前预约订单信息,请选择重新预约!"); + } + if (!appointmentOrderInfo.getOrderStatus().equals(OrderStatusEnum.WAIT_PAY.getInfo())) { + return AjaxResult.error("订单状态异常,无法支付,请联系管理员!"); + } + String jsApiParams = this.buildAppointmentOrderJsApiParams(paymentDTO, appointmentOrderInfo); + prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId); + } else { + prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo()); + } + return AjaxResult.success(getSignInfo(prepayId, paymentDTO.getBuySource())); + } + + /** + * 取消订单接口 + * + * @param orderNo 点单编号 + * @param buySource 购买来源 + * @return 取消结果信息 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public AjaxResult cancelOrderInfo(String orderNo, String buySource) throws Exception { + GoodsOrder goodsOrderInfo = goodsOrderMapper.getGoodsOrderByOrderNo(orderNo); + AppointmentOrder appointmentOrderInfo = appointmentOrderMapper.getAppointmentOrderByOrderNo(orderNo); + if (Objects.isNull(goodsOrderInfo) && Objects.isNull(appointmentOrderInfo)) { + return AjaxResult.error("当前订单信息不存在,请重新选择!"); + } + //商品订单 + if (Objects.nonNull(goodsOrderInfo)) { + if (StringUtils.isBlank(goodsOrderInfo.getOrderStatus()) + || !GooodsOrderStatusEnum.WAIT_PAY.getInfo().equals(goodsOrderInfo.getOrderStatus())) { + log.info("订单状态为:====> {}", StringUtils.isBlank(goodsOrderInfo.getOrderStatus()) ? "不存在" : goodsOrderInfo.getOrderStatus()); + return AjaxResult.error("当前订单状态异常,请联系管理员!"); + } + LocalDateTime tenAfterTime = goodsOrderInfo.getOrderTime().plusMinutes(10); + if (LocalDateTime.now().isBefore(tenAfterTime)) { + return AjaxResult.error("10分钟以后才能取消当前订单,请耐心等待!"); + } + OrderStatusInfoVO orderStatusInfoVO = new OrderStatusInfoVO(); + OrderStatusInfoVO vo = this.queryGoodsOrderStatus(goodsOrderInfo.getOrderNo(), orderStatusInfoVO, buySource); + if (BooleanUtils.isTrue(vo.getPayFlag())) { + int statusCode = this.closeWeChatOrderInfo(goodsOrderInfo.getOrderNo(), goodsOrderInfo.getBuySource()); + if (!(statusCode == HAVE_BODY_SUCCESS_CODE || statusCode == NO_HAVE_BODY_SUCCESS_CODE)) { + return AjaxResult.error("取消订单失败,请联系管理员!"); + } + goodsOrderMapper.updateGoodsOrderStatus(GooodsOrderStatusEnum.CANCEL.getInfo(), orderNo); + } + } + //预约服务订单信息 + if (Objects.nonNull(appointmentOrderInfo)) { + if (StringUtils.isBlank(appointmentOrderInfo.getOrderStatus()) + || !OrderStatusEnum.WAIT_PAY.getInfo().equals(appointmentOrderInfo.getOrderStatus())) { + log.info("订单状态为:====> {}", StringUtils.isBlank(appointmentOrderInfo.getOrderStatus()) ? "不存在" : appointmentOrderInfo.getOrderStatus()); + return AjaxResult.error("当前订单状态异常,请联系管理员!"); + } + LocalDateTime tenAfterTime = appointmentOrderInfo.getCreateTime().plusMinutes(10); + if (LocalDateTime.now().isBefore(tenAfterTime)) { + return AjaxResult.error("10分钟以后才能取消当前订单,请耐心等待!"); + } + OrderStatusInfoVO orderStatusInfoVO = new OrderStatusInfoVO(); + OrderStatusInfoVO vo = this.queryAppointmentOrderStatus(appointmentOrderInfo.getOrderNo(), orderStatusInfoVO); + if (BooleanUtils.isTrue(vo.getPayFlag())) { + int statusCode = this.closeWeChatOrderInfo(appointmentOrderInfo.getOrderNo(), BuySourceEnum.NURSE_STATION.getInfo()); + if (!(statusCode == HAVE_BODY_SUCCESS_CODE || statusCode == NO_HAVE_BODY_SUCCESS_CODE)) { + return AjaxResult.error("取消订单失败,请联系管理员!"); + } + appointmentOrderMapper.updateAppointmentOrderStatus(OrderStatusEnum.CANCEL.getInfo(), orderNo); + } + } + return AjaxResult.success("取消订单成功!"); + } + + /** + * 查询订单状态信息 + * + * @param orderNo 点单编号 + * @param buySource 购买来源 + * @return 结果信息 + * @throws Exception 异常信息 + */ + @Override + public AjaxResult getOrderStatusInfo(String orderNo, String buySource) throws Exception { + OrderStatusInfoVO vo = new OrderStatusInfoVO(); + GoodsOrder goodsOrder = goodsOrderMapper.getGoodsOrderByOrderNo(orderNo); + AppointmentOrder appointmentOrder = appointmentOrderMapper.getAppointmentOrderByOrderNo(orderNo); + if (Objects.isNull(goodsOrder) && Objects.isNull(appointmentOrder)) { + vo.setPayFlag(true); + return AjaxResult.success(vo); + } + //预约服务订单不为空,调用微信查单接口确认订单状态 + if (Objects.nonNull(appointmentOrder)) { + OrderStatusInfoVO order = new OrderStatusInfoVO(); + vo = this.queryAppointmentOrderStatus(appointmentOrder.getOrderNo(), order); + } + //商品订单不为空,调用微信查单接口确认订单状态 + if (Objects.nonNull(goodsOrder)) { + OrderStatusInfoVO order = new OrderStatusInfoVO(); + vo = this.queryGoodsOrderStatus(goodsOrder.getOrderNo(), order, buySource); + } + return AjaxResult.success(vo); + } + + /** + * 微信关闭订单方法 + * + * @param orderNo 订单编号 + * @param buySource 购买来源 + * @return 状态码 + * @throws Exception 异常信息 + */ + @Override + public int closeWeChatOrderInfo(String orderNo, String buySource) throws Exception { + String closeUrl = String.format(weChatPaymentUrlConfig.getCloseOrderUrl(), orderNo); + HttpPost httpPost = new HttpPost(closeUrl); + Map paramMap = new HashMap<>(); + String jsonParams; + CloseableHttpResponse response = null; + if (BuySourceEnum.NURSE_STATION.getInfo().equals(buySource)) { + 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); + } + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(buySource) + || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(buySource) || BuySourceEnum.TRAINING.getInfo().equals(buySource)) { + paramMap.put("mchid", ylypWeChatPaymentConfig.getYlypMchId()); + jsonParams = JSON.toJSONString(paramMap); + StringEntity entity = new StringEntity(jsonParams, "utf-8"); + entity.setContentType("application/json"); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + response = yiLuYouPinWeChatPayClient.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) { + 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, + String buySource) throws Exception { + String requestUrl = ""; + if (BuySourceEnum.NURSE_STATION.getInfo().equals(buySource)) { + requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo).concat("?mchid=").concat(xylWeChatPaymentConfig.getXylMchId()); + } + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(buySource) + || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(buySource) || BuySourceEnum.TRAINING.getInfo().equals(buySource)) { + requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo).concat("?mchid=").concat(ylypWeChatPaymentConfig.getYlypMchId()); + } + 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 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 goodsOrderInfo 订单信息 + * @return 下单参数json串 + */ + private String buildGoodsOrderJsApiParams(PaymentDTO paymentDTO, GoodsOrder goodsOrderInfo) { + Map paramMap = new LinkedHashMap<>(); + if (BuySourceEnum.NURSE_STATION.getInfo().equals(paymentDTO.getBuySource())) { + paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId()); + paramMap.put("out_trade_no", goodsOrderInfo.getOrderNo()); + paramMap.put("appid", appletChatConfig.getAppletId()); + paramMap.put("description", XIN_YI_LU_DESCRIPTION); + paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL); + } + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(paymentDTO.getBuySource()) || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(paymentDTO.getBuySource())) { + paramMap.put("mchid", ylypWeChatPaymentConfig.getYlypMchId()); + paramMap.put("out_trade_no", goodsOrderInfo.getOrderNo()); + paramMap.put("appid", appletChatConfig.getAppletId()); + paramMap.put("description", YI_LU_YOU_PIN_DESCRIPTION); + paramMap.put("notify_url", ylypWeChatPaymentConfig.getYlypWeChatNotifyUrl() + YINLUYOUPIN_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 { + 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 { + if (BuySourceEnum.NURSE_STATION.getInfo().equals(paymentDTO.getBuySource())) { + response = xinYiLuWeChatPayClient.execute(httpPost); + payAccount = "山东新医路信息科技有限公司"; + } + //泉医会员小程序中的商城订单、健康咨询订单、泉医助手学习培训订单都使用医路优品账户进行支付 + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(paymentDTO.getBuySource()) + || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(paymentDTO.getBuySource()) + || BuySourceEnum.TRAINING.getInfo().equals(paymentDTO.getBuySource())) { + response = yiLuYouPinWeChatPayClient.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) { + 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 { + String appId = appletChatConfig.getAppletId(); + String nonceStr = UUID.randomUUID().toString().replace("-", ""); + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + PrivateKey privateKey = null; + //泉医到家小程序中的护理站模块的支付账户均使用新医路账户进行支付 + if (BuySourceEnum.NURSE_STATION.getInfo().equals(buySource)) { + privateKey = weChatUtil.getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath()); + } + //泉医到家小程序中的商城模块和健康咨询模块以及泉医助手中的学习培训模块使用医路优品账户进行支付 + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(buySource) + || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(buySource) + || BuySourceEnum.TRAINING.getInfo().equals(buySource)) { + privateKey = weChatUtil.getPrivateKey(ylypWeChatPaymentConfig.getYlypPrivateKeyPath()); + } + 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(); + } + + /** + * 构建预约订单JsApi微信下单参数 + * + * @param paymentDTO 前端传值 + * @param appointmentOrder 预约订单信息 + * @return 下单参数json串 + */ + private String buildAppointmentOrderJsApiParams(PaymentDTO paymentDTO, AppointmentOrder appointmentOrder) { + Map paramMap = new LinkedHashMap<>(); + paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId()); + paramMap.put("out_trade_no", appointmentOrder.getOrderNo()); + paramMap.put("appid", appletChatConfig.getAppletId()); + paramMap.put("description", XIN_YI_LU_DESCRIPTION); + paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL); + Map amountParamMap = new LinkedHashMap<>(); + int totalPrice = appointmentOrder.getTotalPrice().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); + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatRefundServiceImpl.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatRefundServiceImpl.java new file mode 100644 index 0000000..c275f33 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/service/wechatpaymentinfo/impl/WeChatRefundServiceImpl.java @@ -0,0 +1,451 @@ +package com.xinelu.applet.service.wechatpaymentinfo.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.xinelu.applet.service.goodstock.GoodsStockService; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatRefundService; +import com.xinelu.applet.vo.wechatpaymentinfo.dto.RefundDTO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatRefundInfoVO; +import com.xinelu.common.config.WeChatPaymentUrlConfig; +import com.xinelu.common.config.XylWeChatPaymentConfig; +import com.xinelu.common.config.YlypWeChatPaymentConfig; +import com.xinelu.common.core.domain.AjaxResult; +import com.xinelu.common.enums.*; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.manage.domain.appointmentorder.AppointmentOrder; +import com.xinelu.manage.domain.goodsOrder.GoodsOrder; +import com.xinelu.manage.domain.patientintegralchange.PatientIntegralChange; +import com.xinelu.manage.domain.refundinfo.RefundInfo; +import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper; +import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper; +import com.xinelu.manage.mapper.patientinfo.PatientInfoMapper; +import com.xinelu.manage.mapper.patientintegralchange.PatientIntegralChangeMapper; +import com.xinelu.manage.mapper.refundinfo.RefundInfoMapper; +import com.xinelu.manage.vo.goodsorder.GoodsInfoRedemptionVO; +import com.xinelu.manage.vo.patientinfo.PatientInfoVO; +import lombok.extern.slf4j.Slf4j; +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.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @Description 微信退款业务层实现类 + * @Author 纪寒 + * @Date 2022-10-24 15:23:16 + * @Version 1.0 + */ +@Slf4j +@Service +public class WeChatRefundServiceImpl implements WeChatRefundService { + + @Resource + private GoodsOrderMapper goodsOrderMapper; + @Resource + private AppointmentOrderMapper appointmentOrderMapper; + @Resource + private XylWeChatPaymentConfig xylWeChatPaymentConfig; + @Resource + private YlypWeChatPaymentConfig ylypWeChatPaymentConfig; + @Resource(name = "xinYiLuWeChatPayClient") + private CloseableHttpClient xinYiLuWeChatPayClient; + @Resource(name = "yiLuYouPinWeChatPayClient") + private CloseableHttpClient yiLuYouPinWeChatPayClient; + @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 = "/nurseApp/weChatPayment/xylWeChatRefundNotify"; + + /** + * 医路优品退款回调接口地址 + */ + private static final String YINLUYOUPIN_WE_CHAT_REFUND_URL = "/nurseApp/weChatPayment/ylypWeChatRefundNotify"; + + /** + * 微信确认退款接口 + * + * @param refundDTO 退款参数 + * @return 退款申请结果 + * @throws Exception 异常信息 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public AjaxResult weChatRefundOrderApply(RefundDTO refundDTO) throws Exception { + if (StringUtils.isNotBlank(refundDTO.getOrderType()) && StringUtils.equals(OrderTypeEnum.INTEGRAL_EXCHANGE.getInfo(), refundDTO.getOrderType())) { + return refundPointsRedemption(refundDTO); + } + AppointmentOrder appointmentOrderInfo = appointmentOrderMapper.getAppointmentOrderByOrderNo(refundDTO.getOrderNo()); + if (Objects.nonNull(appointmentOrderInfo)) { + AjaxResult ajaxResult = judgeRefundInfo(appointmentOrderInfo.getCreateTime(), refundDTO.getRefundPrice(), appointmentOrderInfo.getTotalPrice()); + if (Objects.nonNull(ajaxResult)) { + return ajaxResult; + } + if (StringUtils.isBlank(appointmentOrderInfo.getConfirmRefundStatus()) + || !(ConfirmRefundStatusEnum.NOT_CONFIRM.getInfo().equals(appointmentOrderInfo.getConfirmRefundStatus()))) { + return AjaxResult.error("当前预约订单未进行退款确认,请先进行确认!"); + } + if (StringUtils.isBlank(appointmentOrderInfo.getOrderStatus()) + || !OrderStatusEnum.WAIT_REFUND.getInfo().equals(appointmentOrderInfo.getOrderStatus()) + || OrderStatusEnum.REFUNDED.getInfo().equals(appointmentOrderInfo.getOrderStatus())) { + return AjaxResult.error("当前预约订单正在退款中或者已退款,无法进行退款处理!"); + } + Long patientId = Objects.isNull(appointmentOrderInfo.getPatientId()) ? 0 : appointmentOrderInfo.getPatientId(); + String outRefundNo = com.xinelu.common.utils.StringUtils.fillZeroByPatientId(patientId, 5) + System.nanoTime(); + String refundParam = buildRefundParam(null, appointmentOrderInfo, refundDTO, outRefundNo); + this.applyWeRefund(refundParam, appointmentOrderInfo, null, patientId, refundDTO); + return AjaxResult.success(); + } + GoodsOrder goodsOrderInfo = goodsOrderMapper.getGoodsOrderByOrderNo(refundDTO.getOrderNo()); + if (Objects.nonNull(goodsOrderInfo)) { + AjaxResult ajaxResult = judgeRefundInfo(goodsOrderInfo.getOrderTime(), refundDTO.getRefundPrice(), goodsOrderInfo.getTotalPrice()); + if (Objects.nonNull(ajaxResult)) { + return ajaxResult; + } + if (StringUtils.isBlank(goodsOrderInfo.getConfirmRefundStatus()) + || !(ConfirmRefundStatusEnum.NOT_CONFIRM.getInfo().equals(goodsOrderInfo.getConfirmRefundStatus()))) { + return AjaxResult.error("当前商品订单未进行退款确认,请先进行确认!"); + } + if (StringUtils.isBlank(goodsOrderInfo.getOrderStatus()) + || !GooodsOrderStatusEnum.WAIT_REFUND.getInfo().equals(goodsOrderInfo.getOrderStatus()) + || GooodsOrderStatusEnum.REFUNDED.getInfo().equals(goodsOrderInfo.getOrderStatus())) { + return AjaxResult.error("当前商品订单非退款中或者已退款,无法进行退款处理!"); + } + Long patientId = Objects.isNull(goodsOrderInfo.getPatientId()) ? 0 : goodsOrderInfo.getPatientId(); + String outRefundNo = com.xinelu.common.utils.StringUtils.fillZeroByPatientId(patientId, 5) + System.nanoTime(); + String refundParam = buildRefundParam(goodsOrderInfo, null, refundDTO, outRefundNo); + this.applyWeRefund(refundParam, null, goodsOrderInfo, patientId, refundDTO); + return AjaxResult.success(); + } + return AjaxResult.success(); + } + + /** + * 调用微信查询单笔退款接口查询预约订单状态信息 + * + * @param refundNo 退款单号 + * @return 退款信息 + */ + @Override + public WeChatRefundInfoVO queryAppointmentOrderRefundStatus(String refundNo) { + WeChatRefundInfoVO weChatRefundInfoVO = new WeChatRefundInfoVO(); + 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(); + 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 goodsOrderInfo 商品订单信息 + * @param appointmentOrderInfo 预约服务订单信息 + * @param refundDTO 退款参数信息 + * @return 退款申请Json串 + */ + private String buildRefundParam(GoodsOrder goodsOrderInfo, AppointmentOrder appointmentOrderInfo, + RefundDTO refundDTO, String outRefundNo) { + Map paramMap = new LinkedHashMap<>(); + paramMap.put("out_trade_no", refundDTO.getOrderNo()); + paramMap.put("out_refund_no", outRefundNo); + Map amountMap = new LinkedHashMap<>(); + //回调通知地址和退款金额,预约订单使用新医路账户 + if (Objects.nonNull(appointmentOrderInfo) && Objects.isNull(goodsOrderInfo)) { + paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_REFUND_URL); + amountMap.put("refund", refundDTO.getRefundPrice().multiply(BigDecimal.valueOf(100)).intValue()); + amountMap.put("total", appointmentOrderInfo.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue()); + } + //护理站商品购买订单使用新医路账户,商城商品订单和健康咨询订单使用医路优品账户 + if (Objects.nonNull(goodsOrderInfo) && Objects.isNull(appointmentOrderInfo)) { + if (BuySourceEnum.NURSE_STATION.getInfo().equals(goodsOrderInfo.getBuySource())) { + paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_REFUND_URL); + } + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(goodsOrderInfo.getBuySource()) || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(goodsOrderInfo.getBuySource())) { + paramMap.put("notify_url", ylypWeChatPaymentConfig.getYlypWeChatNotifyUrl() + YINLUYOUPIN_WE_CHAT_REFUND_URL); + } + amountMap.put("refund", refundDTO.getRefundPrice().multiply(BigDecimal.valueOf(100)).intValue()); + amountMap.put("total", goodsOrderInfo.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue()); + } + amountMap.put("currency", "CNY"); + paramMap.put("amount", amountMap); + return JSON.toJSONString(paramMap); + } + + /** + * 发起退款申请 + * + * @param refundParam 请求参数 + * @param appointmentOrderInfo 预约服务订单信息 + * @param goodsOrderInfo 商品订单信息 + * @param patientId 会员id + * @param refundDTO 申请信息 + * @throws Exception 异常信息 + */ + public void applyWeRefund(String refundParam, AppointmentOrder appointmentOrderInfo, GoodsOrder goodsOrderInfo, + Long patientId, RefundDTO refundDTO) throws Exception { + 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 { + //预约订单使用新医路账户 + if (Objects.nonNull(appointmentOrderInfo) && Objects.isNull(goodsOrderInfo)) { + response = xinYiLuWeChatPayClient.execute(httpPost); + refundMerchantType = PaymentMerchantTypeEnum.XINYILU.getInfo(); + } + //护理站商品购买订单使用新医路账户,商城商品订单和健康咨询订单使用医路优品账户 + if (Objects.nonNull(goodsOrderInfo) && Objects.isNull(appointmentOrderInfo)) { + if (BuySourceEnum.NURSE_STATION.getInfo().equals(goodsOrderInfo.getBuySource())) { + response = xinYiLuWeChatPayClient.execute(httpPost); + refundMerchantType = PaymentMerchantTypeEnum.XINYILU.getInfo(); + } + if (BuySourceEnum.SHOPPING_MALL.getInfo().equals(goodsOrderInfo.getBuySource()) || BuySourceEnum.HEALTH_CONSULTATION.getInfo().equals(goodsOrderInfo.getBuySource())) { + response = yiLuYouPinWeChatPayClient.execute(httpPost); + refundMerchantType = PaymentMerchantTypeEnum.YILUYOUPIN.getInfo(); + } + } + 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(appointmentOrderInfo)) { + appointmentOrderMapper.updateAppointConfirmRefundStatus(appointmentOrderInfo.getOrderNo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo()); + } + if (Objects.nonNull(goodsOrderInfo)) { + goodsOrderMapper.updateGoodsConfirmRefundStatus(goodsOrderInfo.getOrderNo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo()); + } + WeChatRefundInfoVO weChatRefundInfoVO = JSONObject.parseObject(body, WeChatRefundInfoVO.class); + int refundInfoCount = refundInfoMapper.getRefundInfoByOrderNo(weChatRefundInfoVO.getOutTradeNo()); + if (refundInfoCount <= 0) { + RefundInfo refundInfo = buildRefundInfo(patientId, 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 patientId 会员id + * @param refundInfoVO 退款信息参数 + * @param refundDTO 退款申请参数 + * @param refundBody 返回体 + * @param refundMerchantType 退款账户类型,新医路或者医路优品 + */ + private RefundInfo buildRefundInfo(Long patientId, WeChatRefundInfoVO refundInfoVO, + RefundDTO refundDTO, String refundBody, String refundMerchantType) { + RefundInfo refundInfo = new RefundInfo(); + refundInfo.setPatientId(patientId); + refundInfo.setOrderNo(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(RefundTypeEnum.WE_CHAT.getInfo()); + 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); + refundInfo.setCreateTime(LocalDateTime.parse(refundInfoVO.getCreateTime(), DateTimeFormatter.ISO_DATE_TIME)); + return refundInfo; + } + + /** + * 会员积分兑换商品确认退款接口 + * + * @param refundDTO 退款参数 + * @return com.xinyilu.common.core.domain.AjaxResult + **/ + public AjaxResult refundPointsRedemption(RefundDTO refundDTO) { + GoodsInfoRedemptionVO goodsOrderInfo = goodsOrderMapper.getGoodOrder(refundDTO.getOrderNo()); + if (Objects.isNull(goodsOrderInfo)) { + return AjaxResult.error("当前订单信息不存在,请重新选择!"); + } + PatientInfoVO patientInfo = patientInfoMapper.getPatientInfoById(goodsOrderInfo.getPatientId()); + if (Objects.isNull(patientInfo)) { + return AjaxResult.error("当前用户信息不存在,无法退款!"); + } + AjaxResult ajaxResult = goodIntegralVerification(goodsOrderInfo); + if (Objects.nonNull(ajaxResult)) { + return ajaxResult; + } + goodsOrderMapper.updateConfirmRefundStatusAndOrderStatus(refundDTO.getOrderNo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo(), OrderStatusEnum.REFUNDED.getInfo()); + Integer stockNum = Objects.isNull(goodsOrderInfo.getIntegralExchangeCount()) ? 0 : goodsOrderInfo.getIntegralExchangeCount(); + goodsStockService.addGoodsStockInfo(goodsOrderInfo.getOrderNo(), goodsOrderInfo.getGoodsAttributeDetailsId(), stockNum); + int stockIntegralNum = Objects.isNull(goodsOrderInfo.getIntegralExchangeSill()) ? 0 : goodsOrderInfo.getIntegralExchangeSill(); + patientInfoMapper.addPatientIntegralCount(goodsOrderInfo.getPatientId(), stockIntegralNum); + PatientIntegralChange patientIntegralChange = new PatientIntegralChange(); + patientIntegralChange.setPatientId(goodsOrderInfo.getPatientId()); + patientIntegralChange.setOriginalIntegral(Objects.isNull(patientInfo.getIntegral()) ? 0 : patientInfo.getIntegral()); + patientIntegralChange.setChangeIntegral(stockIntegralNum); + patientIntegralChange.setChangeTime(LocalDateTime.now()); + patientIntegralChange.setChangeType(IntegralChangeType.COMMODITY_EXCHANGE.getInfo()); + patientIntegralChange.setChangeRemark("积分兑换商品订单新增积分变更"); + patientIntegralChange.setChangeIntegralChannel(StringUtils.isBlank(goodsOrderInfo.getOrderChannel()) ? "" : goodsOrderInfo.getOrderChannel()); + patientIntegralChange.setCreateTime(LocalDateTime.now()); + int integralChange = patientIntegralChangeMapper.insertPatientIntegralChange(patientIntegralChange); + if (integralChange <= 0) { + log.error("积分兑换商品-生成积分变更记录表信息失败,积分变更信息:{}", integralChange); + throw new ServiceException("确认退款失败,请联系管理员!"); + } + return AjaxResult.success(); + } + + + /** + * 校验积分兑换商品订单状态以及订单时间等下信息 + * + * @return 返回结果信息 + */ + private AjaxResult goodIntegralVerification(GoodsInfoRedemptionVO goodsOrderInfo) { + LocalDateTime localDateTime = goodsOrderInfo.getOrderTime().plusYears(1); + if (LocalDateTime.now().isAfter(localDateTime)) { + return AjaxResult.error("当前订单超过一年,无法进行退款处理!"); + } + if (StringUtils.isBlank(goodsOrderInfo.getConfirmRefundStatus()) + || !ConfirmRefundStatusEnum.NOT_CONFIRM.getInfo().equals(goodsOrderInfo.getConfirmRefundStatus())) { + return AjaxResult.error("当前商品订单未进行退款确认,请先进行确认!"); + } + if (StringUtils.isBlank(goodsOrderInfo.getOrderStatus()) + || !GooodsOrderStatusEnum.WAIT_REFUND.getInfo().equals(goodsOrderInfo.getOrderStatus()) + || GooodsOrderStatusEnum.REFUNDED.getInfo().equals(goodsOrderInfo.getOrderStatus())) { + return AjaxResult.error("当前商品订单非退款中或者已退款,无法进行退款处理!"); + } + return null; + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/utils/WeChatUtil.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/utils/WeChatUtil.java new file mode 100644 index 0000000..af2a86b --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/utils/WeChatUtil.java @@ -0,0 +1,35 @@ +package com.xinelu.applet.utils; + +import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; +import com.xinelu.common.exception.ServiceException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import java.security.PrivateKey; + +/** + * @Description 微信工具类 + * @Author 纪寒 + * @Date 2022-10-20 10:11:38 + * @Version 1.0 + */ +@Component +@Slf4j +public class WeChatUtil { + + /** + * 获取商户的私钥文件 + * + * @param filename 获取商户的私钥文件 + * @return 商户私钥 + */ + public PrivateKey getPrivateKey(String filename) { + try { + return PemUtil.loadPrivateKey(new ClassPathResource(filename).getInputStream()); + } catch (Exception e) { + log.error("商户私钥文件不存在,错误原因:{}", e.getMessage()); + throw new ServiceException("商户私钥文件不存在,请联系管理员!"); + } + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/PaymentDTO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/PaymentDTO.java new file mode 100644 index 0000000..99e2a7c --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/PaymentDTO.java @@ -0,0 +1,67 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.dto; + +import com.xinelu.common.custominterface.Insert; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * @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 = "会员id不能为空!", groups = {Insert.class}) + private Long patientId; + + /** + * 微信用户openid + */ + private String openid; + + /** + * 订单编号 + */ + @NotBlank(message = "订单编号不能为空!", groups = {Insert.class}) + private String orderNo; + + /** + * 支付类型,微信支付:WECHAT_PAY,支付宝支付:ALI_PAY + */ + @NotBlank(message = "支付类型不能为空!", groups = {Insert.class}) + private String payType; + + /** + * 支付渠道,手机App:MOBILE_APP,微信小程序:WECHAT_APPLET,支付宝小程序:ALI_PAY_APPLE + */ + @NotBlank(message = "支付渠道不能为空!", groups = {Insert.class}) + private String orderChannel; + + /** + * 购买来源,用于判断使用那个商户进行支付 + * 护理站:NURSE_STATION,商城:SHOPPING_MAL, 健康咨询:HEALTH_CONSULTATION,学习培训:TRAINING + */ + @NotBlank(message = "购买来源不能为空!", groups = {Insert.class}) + private String buySource; + + /** + * 支付金额 + */ + @NotNull(message = "支付金额不能为空!", groups = {Insert.class}) + private BigDecimal paymentPrice; + + /** + * 护理员id + */ + private Long nurseStationPersonId; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/RefundDTO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/RefundDTO.java new file mode 100644 index 0000000..2ec9808 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/dto/RefundDTO.java @@ -0,0 +1,57 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.dto; + +import com.xinelu.common.custominterface.Insert; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * @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 = "订单编号不能为空!", groups = {Insert.class}) + private String orderNo; + + /** + * 退款原因 + */ + private String refundReason; + + /** + * 退款金额,必传字段 + */ + @NotNull(message = "退款金额不能为空!", groups = {Insert.class}) + private BigDecimal refundPrice; + + /** + * 补充描述和凭证 + */ + private String remark; + + /** + * 货物到货状态,目前用不到 + */ + private String goodsStatus; + + /** + * 订单类型(健康咨询订单退款使用),积分兑换:INTEGRAL_EXCHANGE,直接购买:DIRECT_BUY,健康咨询:HEALTH_CONSULTATION + */ + private String orderType; + + /** + * 学习培训订单标识(学习培训订单退款使用) + */ + private String trainingOrderFlag; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/OrderStatusInfoVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/OrderStatusInfoVO.java new file mode 100644 index 0000000..a8d1067 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/OrderStatusInfoVO.java @@ -0,0 +1,22 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 订单状态返回值实体类 + * @Author 纪寒 + * @Date 2022-10-21 10:47:20 + * @Version 1.0 + */ +@Data +public class OrderStatusInfoVO implements Serializable { + private static final long serialVersionUID = -8522003863208703215L; + + private Boolean payFlag; + + private String tradeStatus; + + WeChatQueryOrderVO weChatQueryOrderVO; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppSignVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppSignVO.java new file mode 100644 index 0000000..39a9b1b --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppSignVO.java @@ -0,0 +1,34 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 微信App调起支付参数信息 + * @Author 纪寒 + * @Date 2022-10-31 09:59:19 + * @Version 1.0 + */ +@Builder +@Data +public class WeChatAppSignVO implements Serializable { + private static final long serialVersionUID = 6921828781484230912L; + + private String appId; + + private String partnerId; + + private String prepayId; + + private String packageValue; + + private String nonceStr; + + private String timeStamp; + + private String sign; + + +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppletSignVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppletSignVO.java new file mode 100644 index 0000000..710273a --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatAppletSignVO.java @@ -0,0 +1,30 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 微信小程序调起支付参数信息 + * @Author 纪寒 + * @Date 2022-10-20 09:59:19 + * @Version 1.0 + */ +@Builder +@Data +public class WeChatAppletSignVO implements Serializable { + private static final long serialVersionUID = -2527792374746553807L; + + private String appId; + + private String timeStamp; + + private String nonceStr; + + private String prepayId; + + private String signType; + + private String paySign; +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyCiphertextVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyCiphertextVO.java new file mode 100644 index 0000000..1982a33 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyCiphertextVO.java @@ -0,0 +1,57 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description 微信只顾回调密文参数实体类 + * @Author 纪寒 + * @Date 2022-10-20 14:10:15 + * @Version 1.0 + */ +@NoArgsConstructor +@Data +public class WeChatPayNotifyCiphertextVO implements Serializable { + private static final long serialVersionUID = -3000851624508762861L; + + @JsonProperty("id") + private String id; + + @JsonProperty("create_time") + private String createTime; + + @JsonProperty("resource_type") + private String resourceType; + + @JsonProperty("event_type") + private String eventType; + + @JsonProperty("summary") + private String summary; + + @JsonProperty("resource") + private ResourceDTO resource; + + @NoArgsConstructor + @Data + public static class ResourceDTO { + + @JsonProperty("original_type") + private String originalType; + + @JsonProperty("algorithm") + private String algorithm; + + @JsonProperty("ciphertext") + private String ciphertext; + + @JsonProperty("associated_data") + private String associatedData; + + @JsonProperty("nonce") + private String nonce; + } +} diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyPlaintextVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyPlaintextVO.java new file mode 100644 index 0000000..0bd85c0 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatPayNotifyPlaintextVO.java @@ -0,0 +1,146 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @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; + + @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; + + @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; + + @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; + + @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/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatQueryOrderVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatQueryOrderVO.java new file mode 100644 index 0000000..54f3179 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatQueryOrderVO.java @@ -0,0 +1,82 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @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; + + @JsonProperty("appid") + private String appid; + + @JsonProperty("attach") + private String attach; + + @JsonProperty("bank_type") + private String bankType; + + @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; + + @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/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundInfoVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundInfoVO.java new file mode 100644 index 0000000..60af7a9 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundInfoVO.java @@ -0,0 +1,140 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @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 { + @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/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundNotifyVO.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundNotifyVO.java new file mode 100644 index 0000000..2586f22 --- /dev/null +++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/vo/wechatpaymentinfo/vo/WeChatRefundNotifyVO.java @@ -0,0 +1,61 @@ +package com.xinelu.applet.vo.wechatpaymentinfo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @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/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/paymentinfo/PaymentInfo.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/paymentinfo/PaymentInfo.java new file mode 100644 index 0000000..c3b2104 --- /dev/null +++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/paymentinfo/PaymentInfo.java @@ -0,0 +1,156 @@ +package com.xinelu.manage.domain.paymentinfo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xinelu.common.annotation.Excel; +import com.xinelu.common.core.domain.BaseDomain; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 支付记录信息对象 payment_info + * + * @author xinyilu + * @date 2022-10-18 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "支付记录信息对象", description = "payment_info") +public class PaymentInfo extends BaseDomain implements Serializable { + private static final long serialVersionUID = 3272597744303067026L; + /** + * 主键id + */ + private Long id; + + /** + * 会员id + */ + @ApiModelProperty(value = "会员id") + @Excel(name = "会员id") + private Long patientId; + + /** + * 护理员表id + */ + private Long nurseStationPersonId; + + /** + * 商品订单编号,系统自动生成 + */ + @ApiModelProperty(value = "商品订单编号,系统自动生成") + @Excel(name = "商品订单编号,系统自动生成") + private String orderNo; + + /** + * 支付标题 + */ + @ApiModelProperty(value = "支付标题") + @Excel(name = "支付标题") + private String paymentTitle; + + /** + * 交易流水号 + */ + @ApiModelProperty(value = "交易流水号") + @Excel(name = "交易流水号") + private String transactionNo; + + /** + * 支付金额(分) + */ + @ApiModelProperty(value = "支付金额") + @Excel(name = "支付金额", readConverterExp = "分=") + private BigDecimal payPrice; + + /** + * 支付类型,微信支付:WECHAT_PAY,支付宝支付:ALI_PAY + */ + @ApiModelProperty(value = "支付类型,微信支付:WECHAT_PAY,支付宝支付:ALI_PAY") + @Excel(name = "支付类型,微信支付: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") + @Excel(name = "支付渠道,手机App:MOBILE_APP,微信小程序:WECHAT_APPLET,支付宝小程序:ALI_PAY_APPLET") + private String payChannel; + + /** + * 微信支付交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销(付款码支付):REVOKED,用户支付中(付款码支付):USERPAYING,支付失败(其他原因,如银行返回失败):PAYERROR + */ + @ApiModelProperty(value = "微信支付交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销") + @Excel(name = "微信支付交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销", readConverterExp = "付=款码支付") + private String wechatTradeState; + + /** + * 支付宝交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销(付款码支付):REVOKED,用户支付中(付款码支付):USERPAYING,支付失败(其他原因,如银行返回失败):PAYERROR + */ + @ApiModelProperty(value = "支付宝交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销") + @Excel(name = "支付宝交易状态,支付成功:SUCCESS,转入退款:REFUND,未支付:NOTPAY,已关闭:CLOSED,已撤销", readConverterExp = "付=款码支付") + private String alipayTradeState; + + /** + * 支付通知参数内容,微信或者支付宝支付回调通知信息 + */ + @ApiModelProperty(value = "支付通知参数内容,微信或者支付宝支付回调通知信息") + @Excel(name = "支付通知参数内容,微信或者支付宝支付回调通知信息") + private String payNotifyContent; + + /** + * 支付时间 + */ + @ApiModelProperty(value = "支付时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime payTime; + + /** + * 支付账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN + */ + @ApiModelProperty(value = "支付账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN") + @Excel(name = "支付账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN") + private String paymentMerchantType; + + /** + * 是否删除,0:否,1:是 + */ + private Integer delFlag; + + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("patientId", getPatientId()) + .append("orderNo", getOrderNo()) + .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/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/refundinfo/RefundInfo.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/refundinfo/RefundInfo.java new file mode 100644 index 0000000..d87fd12 --- /dev/null +++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/refundinfo/RefundInfo.java @@ -0,0 +1,205 @@ +package com.xinelu.manage.domain.refundinfo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xinelu.common.annotation.Excel; +import com.xinelu.common.core.domain.BaseDomain; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 退款记录信息对象 refund_info + * + * @author xinyilu + * @date 2022-10-18 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "退款记录信息对象", description = "refund_info") +public class RefundInfo extends BaseDomain implements Serializable { + + private static final long serialVersionUID = -3202049270867033957L; + /** + * 主键id + */ + private Long id; + + /** + * 会员id + */ + @ApiModelProperty(value = "会员id") + @Excel(name = "会员id") + private Long patientId; + + /** + * 护理员表id + */ + private Long nurseStationPersonId; + + /** + * 商品订单号 + */ + @ApiModelProperty(value = "商品订单号") + @Excel(name = "商品订单号") + private String orderNo; + + /** + * 支付退款单号,多种支付方式共用 + */ + @ApiModelProperty(value = "支付退款单号,多种支付方式共用") + @Excel(name = "支付退款单号,多种支付方式共用") + private String refundNo; + + /** + * 商户退款单号,系统自动生成 + */ + @ApiModelProperty(value = "商户退款单号,系统自动生成") + @Excel(name = "商户退款单号,系统自动生成") + private String outRefundNo; + + /** + * 支付交易订单号,多种支付方式共用 + */ + @ApiModelProperty(value = "支付交易订单号,多种支付方式共用") + @Excel(name = "支付交易订单号,多种支付方式共用") + private String transactionNo; + + /** + * 退款原因 + */ + @ApiModelProperty(value = "退款原因") + @Excel(name = "退款原因") + private String refundReason; + + /** + * 退款方式,微信:WE_CHAT,支付宝:ALI_PAY + */ + @ApiModelProperty(value = "退款方式,微信:WE_CHAT,支付宝:ALI_PAY") + @Excel(name = "退款方式,微信:WE_CHAT,支付宝:ALI_PAY") + private String refundType; + + /** + * 微信退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL + */ + @ApiModelProperty(value = "微信退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL") + @Excel(name = "微信退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL") + private String wechatRefundStatus; + + /** + * 支付宝退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL + */ + @ApiModelProperty(value = "支付宝退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL") + @Excel(name = "支付宝退款状态,退款成功:SUCCESS,退款关闭:CLOSED,退款处理中:PROCESSING,退款异常:ABNORMAL") + private String alipayRefundStatus; + + /** + * 订单金额(分) + */ + @ApiModelProperty(value = "订单金额") + @Excel(name = "订单金额", readConverterExp = "分=") + private BigDecimal orderTotalPrice; + + /** + * 退款金额(分) + */ + @ApiModelProperty(value = "退款金额") + @Excel(name = "退款金额", readConverterExp = "分=") + private BigDecimal refundPrice; + + /** + * 退款币种,人民币:CNY + */ + @ApiModelProperty(value = "退款币种,人民币:CNY") + @Excel(name = "退款币种,人民币:CNY") + private String currency; + + /** + * ORIGINAL:原路退款,BALANCE:退回到余额,OTHER_BALANCE:原账户异常退到其他余额账户,OTHER_BANKCARD:原银行卡异常退到其他银行卡 + */ + @ApiModelProperty(value = "ORIGINAL:原路退款,BALANCE:退回到余额,OTHER_BALANCE:原账户异常退到其他余额账户,OTHER_BANKCARD:原银行卡异常退到其他银行卡") + @Excel(name = "ORIGINAL:原路退款,BALANCE:退回到余额,OTHER_BALANCE:原账户异常退到其他余额账户,OTHER_BANKCARD:原银行卡异常退到其他银行卡") + private String channel; + + /** + * 退款入账账户 + */ + @ApiModelProperty(value = "退款入账账户") + @Excel(name = "退款入账账户") + private String userreceivedaccount; + + /** + * 退款成功时间 + */ + @ApiModelProperty(value = "退款成功时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "退款成功时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime successTime; + + /** + * 退款回调通知内容,微信或者支付款退款通知内容 + */ + @ApiModelProperty(value = "退款回调通知内容,微信或者支付款退款通知内容") + @Excel(name = "退款回调通知内容,微信或者支付款退款通知内容") + private String refundNotifyContent; + + /** + * 申请退款返回参数内容 + */ + @ApiModelProperty(value = "申请退款返回参数内容") + @Excel(name = "申请退款返回参数内容") + private String applyRefundReturnContent; + + /** + * 退款账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN + */ + @ApiModelProperty(value = "退款账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN") + @Excel(name = "退款账户类型,新医路账户:XINYILU,医路优品:YILUYOUPIN") + private String refundMerchantType; + + /** + * 是否删除,0:否,1:是 + */ + private Integer delFlag; + + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("patientId", getPatientId()) + .append("orderNo", getOrderNo()) + .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/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/paymentinfo/PaymentInfoMapper.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/paymentinfo/PaymentInfoMapper.java new file mode 100644 index 0000000..39e5bc5 --- /dev/null +++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/paymentinfo/PaymentInfoMapper.java @@ -0,0 +1,70 @@ +package com.xinelu.manage.mapper.paymentinfo; + +import com.xinelu.manage.domain.paymentinfo.PaymentInfo; + +import java.util.List; + + +/** + * 支付记录信息Mapper接口 + * + * @author xinyilu + * @date 2022-10-18 + */ +public interface PaymentInfoMapper { + /** + * 查询支付记录信息 + * + * @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 getPaymentInfoByOrderNo(String orderNo); +} diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/refundinfo/RefundInfoMapper.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/refundinfo/RefundInfoMapper.java new file mode 100644 index 0000000..60186c7 --- /dev/null +++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/refundinfo/RefundInfoMapper.java @@ -0,0 +1,90 @@ +package com.xinelu.manage.mapper.refundinfo; + +import com.xinelu.manage.domain.refundinfo.RefundInfo; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 退款记录信息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 orderNo 订单编号 + * @return 数量 + */ + int getRefundInfoByOrderNo(String orderNo); +} diff --git a/xinelu-nurse-manage/src/main/resources/mapper/manage/paymentinfo/PaymentInfoMapper.xml b/xinelu-nurse-manage/src/main/resources/mapper/manage/paymentinfo/PaymentInfoMapper.xml new file mode 100644 index 0000000..0dd3544 --- /dev/null +++ b/xinelu-nurse-manage/src/main/resources/mapper/manage/paymentinfo/PaymentInfoMapper.xml @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, + patient_id, + nurse_station_person_id, + order_no, + 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 + + patient_id, + + nurse_station_person_id, + + order_no, + + 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, + + + + #{patientId}, + + #{nurseStationPersonId}, + + #{orderNo}, + + #{paymentTitle}, + + #{transactionNo}, + + #{payPrice}, + + #{payType}, + + #{payChannel}, + + #{wechatTradeState}, + + #{alipayTradeState}, + + #{payNotifyContent}, + + #{payTime}, + + #{paymentMerchantType}, + + #{delFlag}, + + #{createBy}, + + #{createTime}, + + #{updateBy}, + + #{updateTime}, + + + + + + update payment_info + + patient_id = + #{patientId}, + + nurse_station_person_id = + #{nurseStationPersonId}, + + order_no = + #{orderNo}, + + 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/xinelu-nurse-manage/src/main/resources/mapper/manage/refundinfo/RefundInfoMapper.xml b/xinelu-nurse-manage/src/main/resources/mapper/manage/refundinfo/RefundInfoMapper.xml new file mode 100644 index 0000000..2caaa99 --- /dev/null +++ b/xinelu-nurse-manage/src/main/resources/mapper/manage/refundinfo/RefundInfoMapper.xml @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, + patient_id, + nurse_station_person_id, + order_no, + 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 + + patient_id, + + nurse_station_person_id, + + order_no, + + 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, + + + + #{patientId}, + + #{nurseStationPersonId}, + + #{orderNo}, + + #{refundNo}, + + #{outRefundNo}, + + #{transactionNo}, + + #{refundReason}, + + #{refundType}, + + #{wechatRefundStatus}, + + #{alipayRefundStatus}, + + #{orderTotalPrice}, + + #{refundPrice}, + + #{currency}, + + #{channel}, + + #{userreceivedaccount}, + + #{successTime}, + + #{refundNotifyContent}, + + #{applyRefundReturnContent}, + + #{refundMerchantType}, + + #{delFlag}, + + #{createBy}, + + #{createTime}, + + #{updateBy}, + + #{updateTime}, + + + + + + update refund_info + + patient_id = + #{patientId}, + + nurse_station_person_id = + #{nurseStationPersonId}, + + order_no = + #{orderNo}, + + 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 order_no = #{orderNo} + 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 order_no = #{orderNo} + and wechat_refund_status <> #{weChatRefundStatus} + + + + diff --git a/xinelu-quartz/pom.xml b/xinelu-quartz/pom.xml index ecff609..a493c74 100644 --- a/xinelu-quartz/pom.xml +++ b/xinelu-quartz/pom.xml @@ -35,6 +35,15 @@ xinelu-common + + com.xinelu + xinelu-nurse-manage + + + + com.xinelu + xinelu-nurse-applet + \ No newline at end of file diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/CouponTaskController.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/CouponTaskController.java new file mode 100644 index 0000000..1bfb061 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/CouponTaskController.java @@ -0,0 +1,30 @@ +package com.xinelu.quartz.controller; + +import com.xinelu.quartz.task.CouponTask; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @Description 优惠券定时任务控制器 + * @Author 纪寒 + * @Date 2023-02-28 15:04:38 + * @Version 1.0 + */ +@RestController +@RequestMapping("/monitor/couponTask") +public class CouponTaskController { + + @Resource + private CouponTask couponTask; + + /** + * 手动执行更新会员领取已过期的优惠券状态定时任务 + */ + @GetMapping("/patientCouponStatus") + public void patientCouponStatus() { + couponTask.patientCouponReceiveStatusTask(); + } +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/PaymentInfoTaskController.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/PaymentInfoTaskController.java new file mode 100644 index 0000000..249db9f --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/PaymentInfoTaskController.java @@ -0,0 +1,42 @@ +package com.xinelu.quartz.controller; + +import com.xinelu.quartz.task.PaymentInfoTask; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @Description 手动处理支付定时任务控制器 + * @Author 纪寒 + * @Date 2022-10-21 15:11:38 + * @Version 1.0 + */ +@RestController +@RequestMapping("/monitor/payTask") +public class PaymentInfoTaskController { + + @Resource + private PaymentInfoTask paymentInfoTask; + + /** + * 手动执行关闭订单定时任务 + * + * @throws Exception 异常信息 + */ + @GetMapping("/handCloseOrder") + public void handCloseOrder() throws Exception { + paymentInfoTask.automaticOrderTask(); + } + + /** + * 手动执行更新订单状态定时任务 + * + * @throws Exception 异常信息 + */ + @GetMapping("/handOrderStatus") + public void handOrderStatus() throws Exception { + paymentInfoTask.automaticUpdateOrderStatusTask(); + } +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/RefundInfoTaskController.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/RefundInfoTaskController.java new file mode 100644 index 0000000..2ef9ba8 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/controller/RefundInfoTaskController.java @@ -0,0 +1,32 @@ +package com.xinelu.quartz.controller; + +import com.xinelu.quartz.task.RefundInfoTask; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @Description 手动处理退款定时任务控制器 + * @Author 纪寒 + * @Date 2022-10-25 14:39:10 + * @Version 1.0 + */ +@RestController +@RequestMapping("/monitor/refundTask") +public class RefundInfoTaskController { + + @Resource + private RefundInfoTask refundInfoTask; + + /** + * 手动执行修改退款单状态定时任务 + * + * @throws Exception 异常信息 + */ + @GetMapping("/handleRefundStatus") + public void handleRefundStatus() throws Exception { + refundInfoTask.automaticProcessRefundInfo(); + } +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/CouponTaskService.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/CouponTaskService.java new file mode 100644 index 0000000..a9606df --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/CouponTaskService.java @@ -0,0 +1,15 @@ +package com.xinelu.quartz.service; + +/** + * @Description 优惠券定时任务业务层 + * @Author 纪寒 + * @Date 2023-02-28 14:58:30 + * @Version 1.0 + */ +public interface CouponTaskService { + + /** + * 自动修改会员领取已过期的优惠券状态,第二天凌晨12点半执行一次 + */ + void patientCouponReceiveStatusTask(); +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/PaymentInfoTaskService.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/PaymentInfoTaskService.java new file mode 100644 index 0000000..43d2976 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/PaymentInfoTaskService.java @@ -0,0 +1,24 @@ +package com.xinelu.quartz.service; + +/** + * @Description 支付定时任务业务层 + * @Author 纪寒 + * @Date 2022-10-21 15:03:05 + * @Version 1.0 + */ +public interface PaymentInfoTaskService { + + /** + * 自动关闭商品订单24小时与预约订单2小时未支付的订单信息,每10分钟执行一次 + * + * @throws Exception 异常信息 + */ + void automaticOrderTask() throws Exception; + + /** + * 自动修改未支付的订单状态,每10分钟执行一次 + * + * @throws Exception 异常信息 + */ + void automaticUpdateOrderStatusTask() throws Exception; +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/RefundInfoTaskService.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/RefundInfoTaskService.java new file mode 100644 index 0000000..339a24f --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/RefundInfoTaskService.java @@ -0,0 +1,18 @@ +package com.xinelu.quartz.service; + +/** + * @Description 微信退款定时任务业务层 + * @Author 纪寒 + * @Date 2022-10-25 14:32:12 + * @Version 1.0 + */ +public interface RefundInfoTaskService { + + /** + * 自动处理退款状态定时任务, + * 防止由于网络等其它原因未接受到退款回调通知进而导致的退款单状态修改不及时 + * + * @throws Exception 异常信息 + */ + void automaticProcessRefundInfo() throws Exception; +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/CouponTaskServiceImpl.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/CouponTaskServiceImpl.java new file mode 100644 index 0000000..81fdb73 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/CouponTaskServiceImpl.java @@ -0,0 +1,64 @@ +package com.xinelu.quartz.service.impl; + +import com.xinelu.common.enums.CouponUseStatusEnum; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.manage.domain.patientcouponreceive.PatientCouponReceive; +import com.xinelu.manage.mapper.patientcouponreceive.PatientCouponReceiveMapper; +import com.xinelu.quartz.service.CouponTaskService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @Description + * @Author 纪寒 + * @Date 2023-02-28 14:59:01 + * @Version 1.0 + */ +@Service +@Slf4j +public class CouponTaskServiceImpl implements CouponTaskService { + + @Resource(name = "transactionManager") + private DataSourceTransactionManager transactionManager; + @Resource + private PatientCouponReceiveMapper patientCouponReceiveMapper; + + /** + * 自动修改会员领取已过期的优惠券状态,第二天凌晨12点半执行一次 + */ + @Override + public void patientCouponReceiveStatusTask() { + log.info("开始执行修改会员领取已过期的优惠券状态定时任务......"); + LocalDateTime timePrevious = LocalDateTime.now(); + List patientCouponReceiveList = patientCouponReceiveMapper.getPatientCouponExpirationEndTime(timePrevious, CouponUseStatusEnum.EXPIRED.getInfo()); + if (CollectionUtils.isEmpty(patientCouponReceiveList)) { + return; + } + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + List idList = patientCouponReceiveList.stream().filter(item -> Objects.nonNull(item.getId())).map(PatientCouponReceive::getId).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(idList)) { + int insertCount = patientCouponReceiveMapper.updateCouponReceiveStatus(CouponUseStatusEnum.EXPIRED.getInfo(), idList); + if (insertCount <= 0) { + throw new ServiceException("更新修改券状态失败!"); + } + } + transactionManager.commit(transactionStatus); + log.info("完成修改会员领取已过期的优惠券状态定时任务......"); + } catch (Exception e) { + log.error("修改会员领取已过期的优惠券状态定时任务失败,失败原因为 =====> {}", e.getMessage()); + transactionManager.rollback(transactionStatus); + throw e; + } + } +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/PaymentInfoTaskServiceImpl.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/PaymentInfoTaskServiceImpl.java new file mode 100644 index 0000000..57e9c87 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/PaymentInfoTaskServiceImpl.java @@ -0,0 +1,386 @@ +package com.xinelu.quartz.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.xinelu.applet.service.goodstock.GoodsStockService; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatPaymentService; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.OrderStatusInfoVO; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatQueryOrderVO; +import com.xinelu.common.config.XylWeChatPaymentConfig; +import com.xinelu.common.config.YlypWeChatPaymentConfig; +import com.xinelu.common.constant.Constants; +import com.xinelu.common.enums.*; +import com.xinelu.manage.domain.appointmentorder.AppointmentOrder; +import com.xinelu.manage.domain.goodsOrder.GoodsOrder; +import com.xinelu.manage.domain.paymentinfo.PaymentInfo; +import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper; +import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper; +import com.xinelu.manage.mapper.patientcouponreceive.PatientCouponReceiveMapper; +import com.xinelu.manage.mapper.paymentinfo.PaymentInfoMapper; +import com.xinelu.manage.vo.AppointOrderAndDetailsInfo; +import com.xinelu.manage.vo.goods.GoodsOrderAndDetailsInfo; +import com.xinelu.quartz.service.PaymentInfoTaskService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.BooleanUtils; +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; + +import javax.annotation.Resource; +import javax.sql.rowset.serial.SerialException; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @Description 支付定时任务实现类 + * @Author 纪寒 + * @Date 2022-10-21 15:03:41 + * @Version 1.0 + */ +@Slf4j +@Service +public class PaymentInfoTaskServiceImpl implements PaymentInfoTaskService { + + @Resource + private AppointmentOrderMapper appointmentOrderMapper; + @Resource + private GoodsOrderMapper goodsOrderMapper; + @Resource + private WeChatPaymentService weChatPaymentService; + @Resource + private PaymentInfoMapper paymentInfoMapper; + @Resource + private XylWeChatPaymentConfig xylWeChatPaymentConfig; + @Resource + private YlypWeChatPaymentConfig ylypWeChatPaymentConfig; + @Resource(name = "transactionManager") + private DataSourceTransactionManager transactionManager; + @Resource + private GoodsStockService goodsStockService; + @Resource + private RedisTemplate redisTemplate; + @Resource + private PatientCouponReceiveMapper patientCouponReceiveMapper; + + /** + * 成功状态码 + */ + private static final int SUCCESS_CODE = 200; + + /** + * 成功状态码 + */ + private static final int SUCCESS_CODE_TWO = 204; + + /** + * 自动关闭商品订单2小时与预约订单2小时未支付的订单信息,每10分钟执行一次 + * + * @throws Exception 异常信息 + */ + @Override + public void automaticOrderTask() throws Exception { + LocalDateTime localDateTime = LocalDateTime.now().minusHours(2); + List appointmentOrderList = appointmentOrderMapper.getWaitPayAppointmentOrderList(localDateTime); + List goodsOrderList = goodsOrderMapper.getWaitPayGoodsOrderList(localDateTime); + if (CollectionUtils.isEmpty(appointmentOrderList) && CollectionUtils.isEmpty(goodsOrderList)) { + return; + } + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + List successAppointOrderList = Lists.newArrayList(); + try { + if (CollectionUtils.isNotEmpty(appointmentOrderList)) { + successAppointOrderList = processAppointmentOrderInfo(appointmentOrderList); + } + if (CollectionUtils.isNotEmpty(goodsOrderList)) { + processGoodsOrderInfo(goodsOrderList); + } + transactionManager.commit(transactionStatus); + } catch (Exception e) { + transactionManager.rollback(transactionStatus); + log.error("自动关闭订单失败,失败原因为 =====> {}", e.getMessage()); + if (CollectionUtils.isNotEmpty(successAppointOrderList)) { + for (AppointOrderAndDetailsInfo orderAndDetailsInfo : successAppointOrderList) { + String appointLimitCountKey = Constants.PRE_APPOINTMENT_LIMIT_COUNT_KEY + orderAndDetailsInfo.getNurseStationItemId() + "_" + orderAndDetailsInfo.getServiceDate() + "_" + orderAndDetailsInfo.getServiceStartTime(); + RedisAtomicLong redisAtomicLong = new RedisAtomicLong(appointLimitCountKey, Objects.requireNonNull(redisTemplate.getConnectionFactory())); + if (redisAtomicLong.get() > 0) { + redisAtomicLong.decrementAndGet(); + } + } + } + throw e; + } + } + + /** + * 自动修改未支付的订单状态,每15分钟执行一次 + * + * @throws Exception 异常信息 + */ + @Override + public void automaticUpdateOrderStatusTask() throws Exception { + List appointmentOrderList = appointmentOrderMapper.getWaitPayStatusAppointmentOrderList(OrderStatusEnum.WAIT_PAY.getInfo()); + List goodsOrderList = goodsOrderMapper.getWaitPayStatusGoodsOrderList(GooodsOrderStatusEnum.WAIT_PAY.getInfo()); + if (CollectionUtils.isEmpty(appointmentOrderList) && CollectionUtils.isEmpty(goodsOrderList)) { + return; + } + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + if (CollectionUtils.isNotEmpty(appointmentOrderList)) { + updateAppointmentPaymentInfo(appointmentOrderList); + } + if (CollectionUtils.isNotEmpty(goodsOrderList)) { + updateGoodsPaymentInfo(goodsOrderList); + } + transactionManager.commit(transactionStatus); + } catch (Exception e) { + log.error("修改订单状态定时任务失败,失败原因为 =====> {}", e.getMessage()); + transactionManager.rollback(transactionStatus); + throw e; + } + } + + /** + * 处理预约订单状态 + * + * @param appointmentOrderList 预约订单列表 + * @return List 处理成功的预约订单信息 + * @throws Exception 异常信息 + */ + private List processAppointmentOrderInfo(List appointmentOrderList) throws Exception { + List successList = Lists.newArrayList(); + List closedOrderList = Lists.newArrayList(); + for (AppointOrderAndDetailsInfo orderAndDetailsInfo : appointmentOrderList) { + if (StringUtils.isBlank(orderAndDetailsInfo.getOrderNo())) { + continue; + } + OrderStatusInfoVO vo = new OrderStatusInfoVO(); + OrderStatusInfoVO statusInfoVO = weChatPaymentService.queryAppointmentOrderStatus(orderAndDetailsInfo.getOrderNo(), vo); + if (BooleanUtils.isFalse(statusInfoVO.getPayFlag())) { + closedOrderList.add(orderAndDetailsInfo.getOrderNo()); + continue; + } + int statusCode = weChatPaymentService.closeWeChatOrderInfo(orderAndDetailsInfo.getOrderNo(), BuySourceEnum.NURSE_STATION.getInfo()); + if (statusCode == SUCCESS_CODE || statusCode == SUCCESS_CODE_TWO) { + updateAppointmentOrderStatus(orderAndDetailsInfo, successList); + } + } + if (CollectionUtils.isNotEmpty(closedOrderList)) { + List waitPayAppointList = appointmentOrderMapper.getWaitPayAppointList(closedOrderList); + if (CollectionUtils.isNotEmpty(waitPayAppointList)) { + for (AppointOrderAndDetailsInfo orderAndDetailsInfo : waitPayAppointList) { + updateAppointmentOrderStatus(orderAndDetailsInfo, successList); + } + } + } + return successList; + } + + /** + * 处理商品订单信息 + * + * @param goodsOrderList 商品订单列表 + * @throws Exception 异常信息 + */ + private void processGoodsOrderInfo(List goodsOrderList) throws Exception { + List orderList = Lists.newArrayList(); + List closedOrderList = Lists.newArrayList(); + for (GoodsOrderAndDetailsInfo goodOrder : goodsOrderList) { + if (StringUtils.isBlank(goodOrder.getOrderNo())) { + continue; + } + OrderStatusInfoVO vo = new OrderStatusInfoVO(); + OrderStatusInfoVO statusInfoVO = weChatPaymentService.queryGoodsOrderStatus(goodOrder.getOrderNo(), vo, goodOrder.getBuySource()); + if (BooleanUtils.isFalse(statusInfoVO.getPayFlag())) { + closedOrderList.add(goodOrder.getOrderNo()); + continue; + } + int statusCode = weChatPaymentService.closeWeChatOrderInfo(goodOrder.getOrderNo(), goodOrder.getBuySource()); + if (statusCode == SUCCESS_CODE || statusCode == SUCCESS_CODE_TWO) { + orderList.add(goodOrder.getOrderNo()); + } + if (StringUtils.isNotBlank(goodOrder.getOrderType()) && !OrderTypeEnum.HEALTH_CONSULTATION.getInfo().equals(goodOrder.getOrderType())) { + goodsStockService.addGoodsStockInfo(goodOrder.getOrderNo(), goodOrder.getGoodsAttributeDetailsId(), Objects.isNull(goodOrder.getGoodsCount()) ? 0 : goodOrder.getGoodsCount()); + if (Objects.nonNull(goodOrder.getCouponId()) && Objects.nonNull(goodOrder.getPatientId())) { + patientCouponReceiveMapper.updatePatientCouponUseStatus(goodOrder.getPatientId(), goodOrder.getCouponId(), CouponUseStatusEnum.NOT_USED.getInfo()); + } + } + } + if (CollectionUtils.isNotEmpty(orderList)) { + log.info("自动修改商品订单信息状态 ======> [{}]", orderList); + goodsOrderMapper.updateBatchGoodsOrderStatus(orderList, GooodsOrderStatusEnum.CANCEL.getInfo()); + } + if (CollectionUtils.isNotEmpty(closedOrderList)) { + //说明当前订单在微信系统中已经关闭,需要重新确认本地订单状态是否同步更新,未更新则需要同步更新并且新增库存数量 + List goodsOrderInfoList = goodsOrderMapper.getWaitPayGoodsOrderCountList(closedOrderList); + if (CollectionUtils.isNotEmpty(goodsOrderInfoList)) { + //新增库存数量和修改优惠券状态,健康咨询类型的订单无需新增库存数量和退还优惠券 + for (GoodsOrderAndDetailsInfo detailsInfo : goodsOrderInfoList) { + if (StringUtils.isNotBlank(detailsInfo.getOrderType()) && OrderTypeEnum.HEALTH_CONSULTATION.getInfo().equals(detailsInfo.getOrderType())) { + continue; + } + goodsStockService.addGoodsStockInfo(detailsInfo.getOrderNo(), detailsInfo.getGoodsAttributeDetailsId(), Objects.isNull(detailsInfo.getGoodsCount()) ? 0 : detailsInfo.getGoodsCount()); + if (Objects.nonNull(detailsInfo.getCouponId()) && Objects.nonNull(detailsInfo.getPatientId())) { + patientCouponReceiveMapper.updatePatientCouponUseStatus(detailsInfo.getPatientId(), detailsInfo.getCouponId(), CouponUseStatusEnum.NOT_USED.getInfo()); + } + } + //批量更新本地订单状态,将订单状态修改为已取消 + List orderNos = goodsOrderInfoList.stream().filter(item -> StringUtils.isNotBlank(item.getOrderNo())).map(GoodsOrderAndDetailsInfo::getOrderNo).distinct().collect(Collectors.toList()); + goodsOrderMapper.updateBatchGoodsOrderStatus(orderNos, GooodsOrderStatusEnum.CANCEL.getInfo()); + } + } + } + + /** + * 新增预约订单支付信息 + * + * @param appointmentOrderList 预约订单列表 + * @throws Exception 异常信息 + */ + private void updateAppointmentPaymentInfo(List appointmentOrderList) throws Exception { + for (AppointmentOrder appointmentOrder : appointmentOrderList) { + if (StringUtils.isBlank(appointmentOrder.getOrderNo())) { + continue; + } + OrderStatusInfoVO vo = new OrderStatusInfoVO(); + OrderStatusInfoVO statusInfoVO = weChatPaymentService.queryAppointmentOrderStatus(appointmentOrder.getOrderNo(), vo); + if (StringUtils.isNotBlank(statusInfoVO.getTradeStatus()) + && WeChatTradeStateEnum.SUCCESS.getInfo().equals(statusInfoVO.getTradeStatus())) { + insertAppointmentOrderPaymentInfo(appointmentOrder, statusInfoVO); + } + } + } + + /** + * 新增商品订单支付信息 + * + * @param goodsOrderList 商品订单列表 + * @throws Exception 异常信息 + */ + private void updateGoodsPaymentInfo(List goodsOrderList) throws Exception { + for (GoodsOrder goodsOrder : goodsOrderList) { + if (Objects.isNull(goodsOrder) || StringUtils.isBlank(goodsOrder.getOrderNo())) { + continue; + } + OrderStatusInfoVO vo = new OrderStatusInfoVO(); + OrderStatusInfoVO statusInfoVO = weChatPaymentService.queryGoodsOrderStatus(goodsOrder.getOrderNo(), vo, goodsOrder.getBuySource()); + if (StringUtils.isNotBlank(statusInfoVO.getTradeStatus()) + && WeChatTradeStateEnum.SUCCESS.getInfo().equals(statusInfoVO.getTradeStatus())) { + insertGoodsOrderPaymentInfo(goodsOrder, statusInfoVO); + } + } + } + + /** + * 修改预约订单状态和记录预约订单信息 + * + * @param appointmentOrder 预约订单信息 + * @param vo 微信查单信息 + * @throws Exception 异常信息 + */ + private void insertAppointmentOrderPaymentInfo(AppointmentOrder appointmentOrder, OrderStatusInfoVO vo) throws Exception { + appointmentOrderMapper.updateAppointmentOrderStatus(OrderStatusEnum.WAIT_DISPATCH.getInfo(), appointmentOrder.getOrderNo()); + int paymentInfoCount = paymentInfoMapper.getPaymentInfoByOrderNo(appointmentOrder.getOrderNo()); + if (paymentInfoCount <= 0) { + WeChatQueryOrderVO weChatQueryOrderVO = vo.getWeChatQueryOrderVO(); + PaymentInfo paymentInfo = buildPaymentInfo(weChatQueryOrderVO, null, appointmentOrder); + int insertCount = paymentInfoMapper.insertPaymentInfo(paymentInfo); + if (insertCount <= 0) { + log.error("记录预约订单支付日志出错,参数为 ====> [{}]", paymentInfo); + throw new SerialException("记录预约订单支付日志出错,请联系管理员!"); + } + } + } + + /** + * 修改商品订单状态和记录商品订单支付日志信息 + * + * @param goodsOrder 商品订单信息 + * @param vo 微信查单信息 + * @throws Exception 异常信息 + */ + private void insertGoodsOrderPaymentInfo(GoodsOrder goodsOrder, OrderStatusInfoVO vo) throws Exception { + int paymentInfoCount = 0; + if (Objects.nonNull(goodsOrder)) { + goodsOrderMapper.updateGoodsOrderStatus(GooodsOrderStatusEnum.WAIT_RECEIVED_GOODS.getInfo(), goodsOrder.getOrderNo()); + paymentInfoCount = paymentInfoMapper.getPaymentInfoByOrderNo(goodsOrder.getOrderNo()); + } + if (paymentInfoCount <= 0) { + WeChatQueryOrderVO weChatQueryOrderVO = vo.getWeChatQueryOrderVO(); + PaymentInfo paymentInfo = buildPaymentInfo(weChatQueryOrderVO, goodsOrder, null); + int insertCount = paymentInfoMapper.insertPaymentInfo(paymentInfo); + if (insertCount <= 0) { + log.error("记录商品或者学习培训支付订单支付日志出错,参数为 ====> [{}]", paymentInfo); + throw new SerialException("记录商品或者学习培训支付订单支付日志出错,请联系管理员!"); + } + } + } + + /** + * 构建支付信息参数 + * + * @param weChatQueryOrderVO 微信查单返回结果信息 + * @param goodsOrderInfo 商品订单信息 + * @param appointmentOrderInfo 预约订单信息 + */ + public PaymentInfo buildPaymentInfo(WeChatQueryOrderVO weChatQueryOrderVO, GoodsOrder goodsOrderInfo, + AppointmentOrder appointmentOrderInfo) { + PaymentInfo paymentInfo = new PaymentInfo(); + if (Objects.nonNull(goodsOrderInfo)) { + paymentInfo.setPatientId(Objects.isNull(goodsOrderInfo.getPatientId()) ? null : goodsOrderInfo.getPatientId()); + paymentInfo.setOrderNo(StringUtils.isBlank(goodsOrderInfo.getOrderNo()) ? "" : goodsOrderInfo.getOrderNo()); + paymentInfo.setPayChannel(StringUtils.isBlank(goodsOrderInfo.getOrderChannel()) ? "" : goodsOrderInfo.getOrderChannel()); + } else if (Objects.nonNull(appointmentOrderInfo)) { + paymentInfo.setPatientId(Objects.isNull(appointmentOrderInfo.getPatientId()) ? null : appointmentOrderInfo.getPatientId()); + paymentInfo.setOrderNo(StringUtils.isBlank(appointmentOrderInfo.getOrderNo()) ? "" : appointmentOrderInfo.getOrderNo()); + paymentInfo.setPayChannel(StringUtils.isBlank(appointmentOrderInfo.getOrderChannel()) ? "" : appointmentOrderInfo.getOrderChannel()); + } + paymentInfo.setTransactionNo(StringUtils.isBlank(weChatQueryOrderVO.getTransactionId()) ? "" : weChatQueryOrderVO.getTransactionId()); + paymentInfo.setPayPrice(BigDecimal.valueOf(Objects.isNull(weChatQueryOrderVO.getAmount().getPayerTotal()) ? 0 : weChatQueryOrderVO.getAmount().getPayerTotal()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_DOWN)); + paymentInfo.setPayType(PayTypeEnum.WECHAT_PAY.getInfo()); + paymentInfo.setWechatTradeState(StringUtils.isBlank(weChatQueryOrderVO.getTradeState()) ? "" : weChatQueryOrderVO.getTradeState()); + paymentInfo.setPayNotifyContent(JSON.toJSON(weChatQueryOrderVO).toString()); + paymentInfo.setPayTime(LocalDateTime.parse(StringUtils.isBlank(weChatQueryOrderVO.getSuccessTime()) ? "" : weChatQueryOrderVO.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME)); + if (weChatQueryOrderVO.getMchid().equals(xylWeChatPaymentConfig.getXylMchId())) { + paymentInfo.setPaymentMerchantType(PaymentMerchantTypeEnum.XINYILU.getInfo()); + } + if (weChatQueryOrderVO.getMchid().equals(ylypWeChatPaymentConfig.getYlypMchId())) { + paymentInfo.setPaymentMerchantType(PaymentMerchantTypeEnum.YILUYOUPIN.getInfo()); + } + paymentInfo.setDelFlag(0); + paymentInfo.setCreateTime(LocalDateTime.now()); + return paymentInfo; + } + + /** + * 修改预约订单状态和修改预约次数上限 + * + * @param orderAndDetailsInfo 输入参数 + * @param successList 处理成功的订单集合 + */ + private void updateAppointmentOrderStatus(AppointOrderAndDetailsInfo orderAndDetailsInfo, List successList) { + //关闭系统内部预约订单 + List orderList = Collections.singletonList(orderAndDetailsInfo.getOrderNo()); + appointmentOrderMapper.updateBatchAppointmentOrderStatus(orderList, OrderStatusEnum.CANCEL.getInfo()); + //新增Redis中的预约次数 + if (Objects.nonNull(orderAndDetailsInfo.getAppointmentLimitCount()) && StringUtils.equals(AppointmentOrderTypeEnum.OTHER.getInfo(), orderAndDetailsInfo.getOrderType())) { + String appointLimitCountKey = Constants.PRE_APPOINTMENT_LIMIT_COUNT_KEY + orderAndDetailsInfo.getNurseStationItemId() + "_" + orderAndDetailsInfo.getServiceDate() + "_" + orderAndDetailsInfo.getServiceStartTime(); + RedisAtomicLong redisAtomicLong = new RedisAtomicLong(appointLimitCountKey, Objects.requireNonNull(redisTemplate.getConnectionFactory())); + if (redisAtomicLong.get() >= 0 && redisAtomicLong.get() < orderAndDetailsInfo.getAppointmentLimitCount()) { + //预约次数加一 + redisAtomicLong.incrementAndGet(); + successList.add(orderAndDetailsInfo); + } + } + } + +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/RefundInfoTaskServiceImpl.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/RefundInfoTaskServiceImpl.java new file mode 100644 index 0000000..30436c3 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/service/impl/RefundInfoTaskServiceImpl.java @@ -0,0 +1,164 @@ +package com.xinelu.quartz.service.impl; + +import com.xinelu.applet.service.goodstock.GoodsStockService; +import com.xinelu.applet.service.wechatpaymentinfo.WeChatRefundService; +import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatRefundInfoVO; +import com.xinelu.common.constant.Constants; +import com.xinelu.common.enums.*; +import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper; +import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper; +import com.xinelu.manage.mapper.patientcouponreceive.PatientCouponReceiveMapper; +import com.xinelu.manage.mapper.refundinfo.RefundInfoMapper; +import com.xinelu.manage.vo.RefundOrderInfoVO; +import com.xinelu.quartz.service.RefundInfoTaskService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.compress.utils.Lists; +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; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * @Description 微信退款定时任务业务层实现类 + * @Author 纪寒 + * @Date 2022-10-25 14:32:40 + * @Version 1.0 + */ +@Slf4j +@Service +public class RefundInfoTaskServiceImpl implements RefundInfoTaskService { + + @Resource + private AppointmentOrderMapper appointmentOrderMapper; + @Resource + private GoodsOrderMapper goodsOrderMapper; + @Resource(name = "transactionManager") + private DataSourceTransactionManager transactionManager; + @Resource + private WeChatRefundService weChatRefundService; + @Resource + private RefundInfoMapper refundInfoMapper; + @Resource + private GoodsStockService goodsStockService; + @Resource + private RedisTemplate redisTemplate; + @Resource + private PatientCouponReceiveMapper patientCouponReceiveMapper; + + /** + * 自动处理退款状态定时任务,每15分钟执行一次 + * 防止由于网络等其它原因未接受到退款回调通知进而导致的退款单状态修改不及时 + * + * @throws Exception 异常信息 + */ + @Override + public void automaticProcessRefundInfo() throws Exception { + List appointmentOrderList = appointmentOrderMapper.getRefundAppointmentOrderInfo(OrderStatusEnum.WAIT_REFUND.getInfo(), RefundStatusEnum.PROCESSING.getInfo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo()); + List refundGoodsOrderLit = goodsOrderMapper.getRefundGoodsOrderInfo(GooodsOrderStatusEnum.WAIT_REFUND.getInfo(), RefundStatusEnum.PROCESSING.getInfo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo()); + if (CollectionUtils.isEmpty(appointmentOrderList) && CollectionUtils.isEmpty(refundGoodsOrderLit)) { + return; + } + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + List successAppointOrderList = Lists.newArrayList(); + try { + if (CollectionUtils.isNotEmpty(appointmentOrderList)) { + successAppointOrderList = processAppointmentRefundInfo(appointmentOrderList); + } + if (CollectionUtils.isNotEmpty(refundGoodsOrderLit)) { + processGoodsRefundInfo(refundGoodsOrderLit); + } + transactionManager.commit(transactionStatus); + } catch (Exception e) { + transactionManager.rollback(transactionStatus); + log.error("处理微信退款定时任务异常,异常信息 =====> {}", e.getMessage()); + if (CollectionUtils.isNotEmpty(successAppointOrderList)) { + for (RefundOrderInfoVO refundOrderInfoVO : successAppointOrderList) { + String appointLimitCountKey = Constants.PRE_APPOINTMENT_LIMIT_COUNT_KEY + refundOrderInfoVO.getNurseStationItemId() + "_" + refundOrderInfoVO.getServiceDate() + "_" + refundOrderInfoVO.getServiceStartTime(); + RedisAtomicLong redisAtomicLong = new RedisAtomicLong(appointLimitCountKey, Objects.requireNonNull(redisTemplate.getConnectionFactory())); + if (redisAtomicLong.get() > 0) { + redisAtomicLong.decrementAndGet(); + } + } + } + throw e; + } + } + + /** + * 处理退款的预约订单信息 + * + * @param appointmentOrderList 预约订单列表信息 + * @return 处理成功的订单编号集合 + */ + private List processAppointmentRefundInfo(List appointmentOrderList) { + List successList = Lists.newArrayList(); + for (RefundOrderInfoVO refundOrderInfoVO : appointmentOrderList) { + if (StringUtils.isBlank(refundOrderInfoVO.getOrderNo()) || StringUtils.isBlank(refundOrderInfoVO.getOutRefundNo())) { + continue; + } + WeChatRefundInfoVO weChatRefundInfoVO = weChatRefundService.queryAppointmentOrderRefundStatus(refundOrderInfoVO.getOutRefundNo()); + if (StringUtils.isNotBlank(weChatRefundInfoVO.getStatus()) + && RefundStatusEnum.SUCCESS.getInfo().equals(weChatRefundInfoVO.getStatus())) { + List orderNoList = Collections.singletonList(refundOrderInfoVO.getOrderNo()); + LocalDateTime successTime = StringUtils.isBlank(weChatRefundInfoVO.getSuccessTime()) ? LocalDateTime.now() : LocalDateTime.parse(weChatRefundInfoVO.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME); + refundInfoMapper.updateBatchRefundStatus(refundOrderInfoVO.getOrderNo(), successTime, weChatRefundInfoVO.getStatus()); + appointmentOrderMapper.updateBatchAppointmentOrderStatus(orderNoList, OrderStatusEnum.REFUNDED.getInfo()); + if (Objects.nonNull(refundOrderInfoVO.getAppointmentLimitCount()) && StringUtils.equals(AppointmentOrderTypeEnum.OTHER.getInfo(), refundOrderInfoVO.getAppointmentOrderType())) { + String appointLimitCountKey = Constants.PRE_APPOINTMENT_LIMIT_COUNT_KEY + refundOrderInfoVO.getNurseStationItemId() + "_" + refundOrderInfoVO.getServiceDate() + "_" + refundOrderInfoVO.getServiceStartTime(); + RedisAtomicLong redisAtomicLong = new RedisAtomicLong(appointLimitCountKey, Objects.requireNonNull(redisTemplate.getConnectionFactory())); + if (redisAtomicLong.get() >= 0 && redisAtomicLong.get() < refundOrderInfoVO.getAppointmentLimitCount()) { + redisAtomicLong.incrementAndGet(); + successList.add(refundOrderInfoVO); + } + } + } + } + return successList; + } + + /** + * 处理退款成功的商品订单信息 + * + * @param refundGoodsOrderLit 商品退款订单信息 + * @throws Exception 异常信息 + */ + private void processGoodsRefundInfo(List refundGoodsOrderLit) throws Exception { + List orderNoList = Lists.newArrayList(); + for (RefundOrderInfoVO refundOrderInfoVO : refundGoodsOrderLit) { + if (StringUtils.isBlank(refundOrderInfoVO.getOrderNo()) + || StringUtils.isBlank(refundOrderInfoVO.getOutRefundNo()) || StringUtils.isBlank(refundOrderInfoVO.getBuySource())) { + continue; + } + WeChatRefundInfoVO weChatRefundInfoVO = weChatRefundService.queryGoodsOrderRefundStatus(refundOrderInfoVO.getOutRefundNo(), refundOrderInfoVO.getBuySource()); + if (StringUtils.isNotBlank(weChatRefundInfoVO.getStatus()) + && RefundStatusEnum.SUCCESS.getInfo().equals(weChatRefundInfoVO.getStatus())) { + orderNoList.add(refundOrderInfoVO.getOrderNo()); + } + log.info("修改商品订单表状态和增加库存数量,订单编号=====> {}, 商品属性明细表id======> {}", refundOrderInfoVO.getOrderNo(), refundOrderInfoVO.getGoodsAttributeDetailsId()); + LocalDateTime successTime = StringUtils.isBlank(weChatRefundInfoVO.getSuccessTime()) ? LocalDateTime.now() : LocalDateTime.parse(weChatRefundInfoVO.getSuccessTime(), DateTimeFormatter.ISO_DATE_TIME); + refundInfoMapper.updateBatchRefundStatus(refundOrderInfoVO.getOrderNo(), successTime, weChatRefundInfoVO.getStatus()); + if (StringUtils.isNotBlank(refundOrderInfoVO.getOrderType()) && !OrderTypeEnum.HEALTH_CONSULTATION.getInfo().equals(refundOrderInfoVO.getOrderType())) { + String stockNum = StringUtils.isBlank(refundOrderInfoVO.getGoodsCount()) ? "0" : refundOrderInfoVO.getGoodsCount(); + goodsStockService.addGoodsStockInfo(refundOrderInfoVO.getOrderNo(), refundOrderInfoVO.getGoodsAttributeDetailsId(), Integer.parseInt(stockNum)); + if (Objects.nonNull(refundOrderInfoVO.getCouponId()) && Objects.nonNull(refundOrderInfoVO.getPatientId())) { + patientCouponReceiveMapper.updatePatientCouponUseStatus(refundOrderInfoVO.getPatientId(), refundOrderInfoVO.getCouponId(), CouponUseStatusEnum.NOT_USED.getInfo()); + } + } + } + if (CollectionUtils.isNotEmpty(orderNoList)) { + goodsOrderMapper.updateBatchGoodsOrderStatus(orderNoList, OrderStatusEnum.REFUNDED.getInfo()); + } + } + +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/task/CouponTask.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/CouponTask.java new file mode 100644 index 0000000..4fda424 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/CouponTask.java @@ -0,0 +1,28 @@ +package com.xinelu.quartz.task; + +import com.xinelu.quartz.service.CouponTaskService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Description 优惠券相关定时任务 + * @Author 纪寒 + * @Date 2023-02-28 14:57:33 + * @Version 1.0 + */ +@Slf4j +@Component("couponTask") +public class CouponTask { + + @Resource + private CouponTaskService couponTaskService; + + /** + * 自动修改会员领取已过期的优惠券状态,第二天凌晨12点半执行一次 + */ + public void patientCouponReceiveStatusTask() { + couponTaskService.patientCouponReceiveStatusTask(); + } +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/task/PaymentInfoTask.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/PaymentInfoTask.java new file mode 100644 index 0000000..5e569e8 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/PaymentInfoTask.java @@ -0,0 +1,45 @@ +package com.xinelu.quartz.task; + +import com.xinelu.quartz.service.PaymentInfoTaskService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Description 支付定时任务类 + * @Author 纪寒 + * @Date 2022-10-21 14:34:36 + * @Version 1.0 + */ +@Slf4j +@Component("paymentTask") +public class PaymentInfoTask { + + @Resource + private PaymentInfoTaskService paymentInfoTaskService; + + /** + * 自动关闭商品订单2小时与预约订单2小时未支付的订单信息,每15分钟执行一次 + * + * @throws Exception 异常信息 + */ + public void automaticOrderTask() throws Exception { + log.info("开始执行自动关闭订单定时任务......"); + paymentInfoTaskService.automaticOrderTask(); + log.info("完成自动关闭订单定时任务......"); + } + + /** + * 自动修改未支付的订单状态,每15分钟执行一次 + * 该定时任务用与接收微信支付回调通知失败或者网络原因导致微信支付系统支付成功,但是本地订单状态未同步更新的情况 + * + * @throws Exception 异常信息 + */ + public void automaticUpdateOrderStatusTask() throws Exception { + log.info("开始执行修改订单状态定时任务......"); + paymentInfoTaskService.automaticUpdateOrderStatusTask(); + log.info("完成修改订单状态定时任务......"); + } + +} diff --git a/xinelu-quartz/src/main/java/com/xinelu/quartz/task/RefundInfoTask.java b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/RefundInfoTask.java new file mode 100644 index 0000000..de957c4 --- /dev/null +++ b/xinelu-quartz/src/main/java/com/xinelu/quartz/task/RefundInfoTask.java @@ -0,0 +1,33 @@ +package com.xinelu.quartz.task; + +import com.xinelu.quartz.service.RefundInfoTaskService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Description + * @Author 纪寒 + * @Date 2022-10-25 14:35:59 + * @Version 1.0 + */ +@Slf4j +@Component("refundTask") +public class RefundInfoTask { + + @Resource + private RefundInfoTaskService refundInfoTaskService; + + /** + * 自动处理退款状态定时任务, + * 防止由于网络等其它原因未接受到退款回调通知进而导致的退款单状态修改不及时 + * + * @throws Exception 异常信息 + */ + public void automaticProcessRefundInfo() throws Exception { + log.info("开始执行关闭退款状态定时任务........"); + refundInfoTaskService.automaticProcessRefundInfo(); + log.info("完成关闭退款状态定时任务........"); + } +}