diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java index a87650098..c3d99aaa4 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.xkt; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.json.JSONUtil; import com.github.pagehelper.Page; import com.ruoyi.common.core.controller.XktBaseController; import com.ruoyi.common.core.domain.R; @@ -85,7 +86,6 @@ public class AssetController extends XktBaseController { //创建付款单 WithdrawPrepareResult prepareResult = assetService.prepareWithdraw(SecurityUtils.getStoreId(), vo.getAmount(), vo.getTransactionPassword(), EPayChannel.ALI_PAY); - //TODO 失败补偿 //支付宝转账 boolean success = aliPaymentManger.transfer(prepareResult.getBillNo(), prepareResult.getAccountOwnerNumber(), prepareResult.getAccountOwnerName(), prepareResult.getAmount()); @@ -93,7 +93,7 @@ public class AssetController extends XktBaseController { if (success) { assetService.withdrawSuccess(prepareResult.getFinanceBillId()); } else { - fsNotice.sendMsg2DefaultChat("档口提现异常", "付款单: " + prepareResult.getFinanceBillId()); + fsNotice.sendMsg2DefaultChat("档口提现失败", "参数: " + JSONUtil.toJsonStr(prepareResult)); } return success(); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java index 6195cfefb..8a84db4f4 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java @@ -1,15 +1,19 @@ package com.ruoyi.web.controller.xkt; import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson2.JSON; import com.github.pagehelper.Page; import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.controller.XktBaseController; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.SimpleEntity; import com.ruoyi.common.core.page.PageVO; +import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.notice.fs.FsNotice; import com.ruoyi.web.controller.xkt.vo.order.*; import com.ruoyi.xkt.domain.StoreOrderDetail; import com.ruoyi.xkt.dto.express.ExpressCancelReqDTO; @@ -35,6 +39,7 @@ import javax.validation.Valid; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -52,6 +57,10 @@ public class StoreOrderController extends XktBaseController { private IExpressService expressService; @Autowired private List paymentManagers; + @Autowired + private FsNotice fsNotice; + @Autowired + private RedisCache redisCache; @Log(title = "订单", businessType = BusinessType.INSERT) @ApiOperation("创建订单") @@ -227,16 +236,27 @@ public class StoreOrderController extends XktBaseController { //TODO 权限 StoreOrderRefundConfirmDTO dto = BeanUtil.toBean(vo, StoreOrderRefundConfirmDTO.class); dto.setOperatorId(SecurityUtils.getUserId()); + //支付宝接口要求:同一笔交易的退款至少间隔3s后发起 + String markKey = CacheConstants.STORE_ORDER_REFUND_PROCESSING_MARK + vo.getStoreOrderId(); + boolean less3s = redisCache.hasKey(markKey); + if (less3s) { + throw new ServiceException("同一订单确认退款操作至少间隔3s后发起"); + } //售后状态->售后完成,支付状态->支付中,创建收款单 StoreOrderRefund storeOrderRefund = storeOrderService.prepareRefundOrder(dto); - //TODO 失败补偿 //三方退款 PaymentManager paymentManager = getPaymentManager(EPayChannel.of(storeOrderRefund.getRefundOrder().getPayChannel())); - paymentManager.refundStoreOrder(storeOrderRefund); - //支付状态->已支付,收款单到账 - storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), - storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), - SecurityUtils.getUserId()); + boolean success = paymentManager.refundStoreOrder(storeOrderRefund); + //标记 + redisCache.setCacheObject(markKey, 1, 3, TimeUnit.SECONDS); + if (success) { + //支付状态->已支付,收款单到账 + storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), + storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), + SecurityUtils.getUserId()); + } else { + fsNotice.sendMsg2DefaultChat("确认退款失败", "参数: " + JSON.toJSONString(storeOrderRefund)); + } return success(); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java index 65d9864c2..48e92b8fd 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -47,6 +47,11 @@ public class CacheConstants */ public static final String USER_STS_KEY = "user_sts:"; + /** + * 退款处理中标识 + */ + public static final String STORE_ORDER_REFUND_PROCESSING_MARK = "store_order_refund_processing_mark_"; + /** * 档口 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java index 1f6835da0..d5fb5bda7 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -1,11 +1,5 @@ package com.ruoyi.common.core.redis; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; @@ -13,6 +7,9 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; +import java.util.*; +import java.util.concurrent.TimeUnit; + /** * spring redis 工具类 * @@ -270,4 +267,8 @@ public class RedisCache { return redisTemplate.keys(pattern); } + + public boolean exists(final String key) { + return redisTemplate.hasKey(key); + } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuAtField.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuAtField.java index 9bfbd8309..9d26fdbd2 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuAtField.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuAtField.java @@ -2,12 +2,16 @@ package com.ruoyi.framework.notice.fs.entity; import com.alibaba.fastjson2.annotation.JSONField; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; /** * @author liangyuqi * @date 2021/7/14 15:20 */ @Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class FeiShuAtField extends FeiShuMsg.BaseField { private String tag = "at"; @JSONField(name = "user_id") diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuTextField.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuTextField.java index b952013f0..ffdc2996a 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuTextField.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/notice/fs/entity/FeiShuTextField.java @@ -2,12 +2,16 @@ package com.ruoyi.framework.notice.fs.entity; import com.alibaba.fastjson2.annotation.JSONField; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; /** * @author liangyuqi * @date 2021/7/14 15:20 */ @Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class FeiShuTextField extends FeiShuMsg.BaseField { @JSONField(name = "un_escape") private boolean unEscape = true; diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/XktTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/XktTask.java index 77585fde4..d218eb897 100644 --- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/XktTask.java +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/XktTask.java @@ -1,27 +1,42 @@ package com.ruoyi.quartz.task; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; import co.elastic.clients.elasticsearch.core.BulkResponse; import co.elastic.clients.elasticsearch.core.bulk.BulkOperation; +import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.SimpleEntity; import com.ruoyi.common.core.domain.entity.SysProductCategory; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.framework.es.EsClientWrapper; +import com.ruoyi.framework.notice.fs.FsNotice; import com.ruoyi.system.mapper.SysProductCategoryMapper; import com.ruoyi.xkt.domain.*; +import com.ruoyi.xkt.dto.account.WithdrawPrepareResult; import com.ruoyi.xkt.dto.dailySale.DailySaleCusDTO; import com.ruoyi.xkt.dto.dailySale.DailySaleDTO; import com.ruoyi.xkt.dto.dailySale.DailySaleProdDTO; import com.ruoyi.xkt.dto.dailySale.WeekCateSaleDTO; import com.ruoyi.xkt.dto.dailyStoreTag.DailyStoreTagDTO; +import com.ruoyi.xkt.dto.order.StoreOrderCancelDTO; +import com.ruoyi.xkt.dto.order.StoreOrderRefund; import com.ruoyi.xkt.enums.*; +import com.ruoyi.xkt.manager.PaymentManager; import com.ruoyi.xkt.mapper.*; import com.ruoyi.xkt.service.IAdvertRoundService; +import com.ruoyi.xkt.service.IAlipayCallbackService; +import com.ruoyi.xkt.service.IAssetService; +import com.ruoyi.xkt.service.IStoreOrderService; +import io.jsonwebtoken.lang.Assert; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -42,6 +57,7 @@ import java.util.stream.Collectors; * * @author ruoyi */ +@Slf4j @Component("xktTask") @RequiredArgsConstructor public class XktTask { @@ -68,6 +84,11 @@ public class XktTask { final AdvertRoundMapper advertRoundMapper; final RedisCache redisCache; final IAdvertRoundService advertRoundService; + final IStoreOrderService storeOrderService; + final IAssetService assetService; + final IAlipayCallbackService alipayCallbackService; + final FsNotice fsNotice; + final List paymentManagers; /** * 每晚1点同步档口销售数据 @@ -461,6 +482,140 @@ public class XktTask { }); } + /** + * 自动关闭超时订单 + */ + public void autoCloseTimeoutStoreOrder() { + log.info("-------------自动关闭超时订单开始-------------"); + Integer batchCount = 20; + Date beforeDate = DateUtil.offset(new Date(), DateField.MINUTE, 60); + List storeOrderIds = storeOrderService.listNeedAutoCloseOrder(beforeDate, batchCount); + for (Long storeOrderId : storeOrderIds) { + log.info("开始处理: {}", storeOrderId); + try { + StoreOrderCancelDTO cancelDTO = new StoreOrderCancelDTO(); + cancelDTO.setStoreOrderId(storeOrderId); + cancelDTO.setRemark("超时订单自动关闭"); + storeOrderService.cancelOrder(cancelDTO); + } catch (Exception e) { + log.error("自动关闭超时订单异常", e); + fsNotice.sendMsg2DefaultChat("自动关闭超时订单异常", "订单ID:" + storeOrderId); + } + } + log.info("-------------自动关闭超时订单结束-------------"); + } + + /** + * 继续处理退款(异常中断补偿,非正常流程) + */ + public void continueProcessRefund() { + log.info("-------------继续处理退款开始-------------"); + Integer batchCount = 20; + List storeOrderRefunds = storeOrderService.listNeedContinueRefundOrder(batchCount); + for (StoreOrderRefund storeOrderRefund : storeOrderRefunds) { + log.info("开始处理: {}", storeOrderRefund); + try { + //支付宝接口要求:同一笔交易的退款至少间隔3s后发起 + String markKey = CacheConstants.STORE_ORDER_REFUND_PROCESSING_MARK + + storeOrderRefund.getRefundOrder().getId(); + boolean less3s = redisCache.hasKey(markKey); + if (less3s) { + log.warn("订单[{}]退款间隔小于3s跳过执行", storeOrderRefund.getRefundOrder().getId()); + continue; + } + PaymentManager paymentManager = getPaymentManager(EPayChannel.of(storeOrderRefund.getRefundOrder().getPayChannel())); + //查询退款结果 + ENetResult queryResult = paymentManager.queryStoreOrderRefundResult( + storeOrderRefund.getRefundOrder().getOrderNo(), + storeOrderRefund.getOriginOrder().getOrderNo()); + if (ENetResult.SUCCESS == queryResult) { + //退款成功 + //支付状态->已支付,收款单到账 + storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), + storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), + SecurityUtils.getUserId()); + } else { + //可能是退款失败,也可能是退款处理中,重复调用支付宝接口时只要参数正确也不会重复退款 + boolean success = paymentManager.refundStoreOrder(storeOrderRefund); + //标记 + redisCache.setCacheObject(markKey, 1, 3, TimeUnit.SECONDS); + if (success) { + //支付状态->已支付,收款单到账 + storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), + storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), + SecurityUtils.getUserId()); + } else { + fsNotice.sendMsg2DefaultChat("退款失败", "参数: " + JSON.toJSONString(storeOrderRefund)); + } + } + } catch (Exception e) { + log.error("继续处理退款异常", e); + fsNotice.sendMsg2DefaultChat("退款异常", "参数: " + JSON.toJSONString(storeOrderRefund)); + } + } + log.info("-------------继续处理退款结束-------------"); + } + + /** + * 继续处理档口提现(异常中断补偿,非正常流程) + */ + public void continueProcessWithdraw() { + log.info("-------------继续处理档口提现开始-------------"); + Integer batchCount = 20; + Map> map = assetService.getNeedContinueWithdrawGroupMap(batchCount); + for (Map.Entry> entry : map.entrySet()) { + PaymentManager paymentManager = getPaymentManager(entry.getKey()); + for (WithdrawPrepareResult prepareResult : entry.getValue()) { + log.info("开始处理: {}", prepareResult); + try { + //查询转账结果 + ENetResult queryResult = paymentManager.queryTransferResult(prepareResult.getBillNo()); + if (ENetResult.SUCCESS == queryResult) { + //转账成功,直接到账 + assetService.withdrawSuccess(prepareResult.getFinanceBillId()); + } else if (ENetResult.FAILURE == queryResult) { + //转账失败,尝试重新支付 + boolean success = paymentManager.transfer(prepareResult.getBillNo(), + prepareResult.getAccountOwnerNumber(), + prepareResult.getAccountOwnerName(), + prepareResult.getAmount()); + //付款单到账 + if (success) { + assetService.withdrawSuccess(prepareResult.getFinanceBillId()); + } else { + fsNotice.sendMsg2DefaultChat("档口提现失败", "参数: " + JSON.toJSONString(prepareResult)); + } + } else { + log.warn("档口提现支付宝处理中: {}", prepareResult); + } + } catch (Exception e) { + log.error("继续继续处理档口提现异常", e); + fsNotice.sendMsg2DefaultChat("档口提现异常", "参数: " + JSON.toJSONString(prepareResult)); + } + } + } + log.info("-------------继续处理档口提现结束-------------"); + } + + /** + * 继续处理支付宝支付回调信息(异常中断补偿,非正常流程) + */ + public void continueProcessAliCallback() { + log.info("-------------继续处理支付宝支付回调信息开始-------------"); + Integer batchCount = 20; + List callbacks = alipayCallbackService.listNeedContinueProcessCallback(batchCount); + for (AlipayCallback callback : callbacks) { + log.info("开始处理: {}", callback); + try { + alipayCallbackService.continueProcess(Collections.singletonList(callback)); + } catch (Exception e) { + log.error("继续处理支付宝支付回调异常", e); + fsNotice.sendMsg2DefaultChat("支付回调处理异常", "回调信息: " + JSON.toJSONString(callback)); + } + } + log.info("-------------继续处理支付宝支付回调信息结束-------------"); + } + @@ -775,6 +930,22 @@ public class XktTask { System.out.println("bulkResponse.items() = " + bulkResponse.items()); } + /** + * 根据支付渠道匹配支付类 + * + * @param payChannel + * @return + */ + private PaymentManager getPaymentManager(EPayChannel payChannel) { + Assert.notNull(payChannel); + for (PaymentManager paymentManager : paymentManagers) { + if (paymentManager.channel() == payChannel) { + return paymentManager; + } + } + throw new ServiceException("未知支付渠道"); + } + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java b/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java index a1086b374..ec6d8f83b 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java @@ -47,7 +47,16 @@ public interface PaymentManager { * * @param orderRefund */ - void refundStoreOrder(StoreOrderRefund orderRefund); + boolean refundStoreOrder(StoreOrderRefund orderRefund); + + /** + * 退款结果 + * + * @param refundOrderNo + * @param originOrderNo + * @return + */ + ENetResult queryStoreOrderRefundResult(String refundOrderNo, String originOrderNo); /** * 是否已支付 diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java index 0284b151c..594958b51 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java @@ -181,11 +181,11 @@ public class AliPaymentMangerImpl implements PaymentManager { throw new ServiceException("订单[" + orderExt.getOrder().getOrderNo() + "]支付状态异常"); } return pay(orderExt.getOrder().getOrderNo(), orderExt.getOrder().getTotalAmount(), - "代发订单" + orderExt.getOrder().getOrderNo(), payPage,null); + "代发订单" + orderExt.getOrder().getOrderNo(), payPage, null); } @Override - public void refundStoreOrder(StoreOrderRefund orderRefund) { + public boolean refundStoreOrder(StoreOrderRefund orderRefund) { Assert.notNull(orderRefund); Assert.notNull(orderRefund.getOriginOrder()); Assert.notNull(orderRefund.getRefundOrder()); @@ -199,7 +199,7 @@ public class AliPaymentMangerImpl implements PaymentManager { // 设置商户订单号 model.setOutTradeNo(orderRefund.getOriginOrder().getOrderNo()); // 设置支付宝交易号 - model.setTradeNo(orderRefund.getOriginOrder().getPayTradeNo()); +// model.setTradeNo(orderRefund.getOriginOrder().getPayTradeNo()); // 设置退款金额 BigDecimal amount = BigDecimal.ZERO; for (StoreOrderDetail orderDetail : orderRefund.getRefundOrderDetails()) { @@ -212,11 +212,22 @@ public class AliPaymentMangerImpl implements PaymentManager { // 设置退款请求号 model.setOutRequestNo(orderRefund.getRefundOrder().getOrderNo()); try { + //TODO 沙箱环境接口无法完全退款? +// AlipayTradeRefundResponse response = alipayClient.certificateExecute(request); AlipayTradeRefundResponse response = alipayClient.execute(request); log.info("支付宝退款:{}", response.getBody()); + String fundChange = JSON.parseObject(response.getBody()) + .getJSONObject("alipay_trade_refund_response") + .getString("fund_change"); if (response.isSuccess()) { - //TODO 沙箱环境接口不通 - return; + //退款成功判断说明:接口返回fund_change=Y为退款成功, + // fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态。 + return "Y".equals(fundChange); + } else { + //获取诊断链接 + String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response); + log.warn("支付宝退款异常: {}", diagnosisUrl); + return false; } } catch (Exception e) { log.error("退款异常", e); @@ -226,6 +237,43 @@ public class AliPaymentMangerImpl implements PaymentManager { throw new ServiceException("退款失败"); } + @Override + public ENetResult queryStoreOrderRefundResult(String refundOrderNo, String originOrderNo) { + Assert.notEmpty(refundOrderNo); + Assert.notEmpty(originOrderNo); + AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, appId, privateKey, DEFAULT_FORMAT, charset, + alipayPublicKey, signType); + // 构造请求参数以调用接口 + AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); + AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); + model.setOutRequestNo(refundOrderNo); + model.setOutTradeNo(originOrderNo); + request.setBizModel(model); + try { + AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request); + log.warn("查询支付宝订单退款结果: {}", response.getBody()); + if (response.isSuccess()) { + String refundStatus = JSON.parseObject(response.getBody()) + .getJSONObject("alipay_trade_fastpay_refund_query_response") + .getString("refund_status"); + if ("REFUND_SUCCESS".equals(refundStatus)) { + return ENetResult.SUCCESS; + } + return ENetResult.FAILURE; + } else { + //获取诊断链接 + String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response); + log.error("查询支付宝订单退款果异常: {}", diagnosisUrl); + return ENetResult.FAILURE; + } + } catch (Exception e) { + log.error("查询支付宝订单退款结果异常", e); + } + //告警 + fsNotice.sendMsg2DefaultChat("查询支付宝订单退款结果异常", JSON.toJSONString(model)); + throw new ServiceException("查询支付宝订单退款结果失败"); + } + @Override public ENetResult queryStoreOrderPayResult(String orderNo) { Assert.notEmpty(orderNo); diff --git a/xkt/src/main/java/com/ruoyi/xkt/mapper/StoreOrderMapper.java b/xkt/src/main/java/com/ruoyi/xkt/mapper/StoreOrderMapper.java index c12e3c059..7140141db 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/mapper/StoreOrderMapper.java +++ b/xkt/src/main/java/com/ruoyi/xkt/mapper/StoreOrderMapper.java @@ -16,4 +16,6 @@ import java.util.List; public interface StoreOrderMapper extends BaseMapper { List listStoreOrderPageItem(StoreOrderQueryDTO queryDTO); + + List listNeedContinueRefundOrder(); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IAlipayCallbackService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IAlipayCallbackService.java index fc36d4a3e..9fea68abc 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IAlipayCallbackService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IAlipayCallbackService.java @@ -2,6 +2,8 @@ package com.ruoyi.xkt.service; import com.ruoyi.xkt.domain.AlipayCallback; +import java.util.List; + /** * @author liangyq * @date 2025-04-08 17:39 @@ -46,9 +48,17 @@ public interface IAlipayCallbackService { void noNeedProcess(AlipayCallback info); /** - * 继续处理回调 + * 获取需要继续处理的回调 * * @param count + * @return */ - void continueProcess(int count); + List listNeedContinueProcessCallback(Integer count); + + /** + * 继续处理回调 + * + * @param infoList + */ + void continueProcess(List infoList); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java index 81fbcc573..98df5a8aa 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java @@ -7,6 +7,8 @@ import com.ruoyi.xkt.dto.finance.RechargeAddResult; import com.ruoyi.xkt.enums.EPayChannel; import java.math.BigDecimal; +import java.util.List; +import java.util.Map; /** * @author liangyq @@ -120,4 +122,12 @@ public interface IAssetService { * @param amount */ void refundAdvertFee(Long storeId, BigDecimal amount); + + /** + * 获取需要继续处理的提现信息 + * + * @param count + * @return + */ + Map> getNeedContinueWithdrawGroupMap(Integer count); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java index 629c1321e..16b16d09c 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java @@ -1,8 +1,12 @@ package com.ruoyi.xkt.service; import com.ruoyi.xkt.domain.FinanceBill; +import com.ruoyi.xkt.dto.finance.FinanceBillDTO; import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.order.StoreOrderExt; +import com.ruoyi.xkt.enums.EFinBillSrcType; +import com.ruoyi.xkt.enums.EFinBillStatus; +import com.ruoyi.xkt.enums.EFinBillType; import com.ruoyi.xkt.enums.EPayChannel; import java.math.BigDecimal; @@ -113,5 +117,17 @@ public interface IFinanceBillService { FinanceBillExt createInternalTransferBill(Long inputAccountId, Long outputAccountId, BigDecimal amount, Integer srcType, Long srcId, Integer relType, Long relId, String remark); + /** + * 单据列表 + * + * @param billStatus + * @param billType + * @param billSrcType + * @param count + * @return + */ + List listByStatus(EFinBillStatus billStatus, EFinBillType billType, EFinBillSrcType billSrcType, + Integer count); + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java index 0bbc80ab5..ca718e177 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java @@ -178,10 +178,19 @@ public interface IStoreOrderService { void addTrack(StoreOrderExpressTrackAddDTO trackAddDTO); /** - * 自动取消 + * 获取需要自动关闭的订单 * * @param beforeDate * @param count + * @return */ - void autoCancel(Date beforeDate, int count); + List listNeedAutoCloseOrder(Date beforeDate, Integer count); + + /** + * 获取需要继续退款的订单信息 + * + * @param count + * @return + */ + List listNeedContinueRefundOrder(Integer count); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AlipayCallbackServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AlipayCallbackServiceImpl.java index 9d7c6d1e8..364925eb9 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AlipayCallbackServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AlipayCallbackServiceImpl.java @@ -84,13 +84,20 @@ public class AlipayCallbackServiceImpl implements IAlipayCallbackService { alipayCallbackMapper.updateById(info); } - @Transactional(rollbackFor = Exception.class) @Override - public void continueProcess(int count) { - PageHelper.startPage(1, count, false); + public List listNeedContinueProcessCallback(Integer count) { + if (count != null) { + PageHelper.startPage(1, count, false); + } List infoList = alipayCallbackMapper.selectList(Wrappers.lambdaQuery(AlipayCallback.class) .eq(AlipayCallback::getProcessStatus, EProcessStatus.INIT.getValue()) .eq(SimpleEntity::getDelFlag, Constants.UNDELETED)); + return infoList; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void continueProcess(List infoList) { for (AlipayCallback info : infoList) { if (!"TRADE_SUCCESS".equals(info.getTradeStatus())) { //非交易支付成功的回调不处理 diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java index 1a3644da5..094b61be0 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java @@ -14,6 +14,7 @@ import com.ruoyi.xkt.domain.ExternalAccount; import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.domain.InternalAccount; import com.ruoyi.xkt.dto.account.*; +import com.ruoyi.xkt.dto.finance.FinanceBillDTO; import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.finance.RechargeAddDTO; import com.ruoyi.xkt.dto.finance.RechargeAddResult; @@ -29,7 +30,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author liangyq @@ -242,6 +246,28 @@ public class AssetServiceImpl implements IAssetService { } + @Override + public Map> getNeedContinueWithdrawGroupMap(Integer count) { + //TODO 付款单据没有支付渠道和账户快照 + List bills = financeBillService.listByStatus(EFinBillStatus.PROCESSING, + EFinBillType.PAYMENT, EFinBillSrcType.WITHDRAW, count); + List results = new ArrayList<>(bills.size()); + for (FinanceBillDTO bill : bills) { + Long storeId = bill.getSrcId(); + ExternalAccount externalAccount = externalAccountService.getAccountAndCheck(storeId, + EAccountOwnerType.STORE, EAccountType.ALI_PAY); + results.add(new WithdrawPrepareResult(bill.getId(), + bill.getBillNo(), + bill.getTransAmount(), + externalAccount.getAccountOwnerNumber(), + externalAccount.getAccountOwnerName(), + externalAccount.getAccountOwnerPhoneNumber())); + } + Map> rtn = new HashMap<>(1); + rtn.put(EPayChannel.ALI_PAY, results); + return rtn; + } + /** * 根据支付渠道匹配支付类 * diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java index 26f63a394..a82c9b00b 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java @@ -1,5 +1,6 @@ package com.ruoyi.xkt.service.impl; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; @@ -8,6 +9,7 @@ import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.NumberUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.github.pagehelper.PageHelper; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.SimpleEntity; import com.ruoyi.common.exception.ServiceException; @@ -16,6 +18,7 @@ import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.domain.FinanceBillDetail; import com.ruoyi.xkt.domain.InternalAccount; import com.ruoyi.xkt.domain.StoreOrderDetail; +import com.ruoyi.xkt.dto.finance.FinanceBillDTO; import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.finance.TransInfo; import com.ruoyi.xkt.dto.order.StoreOrderExt; @@ -562,6 +565,20 @@ public class FinanceBillServiceImpl implements IFinanceBillService { return new FinanceBillExt(bill, ListUtil.empty()); } + @Override + public List listByStatus(EFinBillStatus billStatus, EFinBillType billType, + EFinBillSrcType billSrcType, Integer count) { + if (count != null) { + PageHelper.startPage(1, count, false); + } + List doList = financeBillMapper.selectList(Wrappers.lambdaQuery(FinanceBill.class) + .eq(FinanceBill::getBillStatus, Optional.ofNullable(billStatus).map(EFinBillStatus::getValue).orElse(null)) + .eq(FinanceBill::getBillType, Optional.ofNullable(billType).map(EFinBillType::getValue).orElse(null)) + .eq(FinanceBill::getSrcType, Optional.ofNullable(billSrcType).map(EFinBillSrcType::getValue).orElse(null)) + .eq(FinanceBill::getDelFlag, Constants.UNDELETED)); + return BeanUtil.copyToList(doList, FinanceBillDTO.class); + } + /** * 生成单号 diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java index 9ba6868cc..cad51cf9a 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java @@ -2,6 +2,7 @@ package com.ruoyi.xkt.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.CharSequenceUtil; @@ -1259,46 +1260,45 @@ public class StoreOrderServiceImpl implements IStoreOrderService { expressService.addTrackRecord(expressTrackRecordAddDTO); } - @Transactional(rollbackFor = Exception.class) @Override - public void autoCancel(Date beforeDate, int count) { - PageHelper.startPage(1, count, false); + public List listNeedAutoCloseOrder(Date beforeDate, Integer count) { + if (count != null) { + PageHelper.startPage(1, count, false); + } List orders = storeOrderMapper.selectList(Wrappers.lambdaQuery(StoreOrder.class) .eq(StoreOrder::getOrderStatus, EOrderStatus.PENDING_PAYMENT.getValue()) .ne(StoreOrder::getPayStatus, EPayStatus.PAID.getValue()) .eq(SimpleEntity::getDelFlag, Constants.UNDELETED) .le(SimpleEntity::getCreateTime, beforeDate)); - for (StoreOrder order : orders) { - PaymentManager paymentManager = getPaymentManager(EPayChannel.of(order.getPayChannel())); - ENetResult result = paymentManager.queryStoreOrderPayResult(order.getOrderNo()); - if (ENetResult.SUCCESS == result) { - log.warn("订单[{}]已支付,无法取消", order.getId()); - continue; - } - //订单已取消 - order.setOrderStatus(EOrderStatus.CANCELLED.getValue()); - int orderSuccess = storeOrderMapper.updateById(order); - if (orderSuccess == 0) { - throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG); - } - List orderDetails = storeOrderDetailMapper.selectList( - Wrappers.lambdaQuery(StoreOrderDetail.class) - .eq(StoreOrderDetail::getStoreOrderId, order.getId()) - .eq(SimpleEntity::getDelFlag, Constants.UNDELETED)); - List orderDetailIdList = new ArrayList<>(orderDetails.size()); - for (StoreOrderDetail orderDetail : orderDetails) { - //明细已取消 - orderDetail.setDetailStatus(EOrderStatus.CANCELLED.getValue()); - int orderDetailSuccess = storeOrderDetailMapper.updateById(orderDetail); - if (orderDetailSuccess == 0) { - throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG); - } - orderDetailIdList.add(orderDetail.getId()); - } - //操作记录 - addOperationRecords(order.getId(), EOrderAction.CANCEL, orderDetailIdList, EOrderAction.CANCEL, - "超时取消", null, new Date()); + return orders.stream().map(SimpleEntity::getId).collect(Collectors.toList()); + } + + @Override + public List listNeedContinueRefundOrder(Integer count) { + if (count != null) { + PageHelper.startPage(1, count, false); } + List orders = storeOrderMapper.listNeedContinueRefundOrder(); + List storeOrderRefunds = new ArrayList<>(orders.size()); + if (CollUtil.isEmpty(orders)) { + return ListUtil.empty(); + } + List orderDetails = storeOrderDetailMapper.selectList(Wrappers + .lambdaQuery(StoreOrderDetail.class) + .in(StoreOrderDetail::getStoreOrderId, orders.stream().map(SimpleEntity::getId) + .collect(Collectors.toSet())) + .eq(SimpleEntity::getDelFlag, Constants.UNDELETED)); + Map> orderDetailMap = orderDetails.stream() + .filter(o -> EOrderStatus.AFTER_SALE_COMPLETED.getValue().equals(o.getDetailStatus()) + && EPayStatus.PAYING.getValue().equals(o.getPayStatus())) + .collect(Collectors.groupingBy(StoreOrderDetail::getStoreOrderId)); + for (StoreOrder order : orders) { + List orderDetailList = orderDetailMap.get(order.getId()); + Assert.notEmpty(orderDetailList); + storeOrderRefunds.add(new StoreOrderRefund(order, orderDetailList, + getAndBaseCheck(order.getOriginOrderId()))); + } + return storeOrderRefunds; } /** diff --git a/xkt/src/main/resources/mapper/StoreOrderMapper.xml b/xkt/src/main/resources/mapper/StoreOrderMapper.xml index 69e344bad..296098e22 100644 --- a/xkt/src/main/resources/mapper/StoreOrderMapper.xml +++ b/xkt/src/main/resources/mapper/StoreOrderMapper.xml @@ -50,4 +50,21 @@ + \ No newline at end of file