商城相关功能代码移植

This commit is contained in:
纪寒 2023-10-16 17:00:31 +08:00
parent 8af9f2edf8
commit 3b6c9994dd
45 changed files with 4942 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String, Object> 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<String, Object> 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;
}
}

View File

@ -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<String, String> 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<String, Object> 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<String, Object> 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<String, Object> amountParamMap = new LinkedHashMap<>();
int totalPrice = paymentDTO.getPaymentPrice().multiply(BigDecimal.valueOf(100)).intValue();
amountParamMap.put("total", totalPrice);
amountParamMap.put("currency", "CNY");
paramMap.put("amount", amountParamMap);
Map<String, Object> payerParamMap = new LinkedHashMap<>();
payerParamMap.put("openid", paymentDTO.getOpenid());
paramMap.put("payer", payerParamMap);
return JSON.toJSONString(paramMap);
}
/**
* 请求JsApi下单接口方法
*
* @param jsApiParams 下单参数
* @param paymentDTO 输入参数
* @param prepayId JsAp下单接口返回值
* @return 结果
* @throws Exception 异常信息
*/
private String requestJsApiInterface(String jsApiParams, PaymentDTO paymentDTO, String prepayId) throws Exception {
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<String, Object> resultMap = JSONObject.parseObject(result);
prepayId = resultMap.getOrDefault("prepay_id", "").toString();
redisTemplate.opsForValue().set(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo(), prepayId, 5400, TimeUnit.SECONDS);
} else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
log.info("JsApi下单成功无返回值信息");
} else {
log.error("JsApi下单失败失败原因为 ====> {}", result);
throw new ServiceException("JsApi下单失败失败原因为" + result);
}
} catch (Exception e) {
log.error("JsApi下单失败失败原因 =====> {}", e.getMessage());
throw new ServiceException("JsApi下单失败请联系管理员");
} finally {
if (response != null) {
response.close();
}
}
return prepayId;
}
/**
* 构建微信小程序调起支付参数设置
*
* @param prepayId jsapi下单返回参数信息
* @param buySource 购买来源
* @return 参数信息
* @throws Exception 异常信息
*/
private WeChatAppletSignVO getSignInfo(String prepayId, String buySource) throws Exception {
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<String, Object> 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<String, Object> 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<String, Object> payerParamMap = new LinkedHashMap<>();
payerParamMap.put("openid", paymentDTO.getOpenid());
paramMap.put("payer", payerParamMap);
return JSON.toJSONString(paramMap);
}
}

View File

@ -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<String, Object> paramMap = new LinkedHashMap<>();
paramMap.put("out_trade_no", refundDTO.getOrderNo());
paramMap.put("out_refund_no", outRefundNo);
Map<String, Object> 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;
}
}

View File

@ -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("商户私钥文件不存在,请联系管理员!");
}
}
}

View File

@ -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;
/**
* 支付渠道手机AppMOBILE_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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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<PromotionDetailDTO> promotionDetail;
@JsonProperty("success_time")
private String successTime;
@JsonProperty("payer")
private PayerDTO payer;
@JsonProperty("out_trade_no")
private String outTradeNo;
@JsonProperty("appid")
private String appid;
@JsonProperty("trade_state_desc")
private String tradeStateDesc;
@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<GoodsDetailDTO> goodsDetail;
@NoArgsConstructor
@Data
public static class GoodsDetailDTO {
@JsonProperty("goods_remark")
private String goodsRemark;
@JsonProperty("quantity")
private Integer quantity;
@JsonProperty("discount_amount")
private Integer discountAmount;
@JsonProperty("goods_id")
private String goodsId;
@JsonProperty("unit_price")
private Integer unitPrice;
}
}
}

View File

@ -0,0 +1,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;
}
}

View File

@ -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<PromotionDetailDTO> promotionDetail;
@NoArgsConstructor
@Data
public static class AmountDTO {
@JsonProperty("total")
private Integer total;
@JsonProperty("refund")
private Integer refund;
@JsonProperty("from")
private List<FromDTO> from;
@JsonProperty("payer_total")
private Integer payerTotal;
@JsonProperty("payer_refund")
private Integer payerRefund;
@JsonProperty("settlement_refund")
private Integer settlementRefund;
@JsonProperty("settlement_total")
private Integer settlementTotal;
@JsonProperty("discount_refund")
private Integer discountRefund;
@JsonProperty("currency")
private String currency;
@NoArgsConstructor
@Data
public static class FromDTO {
@JsonProperty("account")
private String account;
@JsonProperty("amount")
private Integer amount;
}
}
@NoArgsConstructor
@Data
public static class PromotionDetailDTO {
@JsonProperty("promotion_id")
private String promotionId;
@JsonProperty("scope")
private String scope;
@JsonProperty("type")
private String type;
@JsonProperty("amount")
private Integer amount;
@JsonProperty("refund_amount")
private Integer refundAmount;
@JsonProperty("goods_detail")
private List<GoodsDetailDTO> goodsDetail;
@NoArgsConstructor
@Data
public static class GoodsDetailDTO {
@JsonProperty("merchant_goods_id")
private String merchantGoodsId;
@JsonProperty("wechatpay_goods_id")
private String wechatpayGoodsId;
@JsonProperty("goods_name")
private String goodsName;
@JsonProperty("unit_price")
private Integer unitPrice;
@JsonProperty("refund_amount")
private Integer refundAmount;
@JsonProperty("refund_quantity")
private Integer refundQuantity;
}
}
}

View File

@ -0,0 +1,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;
}
}

View File

@ -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;
/**
* 支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLET
*/
@ApiModelProperty(value = "支付渠道手机AppMOBILE_APP微信小程序WECHAT_APPLET支付宝小程序ALI_PAY_APPLET")
@Excel(name = "支付渠道手机AppMOBILE_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;
/**
* 是否删除01
*/
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();
}
}

View File

@ -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;
/**
* 是否删除01
*/
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();
}
}

View File

@ -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<PaymentInfo> selectPaymentInfoList(PaymentInfo paymentInfo);
/**
* 新增支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int insertPaymentInfo(PaymentInfo paymentInfo);
/**
* 修改支付记录信息
*
* @param paymentInfo 支付记录信息
* @return 结果
*/
int updatePaymentInfo(PaymentInfo paymentInfo);
/**
* 删除支付记录信息
*
* @param id 支付记录信息主键
* @return 结果
*/
int deletePaymentInfoById(Long id);
/**
* 批量删除支付记录信息
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deletePaymentInfoByIds(Long[] ids);
/**
* 根据订单查询支付记录表信息
*
* @param orderNo 订单编号
* @return 数量
*/
int getPaymentInfoByOrderNo(String orderNo);
}

View File

@ -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<RefundInfo> selectRefundInfoList(RefundInfo refundInfo);
/**
* 新增退款记录信息
*
* @param refundInfo 退款记录信息
* @return 结果
*/
int insertRefundInfo(RefundInfo refundInfo);
/**
* 修改退款记录信息
*
* @param refundInfo 退款记录信息
* @return 结果
*/
int updateRefundInfo(RefundInfo refundInfo);
/**
* 删除退款记录信息
*
* @param id 退款记录信息主键
* @return 结果
*/
int deleteRefundInfoById(Long id);
/**
* 批量删除退款记录信息
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deleteRefundInfoByIds(Long[] ids);
/**
* 修改退款单信息
*
* @param refundInfo 退款单信息
* @return 更新数量
*/
int updateRefundInfoByNo(RefundInfo refundInfo);
/**
* 批量更新支付订单状态
*
* @param orderNo 订单编号
* @param successTime 退款成功时间
* @param weChatRefundStatus 订单状态
* @return 更新数量
*/
int updateBatchRefundStatus(@Param("orderNo") String orderNo, @Param("successTime") LocalDateTime successTime,
@Param("weChatRefundStatus") String weChatRefundStatus);
/**
* 根据订单编号查询支付退款表信息
*
* @param orderNo 订单编号
* @return 数量
*/
int getRefundInfoByOrderNo(String orderNo);
}

View File

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

View File

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

View File

@ -35,6 +35,15 @@
<artifactId>xinelu-common</artifactId>
</dependency>
<dependency>
<groupId>com.xinelu</groupId>
<artifactId>xinelu-nurse-manage</artifactId>
</dependency>
<dependency>
<groupId>com.xinelu</groupId>
<artifactId>xinelu-nurse-applet</artifactId>
</dependency>
</dependencies>
</project>

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<PatientCouponReceive> patientCouponReceiveList = patientCouponReceiveMapper.getPatientCouponExpirationEndTime(timePrevious, CouponUseStatusEnum.EXPIRED.getInfo());
if (CollectionUtils.isEmpty(patientCouponReceiveList)) {
return;
}
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<Long> 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;
}
}
}

View File

@ -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<String, Objects> 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<AppointOrderAndDetailsInfo> appointmentOrderList = appointmentOrderMapper.getWaitPayAppointmentOrderList(localDateTime);
List<GoodsOrderAndDetailsInfo> goodsOrderList = goodsOrderMapper.getWaitPayGoodsOrderList(localDateTime);
if (CollectionUtils.isEmpty(appointmentOrderList) && CollectionUtils.isEmpty(goodsOrderList)) {
return;
}
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
List<AppointOrderAndDetailsInfo> 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<AppointmentOrder> appointmentOrderList = appointmentOrderMapper.getWaitPayStatusAppointmentOrderList(OrderStatusEnum.WAIT_PAY.getInfo());
List<GoodsOrder> 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<AppointOrderAndDetailsInfo> 处理成功的预约订单信息
* @throws Exception 异常信息
*/
private List<AppointOrderAndDetailsInfo> processAppointmentOrderInfo(List<AppointOrderAndDetailsInfo> appointmentOrderList) throws Exception {
List<AppointOrderAndDetailsInfo> successList = Lists.newArrayList();
List<String> 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<AppointOrderAndDetailsInfo> 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<GoodsOrderAndDetailsInfo> goodsOrderList) throws Exception {
List<String> orderList = Lists.newArrayList();
List<String> 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<GoodsOrderAndDetailsInfo> 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<String> 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<AppointmentOrder> 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<GoodsOrder> 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<AppointOrderAndDetailsInfo> successList) {
//关闭系统内部预约订单
List<String> 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);
}
}
}
}

View File

@ -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<String, Objects> redisTemplate;
@Resource
private PatientCouponReceiveMapper patientCouponReceiveMapper;
/**
* 自动处理退款状态定时任务每15分钟执行一次
* 防止由于网络等其它原因未接受到退款回调通知进而导致的退款单状态修改不及时
*
* @throws Exception 异常信息
*/
@Override
public void automaticProcessRefundInfo() throws Exception {
List<RefundOrderInfoVO> appointmentOrderList = appointmentOrderMapper.getRefundAppointmentOrderInfo(OrderStatusEnum.WAIT_REFUND.getInfo(), RefundStatusEnum.PROCESSING.getInfo(), ConfirmRefundStatusEnum.CONFIRMED.getInfo());
List<RefundOrderInfoVO> 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<RefundOrderInfoVO> 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<RefundOrderInfoVO> processAppointmentRefundInfo(List<RefundOrderInfoVO> appointmentOrderList) {
List<RefundOrderInfoVO> 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<String> 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<RefundOrderInfoVO> refundGoodsOrderLit) throws Exception {
List<String> 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());
}
}
}

View File

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

View File

@ -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("完成修改订单状态定时任务......");
}
}

View File

@ -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("完成关闭退款状态定时任务........");
}
}