Compare commits

...

2 Commits

Author SHA1 Message Date
636742e53d 8.4 小型商城退单(未对接) 2025-08-04 22:18:47 +08:00
d1d2a21fc2 7.21 sql小bug 2025-07-21 09:52:16 +08:00
19 changed files with 379 additions and 18 deletions

View File

@ -13,15 +13,15 @@
Date: 16/07/2025 17:08:44
*/
-- 如果存在就删除
-- 如果存在,则删除旧的数据库
DROP DATABASE IF EXISTS `pay-mall`;
-- 创建(如需指定字符集与排序规则,可自行调整
CREATE DATABASE IF NOT EXISTS `group_buying_sys`
DEFAULT CHARACTER SET utf8mb4
-- 创建新的数据库(可根据需要指定字符集和排序规则
CREATE DATABASE `pay-mall`
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 切换到
-- 切换到新创建的数据
USE `pay-mall`;
SET NAMES utf8mb4;

View File

@ -1,7 +1,6 @@
package edu.whut.api;
import edu.whut.api.dto.CreatePayRequestDTO;
import edu.whut.api.dto.NotifyRequestDTO;
import edu.whut.api.dto.*;
import edu.whut.api.response.Response;
public interface IPayService {
@ -15,4 +14,20 @@ public interface IPayService {
* @return 返参success 成功
*/
String groupBuyNotify(NotifyRequestDTO requestDTO);
/**
* 查询用户订单列表
*
* @param requestDTO 请求对象
* @return 订单列表
*/
Response<QueryOrderListResponseDTO> queryUserOrderList(QueryOrderListRequestDTO requestDTO);
/**
* 用户退单
*
* @param requestDTO 请求对象
* @return 退单结果
*/
Response<RefundOrderResponseDTO> refundOrder(RefundOrderRequestDTO requestDTO);
}

View File

@ -0,0 +1,15 @@
package edu.whut.api.dto;
import lombok.Data;
@Data
public class QueryOrderListRequestDTO {
/** 用户ID */
private String userId;
/** 分页参数大于此ID的记录 */
private Long lastId;
/** 每页数量 */
private Integer pageSize = 10;
}

View File

@ -0,0 +1,49 @@
package edu.whut.api.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class QueryOrderListResponseDTO {
/** 订单列表 */
private List<OrderInfo> orderList;
/** 是否还有更多数据 */
private Boolean hasMore;
/** 最后一条记录的ID */
private Long lastId;
@Data
public static class OrderInfo {
/** 订单ID */
private Long id;
/** 用户ID */
private String userId;
/** 商品ID */
private String productId;
/** 商品名称 */
private String productName;
/** 订单号 */
private String orderId;
/** 下单时间 */
private Date orderTime;
/** 订单金额 */
private BigDecimal totalAmount;
/** 订单状态 */
private String status;
/** 支付链接 */
private String payUrl;
/** 营销类型 */
private Integer marketType;
/** 营销优惠金额 */
private BigDecimal marketDeductionAmount;
/** 实际支付金额 */
private BigDecimal payAmount;
/** 支付时间 */
private Date payTime;
}
}

View File

@ -0,0 +1,17 @@
package edu.whut.api.dto;
import lombok.Data;
/**
* 退单请求DTO
*/
@Data
public class RefundOrderRequestDTO {
/** 用户ID */
private String userId;
/** 订单号 */
private String orderId;
}

View File

@ -0,0 +1,20 @@
package edu.whut.api.dto;
import lombok.Data;
/**
* 退单响应DTO
*/
@Data
public class RefundOrderResponseDTO {
/** 退单是否成功 */
private Boolean success;
/** 退单消息 */
private String message;
/** 订单号 */
private String orderId;
}

View File

@ -54,7 +54,7 @@
<select id="queryTimeoutCloseOrderList" parameterType="java.lang.String" resultType="java.lang.String">
SELECT order_id as orderId FROM pay_order
WHERE status = 'PAY_WAIT' AND NOW() >= order_time + INTERVAL 30 MINUTE
WHERE status = 'PAY_WAIT' AND NOW() >= order_time + INTERVAL 15 MINUTE
ORDER BY id ASC
LIMIT 50
</select>
@ -85,4 +85,27 @@
where order_id = #{orderId}
</update>
<select id="queryUserOrderList" resultMap="dataMap">
select id, user_id, product_id, product_name, order_id, order_time, total_amount, status, pay_url, pay_time, market_type, market_deduction_amount, pay_amount
from pay_order
where user_id = #{userId}
<if test="lastId != null">
and id &gt; #{lastId}
</if>
order by id asc
limit #{pageSize}
</select>
<select id="queryOrderByUserIdAndOrderId" resultMap="dataMap">
select id, user_id, product_id, product_name, order_id, order_time, total_amount, status, pay_url, pay_time, market_type, market_deduction_amount, pay_amount
from pay_order
where user_id = #{userId} and order_id = #{orderId}
</select>
<update id="refundOrder">
update pay_order
set status = 'CLOSE', update_time = now()
where user_id = #{userId} and order_id = #{orderId}
</update>
</mapper>

View File

@ -28,4 +28,10 @@ public interface IOrderRepository {
void changeOrderMarketSettlement(List<String> outTradeNoList);
OrderEntity queryOrderByOrderId(String orderId);
List<OrderEntity> queryUserOrderList(String userId, Long lastId, Integer pageSize);
OrderEntity queryOrderByUserIdAndOrderId(String userId, String orderId);
boolean refundOrder(String userId, String orderId);
}

View File

@ -26,7 +26,7 @@ public class CreateOrderAggregate {
return OrderEntity.builder()
.productId(productId)
.productName(productName)
.orderId(RandomStringUtils.randomNumeric(12))
.orderId(RandomStringUtils.randomNumeric(12)) //生成12位随机订单号
.orderTime(new Date())
.orderStatusVO(OrderStatusVO.CREATE)
.marketType(marketType)

View File

@ -14,6 +14,8 @@ import java.util.Date;
@NoArgsConstructor
public class OrderEntity {
// 主键ID
private Long id;
// 用户ID
private String userId;
private String productId;
@ -29,5 +31,6 @@ public class OrderEntity {
private BigDecimal marketDeductionAmount;
// 支付金额
private BigDecimal payAmount;
// 支付时间
private Date payTime;
}

View File

@ -10,6 +10,7 @@ import edu.whut.domain.order.model.valobj.OrderStatusVO;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.List;
@Slf4j
public abstract class AbstractOrderService implements IOrderService {
@ -155,4 +156,8 @@ public abstract class AbstractOrderService implements IOrderService {
protected abstract PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount, MarketPayDiscountEntity marketPayDiscountEntity) throws AlipayApiException;
@Override
public List<OrderEntity> queryUserOrderList(String userId, Long lastId, Integer pageSize) {
return repository.queryUserOrderList(userId, lastId, pageSize);
}
}

View File

@ -1,6 +1,7 @@
package edu.whut.domain.order.service;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
@ -21,4 +22,8 @@ public interface IOrderService {
void changeOrderMarketSettlement(List<String> outTradeNoList);
List<OrderEntity> queryUserOrderList(String userId, Long lastId, Integer pageSize);
boolean refundOrder(String userId, String orderId);
}

View File

@ -141,4 +141,33 @@ public class OrderService extends AbstractOrderService{
public void changeOrderMarketSettlement(List<String> outTradeNoList) {
repository.changeOrderMarketSettlement(outTradeNoList);
}
@Override
public boolean refundOrder(String userId, String orderId) {
// 1. 查询订单信息验证订单是否存在且属于该用户
OrderEntity orderEntity = repository.queryOrderByUserIdAndOrderId(userId, orderId);
if (null == orderEntity) {
log.warn("退单失败,订单不存在或不属于该用户 userId:{} orderId:{}", userId, orderId);
return false;
}
// 2. 检查订单状态只有createpay_waitpay_successdeal_done状态的订单可以退单
String status = orderEntity.getOrderStatusVO().getCode();
if (OrderStatusVO.CLOSE.getCode().equals(status)) {
log.warn("退单失败,订单已关闭 userId:{} orderId:{} status:{}", userId, orderId, status);
return false;
}
// 3. 对于营销类型的单子调用拼团执行组队退单 todo
// 4. 执行退单操作
boolean result = repository.refundOrder(userId, orderId);
if (result) {
log.info("退单成功 userId:{} orderId:{}", userId, orderId);
} else {
log.warn("退单失败 userId:{} orderId:{}", userId, orderId);
}
return result;
}
}

View File

@ -68,6 +68,7 @@ public class ProductPort implements IProductPort {
requestDTO.setActivityId(activityId);
requestDTO.setSource(source);
requestDTO.setChannel(chanel);
//拼团的外部交易单号等于支付系统的订单号
requestDTO.setOutTradeNo(orderId);
// 根据配置决定用 HTTP 回调还是 MQ
@ -105,6 +106,9 @@ public class ProductPort implements IProductPort {
}
}
/**
* 发起拼团系统的营销结算
*/
@Override
public void settlementMarketPayOrder(String userId, String orderId, Date orderTime) {
SettlementMarketPayOrderRequestDTO requestDTO = new SettlementMarketPayOrderRequestDTO();

View File

@ -19,8 +19,10 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Repository
@RequiredArgsConstructor
@ -77,6 +79,7 @@ public class OrderRepository implements IOrderRepository {
// 映射到领域实体返回
return OrderEntity.builder()
.id(order.getId())
.productId(order.getProductId())
.productName(order.getProductName())
.orderId(order.getOrderId())
@ -153,7 +156,7 @@ public class OrderRepository implements IOrderRepository {
}
/**
* 查询超过30分钟仍未支付的订单用于批量关闭
* 查询超过15分钟仍未支付的订单用于批量关闭
*/
@Override
public List<String> queryTimeoutCloseOrderList() {
@ -208,6 +211,7 @@ public class OrderRepository implements IOrderRepository {
if (null == orderId) return null;
return OrderEntity.builder()
.id(payOrder.getId())
.userId(payOrder.getUserId())
.productId(payOrder.getProductId())
.productName(payOrder.getProductName())
@ -220,4 +224,55 @@ public class OrderRepository implements IOrderRepository {
.payAmount(payOrder.getPayAmount())
.build();
}
@Override
public List<OrderEntity> queryUserOrderList(String userId, Long lastId, Integer pageSize) {
List<PayOrder> payOrderList = orderDao.queryUserOrderList(userId, lastId, pageSize);
if (null == payOrderList || payOrderList.isEmpty()) {
return new ArrayList<>();
}
return payOrderList.stream().map(payOrder -> OrderEntity.builder()
.id(payOrder.getId())
.userId(payOrder.getUserId())
.productId(payOrder.getProductId())
.productName(payOrder.getProductName())
.orderId(payOrder.getOrderId())
.orderTime(payOrder.getOrderTime())
.totalAmount(payOrder.getTotalAmount())
.orderStatusVO(OrderStatusVO.valueOf(payOrder.getStatus()))
.payUrl(payOrder.getPayUrl())
.payTime(payOrder.getPayTime())
.marketType(payOrder.getMarketType())
.marketDeductionAmount(payOrder.getMarketDeductionAmount())
.payAmount(payOrder.getPayAmount())
.build()).collect(Collectors.toList());
}
@Override
public OrderEntity queryOrderByUserIdAndOrderId(String userId, String orderId) {
PayOrder payOrder = orderDao.queryOrderByUserIdAndOrderId(userId, orderId);
if (null == payOrder) return null;
return OrderEntity.builder()
.id(payOrder.getId())
.userId(payOrder.getUserId())
.productId(payOrder.getProductId())
.productName(payOrder.getProductName())
.orderId(payOrder.getOrderId())
.orderTime(payOrder.getOrderTime())
.totalAmount(payOrder.getTotalAmount())
.orderStatusVO(OrderStatusVO.valueOf(payOrder.getStatus()))
.payUrl(payOrder.getPayUrl())
.payTime(payOrder.getPayTime())
.marketType(payOrder.getMarketType())
.marketDeductionAmount(payOrder.getMarketDeductionAmount())
.payAmount(payOrder.getPayAmount())
.build();
}
@Override
public boolean refundOrder(String userId, String orderId) {
return orderDao.refundOrder(userId, orderId);
}
}

View File

@ -28,4 +28,11 @@ public interface IOrderDao {
void changeOrderDealDone(String orderId);
List<PayOrder> queryUserOrderList(@Param("userId") String userId, @Param("lastId") Long lastId, @Param("pageSize") Integer pageSize);
PayOrder queryOrderByUserIdAndOrderId(@Param("userId") String userId, @Param("orderId") String orderId);
boolean refundOrder(@Param("userId") String userId, @Param("orderId") String orderId);
}

View File

@ -3,9 +3,9 @@ import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import edu.whut.api.IPayService;
import edu.whut.api.dto.CreatePayRequestDTO;
import edu.whut.api.dto.NotifyRequestDTO;
import edu.whut.api.dto.*;
import edu.whut.api.response.Response;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import edu.whut.domain.order.model.valobj.MarketTypeVO;
@ -19,7 +19,9 @@ import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@RestController()
@ -74,6 +76,7 @@ public class AliPayController implements IPayService {
/**
* 拼团完成目标人数已达成后的回调地址由group_buy_market调用
* 仅HTTP方式下触发MQ方式下不会触发
*/
@PostMapping("/group_buy_notify")
@Override
@ -134,4 +137,109 @@ public class AliPayController implements IPayService {
return "success";
}
@RequestMapping(value = "query_user_order_list", method = RequestMethod.POST)
@Override
public Response<QueryOrderListResponseDTO> queryUserOrderList(@RequestBody QueryOrderListRequestDTO requestDTO) {
try {
log.info("查询用户订单列表开始 userId:{} lastId:{} pageSize:{}", requestDTO.getUserId(), requestDTO.getLastId(), requestDTO.getPageSize());
String userId = requestDTO.getUserId();
Long lastId = requestDTO.getLastId();
Integer pageSize = requestDTO.getPageSize();
// 查询订单列表多查询一条用于判断是否还有更多数据
List<OrderEntity> orderList = orderService.queryUserOrderList(userId, lastId, pageSize + 1);
// 判断是否还有更多数据
boolean hasMore = orderList.size() > pageSize;
if (hasMore) {
orderList = orderList.subList(0, pageSize);
}
// 转换为响应对象
List<QueryOrderListResponseDTO.OrderInfo> orderInfoList = orderList.stream().map(order -> {
QueryOrderListResponseDTO.OrderInfo orderInfo = new QueryOrderListResponseDTO.OrderInfo();
orderInfo.setId(order.getId());
orderInfo.setUserId(order.getUserId());
orderInfo.setProductId(order.getProductId());
orderInfo.setProductName(order.getProductName());
orderInfo.setOrderId(order.getOrderId());
orderInfo.setOrderTime(order.getOrderTime());
orderInfo.setTotalAmount(order.getTotalAmount());
orderInfo.setStatus(order.getOrderStatusVO() != null ? order.getOrderStatusVO().getCode() : null);
orderInfo.setPayUrl(order.getPayUrl());
orderInfo.setMarketType(order.getMarketType());
orderInfo.setMarketDeductionAmount(order.getMarketDeductionAmount());
orderInfo.setPayAmount(order.getPayAmount());
orderInfo.setPayTime(order.getPayTime());
return orderInfo;
}).collect(Collectors.toList());
QueryOrderListResponseDTO responseDTO = new QueryOrderListResponseDTO();
responseDTO.setOrderList(orderInfoList);
responseDTO.setHasMore(hasMore);
responseDTO.setLastId(!orderList.isEmpty() ? orderList.get(orderList.size() - 1).getId() : null);
log.info("查询用户订单列表完成 userId:{} 返回订单数量:{} hasMore:{}", userId, orderInfoList.size(), hasMore);
return Response.<QueryOrderListResponseDTO>builder()
.code(Constants.ResponseCode.SUCCESS.getCode())
.info(Constants.ResponseCode.SUCCESS.getInfo())
.data(responseDTO)
.build();
} catch (Exception e) {
log.error("查询用户订单列表失败 userId:{}", requestDTO.getUserId(), e);
return Response.<QueryOrderListResponseDTO>builder()
.code(Constants.ResponseCode.UN_ERROR.getCode())
.info(Constants.ResponseCode.UN_ERROR.getInfo())
.build();
}
}
/**
* http://localhost:8080/api/v1/alipay/refund_order
* <p>
* {
* "userId": "xfg02",
* "orderId": "928263928388"
* }
*/
@RequestMapping(value = "refund_order", method = RequestMethod.POST)
@Override
public Response<RefundOrderResponseDTO> refundOrder(@RequestBody RefundOrderRequestDTO requestDTO) {
try {
log.info("用户退单开始 userId:{} orderId:{}", requestDTO.getUserId(), requestDTO.getOrderId());
String userId = requestDTO.getUserId();
String orderId = requestDTO.getOrderId();
// 执行退单操作
boolean success = orderService.refundOrder(userId, orderId);
RefundOrderResponseDTO responseDTO = new RefundOrderResponseDTO();
responseDTO.setSuccess(success);
responseDTO.setOrderId(orderId);
responseDTO.setMessage(success ? "退单成功" : "退单失败,订单不存在、已关闭或不属于该用户");
log.info("用户退单完成 userId:{} orderId:{} success:{}", userId, orderId, success);
return Response.<RefundOrderResponseDTO>builder()
.code(Constants.ResponseCode.SUCCESS.getCode())
.info(Constants.ResponseCode.SUCCESS.getInfo())
.data(responseDTO)
.build();
} catch (Exception e) {
log.error("用户退单失败 userId:{} orderId:{}", requestDTO.getUserId(), requestDTO.getOrderId(), e);
RefundOrderResponseDTO responseDTO = new RefundOrderResponseDTO();
responseDTO.setSuccess(false);
responseDTO.setOrderId(requestDTO.getOrderId());
responseDTO.setMessage("退单失败,系统异常");
return Response.<RefundOrderResponseDTO>builder()
.code(Constants.ResponseCode.UN_ERROR.getCode())
.info(Constants.ResponseCode.UN_ERROR.getInfo())
.data(responseDTO)
.build();
}
}
}

View File

@ -18,15 +18,15 @@ public class TimeoutCloseOrderJob {
private IOrderService orderService;
/**
* 15 分钟执行一次扫描超时未支付订单
* 3 分钟执行一次扫描超时未支付订单 尽快让用户释放锁单
*/
@Scheduled(cron = "0 0/15 * * * ?")
@Scheduled(cron = "0 0/3 * * * ?")
public void exec() {
try {
log.info("任务超时30分钟订单关闭");
log.info("任务每隔3分钟查询是否有订单超15分钟仍未支付。");
List<String> orderIds = orderService.queryTimeoutCloseOrderList();
if (null == orderIds || orderIds.isEmpty()) {
log.info("定时任务,超时30分钟订单关闭,暂无超时未支付订单 orderIds is null");
log.info("定时任务,超时15分钟订单关闭,暂无超时未支付订单 orderIds is null");
return;
}
// 遍历订单逐一关闭并记录结果