Files
order/service/order.go

479 lines
14 KiB
Go
Raw Normal View History

2025-12-10 09:02:41 +08:00
package service
import (
"context"
"errors"
"fmt"
"math/rand"
"time"
2025-12-10 13:51:09 +08:00
"go.mongodb.org/mongo-driver/v2/bson"
"order/consts"
2025-12-10 09:02:41 +08:00
"order/dao"
"order/model/dto"
"order/model/entity"
)
2025-12-10 13:51:09 +08:00
type order struct{}
2025-12-10 09:02:41 +08:00
2025-12-10 13:51:09 +08:00
// Order 订单服务
var Order = new(order)
// convertOrderItemsFromDTO 从DTO转换订单商品项
func convertOrderItemsFromDTO(items []dto.OrderItemReq) []entity.OrderItem {
var result []entity.OrderItem
for _, item := range items {
result = append(result, entity.OrderItem{
ProductID: item.ProductID,
ProductName: item.ProductName,
Price: item.Price,
Quantity: item.Quantity,
TotalPrice: item.Price * int64(item.Quantity),
ImageURL: item.ImageURL,
})
}
return result
}
// convertOrderItems 转换订单商品项
func convertOrderItems(items []entity.OrderItem) []dto.OrderItem {
var result []dto.OrderItem
for _, item := range items {
result = append(result, dto.OrderItem{
ProductID: item.ProductID,
ProductName: item.ProductName,
Price: item.Price,
Quantity: item.Quantity,
TotalPrice: item.TotalPrice,
ImageURL: item.ImageURL,
})
}
return result
2025-12-10 09:02:41 +08:00
}
2025-12-10 13:51:09 +08:00
// convertEntityOrderItemsToDTO 转换entity订单商品项到DTO
func convertEntityOrderItemsToDTO(items []entity.OrderItem) []dto.OrderItem {
var result []dto.OrderItem
for _, item := range items {
result = append(result, dto.OrderItem{
ProductID: item.ProductID,
ProductName: item.ProductName,
Price: item.Price,
Quantity: item.Quantity,
TotalPrice: item.TotalPrice,
ImageURL: item.ImageURL,
})
2025-12-10 09:02:41 +08:00
}
2025-12-10 13:51:09 +08:00
return result
2025-12-10 09:02:41 +08:00
}
// CreateOrder 创建订单
2025-12-10 13:51:09 +08:00
func (s *order) CreateOrder(ctx context.Context, req *dto.CreateOrderReq) (*dto.CreateOrderResp, error) {
2025-12-10 09:02:41 +08:00
// 1. 参数验证
if req.TenantID == "" || req.UserID == "" || req.Subject == "" {
return nil, errors.New("必填参数不能为空")
}
if len(req.OrderItems) == 0 {
return nil, errors.New("订单商品不能为空")
}
// 2. 计算订单金额
totalAmount := int64(0)
for i := range req.OrderItems {
item := &req.OrderItems[i]
if item.Price <= 0 || item.Quantity <= 0 {
return nil, fmt.Errorf("商品价格或数量无效: %s", item.ProductName)
}
2025-12-10 13:51:09 +08:00
totalPrice := item.Price * int64(item.Quantity)
totalAmount += totalPrice
2025-12-10 09:02:41 +08:00
}
if totalAmount <= 0 {
return nil, errors.New("订单总金额必须大于0")
}
// 3. 生成订单号
orderNo := s.generateOrderNo(req.TenantID)
// 4. 设置订单过期时间30分钟后
expiredAt := time.Now().Add(30 * time.Minute)
// 5. 创建待支付订单
order := &entity.OrderPending{
OrderBase: entity.OrderBase{
TenantID: req.TenantID,
OrderNo: orderNo,
UserID: req.UserID,
TotalAmount: totalAmount,
PayAmount: totalAmount, // 暂时没有优惠,实付金额等于总金额
OrderType: req.OrderType,
Subject: req.Subject,
Description: req.Description,
ExpiredAt: &expiredAt,
},
2025-12-10 13:51:09 +08:00
OrderItems: convertOrderItemsFromDTO(req.OrderItems),
2025-12-10 09:02:41 +08:00
ShippingInfo: (*entity.ShippingInfo)(&req.ShippingInfo),
PayInfo: entity.PayInfo{},
}
// 6. 保存订单
2025-12-10 13:51:09 +08:00
if err := dao.Order.CreatePendingOrder(ctx, order); err != nil {
2025-12-10 09:02:41 +08:00
return nil, fmt.Errorf("创建订单失败: %w", err)
}
// 7. 返回结果
resp := &dto.CreateOrderResp{
OrderNo: orderNo,
TotalAmount: totalAmount,
PayAmount: totalAmount,
ExpiredAt: expiredAt.Format(time.RFC3339),
}
return resp, nil
}
// generateOrderNo 生成订单号
2025-12-10 13:51:09 +08:00
func (s *order) generateOrderNo(tenantID string) string {
2025-12-10 09:02:41 +08:00
timestamp := time.Now().Format("20060102150405")
random := rand.Intn(10000)
return fmt.Sprintf("%s%s%04d", tenantID, timestamp, random)
}
// QueryOrder 查询订单详情
2025-12-10 13:51:09 +08:00
func (s *order) QueryOrder(ctx context.Context, req *dto.QueryOrderReq) (*dto.QueryOrderResp, error) {
2025-12-10 09:02:41 +08:00
// 1. 参数验证
if req.TenantID == "" || req.OrderNo == "" {
return nil, errors.New("必填参数不能为空")
}
// 2. 查询订单
2025-12-10 13:51:09 +08:00
order, status, err := dao.Order.GetOrderByNo(ctx, req.TenantID, req.OrderNo)
2025-12-10 09:02:41 +08:00
if err != nil {
return nil, fmt.Errorf("获取订单失败: %w", err)
}
if order == nil {
return nil, errors.New("订单不存在")
}
// 3. 构建响应
var resp dto.QueryOrderResp
switch status {
2025-12-10 13:51:09 +08:00
case consts.OrderStatusPending:
2025-12-10 09:02:41 +08:00
if pendingOrder, ok := order.(*entity.OrderPending); ok {
resp.Order = s.convertPendingOrderToDetail(pendingOrder)
}
2025-12-10 13:51:09 +08:00
case consts.OrderStatusPaid:
2025-12-10 09:02:41 +08:00
if paidOrder, ok := order.(*entity.OrderPaid); ok {
resp.Order = s.convertPaidOrderToDetail(paidOrder)
}
2025-12-10 13:51:09 +08:00
case consts.OrderStatusShipped:
2025-12-10 09:02:41 +08:00
if shippedOrder, ok := order.(*entity.OrderShipped); ok {
resp.Order = s.convertShippedOrderToDetail(shippedOrder)
}
2025-12-10 13:51:09 +08:00
case consts.OrderStatusCompleted:
2025-12-10 09:02:41 +08:00
if completedOrder, ok := order.(*entity.OrderCompleted); ok {
resp.Order = s.convertCompletedOrderToDetail(completedOrder)
}
default:
return nil, fmt.Errorf("不支持的订单状态: %s", status)
}
return &resp, nil
}
// convertPendingOrderToDetail 转换待支付订单为详情
2025-12-10 13:51:09 +08:00
func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.OrderDetail {
2025-12-10 09:02:41 +08:00
return dto.OrderDetail{
ID: order.ID.Hex(),
TenantID: order.TenantID,
OrderNo: order.OrderNo,
UserID: order.UserID,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusPending),
2025-12-10 09:02:41 +08:00
PayMethod: order.PayMethod,
PayStatus: "unpaid",
OrderType: order.OrderType,
Subject: order.Subject,
Description: order.Description,
2025-12-10 13:51:09 +08:00
OrderItems: convertOrderItems(order.OrderItems),
2025-12-10 09:02:41 +08:00
ShippingInfo: dto.ShippingInfo{
Consignee: order.ShippingInfo.Consignee,
Phone: order.ShippingInfo.Phone,
Province: order.ShippingInfo.Province,
City: order.ShippingInfo.City,
District: order.ShippingInfo.District,
Address: order.ShippingInfo.Address,
PostalCode: order.ShippingInfo.PostalCode,
},
PayInfo: dto.PayInfo{
OutTradeNo: order.PayInfo.OutTradeNo,
PrepayID: order.PayInfo.PrepayID,
QRCode: order.PayInfo.QRCode,
PayURL: order.PayInfo.PayURL,
},
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
ExpiredAt: order.ExpiredAt,
}
}
// convertPaidOrderToDetail 转换已支付订单为详情
2025-12-10 13:51:09 +08:00
func (s *order) convertPaidOrderToDetail(order *entity.OrderPaid) dto.OrderDetail {
2025-12-10 09:02:41 +08:00
return dto.OrderDetail{
ID: order.ID.Hex(),
TenantID: order.TenantID,
OrderNo: order.OrderNo,
UserID: order.UserID,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusPaid),
2025-12-10 09:02:41 +08:00
PayMethod: order.PayMethod,
PayStatus: "paid",
OrderType: order.OrderType,
Subject: order.Subject,
Description: order.Description,
2025-12-10 13:51:09 +08:00
OrderItems: convertOrderItems(order.OrderItems),
2025-12-10 09:02:41 +08:00
ShippingInfo: dto.ShippingInfo{
Consignee: order.ShippingInfo.Consignee,
Phone: order.ShippingInfo.Phone,
Province: order.ShippingInfo.Province,
City: order.ShippingInfo.City,
District: order.ShippingInfo.District,
Address: order.ShippingInfo.Address,
PostalCode: order.ShippingInfo.PostalCode,
},
PayInfo: dto.PayInfo{
TransactionID: order.TransactionID,
OutTradeNo: order.OrderNo,
},
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
PaidAt: &order.PaidAt,
}
}
// convertShippedOrderToDetail 转换已发货订单为详情
2025-12-10 13:51:09 +08:00
func (s *order) convertShippedOrderToDetail(order *entity.OrderShipped) dto.OrderDetail {
2025-12-10 09:02:41 +08:00
return dto.OrderDetail{
ID: order.ID.Hex(),
TenantID: order.TenantID,
OrderNo: order.OrderNo,
UserID: order.UserID,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusShipped),
2025-12-10 09:02:41 +08:00
PayMethod: order.PayMethod,
PayStatus: "paid",
OrderType: order.OrderType,
Subject: order.Subject,
Description: order.Description,
2025-12-10 13:51:09 +08:00
OrderItems: convertOrderItems(order.OrderItems),
2025-12-10 09:02:41 +08:00
ShippingInfo: dto.ShippingInfo{
Consignee: order.ShippingInfo.Consignee,
Phone: order.ShippingInfo.Phone,
Province: order.ShippingInfo.Province,
City: order.ShippingInfo.City,
District: order.ShippingInfo.District,
Address: order.ShippingInfo.Address,
PostalCode: order.ShippingInfo.PostalCode,
},
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
PaidAt: &order.PaidAt,
}
}
// convertCompletedOrderToDetail 转换已完成订单为详情
2025-12-10 13:51:09 +08:00
func (s *order) convertCompletedOrderToDetail(order *entity.OrderCompleted) dto.OrderDetail {
2025-12-10 09:02:41 +08:00
return dto.OrderDetail{
ID: order.ID.Hex(),
TenantID: order.TenantID,
OrderNo: order.OrderNo,
UserID: order.UserID,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusCompleted),
2025-12-10 09:02:41 +08:00
PayMethod: order.PayMethod,
PayStatus: "paid",
OrderType: order.OrderType,
Subject: order.Subject,
Description: order.Description,
2025-12-10 13:51:09 +08:00
OrderItems: convertOrderItems(order.OrderItems),
2025-12-10 09:02:41 +08:00
ShippingInfo: dto.ShippingInfo{
Consignee: order.ShippingInfo.Consignee,
Phone: order.ShippingInfo.Phone,
Province: order.ShippingInfo.Province,
City: order.ShippingInfo.City,
District: order.ShippingInfo.District,
Address: order.ShippingInfo.Address,
PostalCode: order.ShippingInfo.PostalCode,
},
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
PaidAt: &order.PaidAt,
}
}
// convertOrderItems 转换订单商品项
2025-12-10 13:51:09 +08:00
func (s *order) convertOrderItems(items []*entity.OrderItem) []dto.OrderItem {
2025-12-10 09:02:41 +08:00
var result []dto.OrderItem
for _, item := range items {
result = append(result, dto.OrderItem{
ProductID: item.ProductID,
ProductName: item.ProductName,
Price: item.Price,
Quantity: item.Quantity,
TotalPrice: item.TotalPrice,
ImageURL: item.ImageURL,
})
}
return result
}
// CancelOrder 取消订单
2025-12-10 13:51:09 +08:00
func (s *order) CancelOrder(ctx context.Context, req *dto.CancelOrderReq) (*dto.CancelOrderResp, error) {
2025-12-10 09:02:41 +08:00
// 1. 参数验证
if req.TenantID == "" || req.OrderNo == "" {
return nil, errors.New("必填参数不能为空")
}
// 2. 查询订单
2025-12-10 13:51:09 +08:00
order, status, err := dao.Order.GetOrderByNo(ctx, req.TenantID, req.OrderNo)
2025-12-10 09:02:41 +08:00
if err != nil {
return nil, fmt.Errorf("获取订单失败: %w", err)
}
if order == nil {
return nil, errors.New("订单不存在")
}
// 3. 检查订单状态(只有待支付订单可以取消)
2025-12-10 13:51:09 +08:00
if status != consts.OrderStatusPending {
2025-12-10 09:02:41 +08:00
return nil, fmt.Errorf("订单状态不正确,当前状态: %s", status)
}
// 4. 将订单移动到已取消状态
updateData := bson.M{
"cancel_reason": req.Reason,
}
2025-12-10 13:51:09 +08:00
if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, req.TenantID, req.OrderNo, updateData); err != nil {
2025-12-10 09:02:41 +08:00
return nil, fmt.Errorf("取消订单失败: %w", err)
}
// 5. 返回结果
resp := &dto.CancelOrderResp{
OrderNo: req.OrderNo,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusCancelled),
2025-12-10 09:02:41 +08:00
}
return resp, nil
}
// ListOrders 查询订单列表
2025-12-10 13:51:09 +08:00
func (s *order) ListOrders(ctx context.Context, req *dto.ListOrdersReq) (*dto.ListOrdersResp, error) {
2025-12-10 09:02:41 +08:00
// 1. 参数验证
if req.TenantID == "" {
return nil, errors.New("租户ID不能为空")
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 || req.PageSize > 100 {
req.PageSize = 20
}
// 2. 根据状态查询订单列表
2025-12-10 13:51:09 +08:00
var status consts.OrderStatus
2025-12-10 09:02:41 +08:00
if req.Status != "" {
2025-12-10 13:51:09 +08:00
status = consts.OrderStatus(req.Status)
2025-12-10 09:02:41 +08:00
} else {
// 默认查询所有状态
2025-12-10 13:51:09 +08:00
status = consts.OrderStatusPending
2025-12-10 09:02:41 +08:00
}
2025-12-10 13:51:09 +08:00
orders, total, err := dao.Order.ListOrdersByStatus(ctx, status, req.TenantID, req.UserID, req.Page, req.PageSize)
2025-12-10 09:02:41 +08:00
if err != nil {
return nil, fmt.Errorf("查询订单列表失败: %w", err)
}
// 3. 转换订单列表
var orderSummaries []dto.OrderSummary
switch status {
2025-12-10 13:51:09 +08:00
case consts.OrderStatusPending:
2025-12-10 09:02:41 +08:00
if pendingOrders, ok := orders.([]entity.OrderPending); ok {
for _, order := range pendingOrders {
orderSummaries = append(orderSummaries, dto.OrderSummary{
ID: order.ID.Hex(),
OrderNo: order.OrderNo,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusPending),
2025-12-10 09:02:41 +08:00
Subject: order.Subject,
CreatedAt: order.CreatedAt,
})
}
}
2025-12-10 13:51:09 +08:00
case consts.OrderStatusPaid:
2025-12-10 09:02:41 +08:00
if paidOrders, ok := orders.([]entity.OrderPaid); ok {
for _, order := range paidOrders {
orderSummaries = append(orderSummaries, dto.OrderSummary{
ID: order.ID.Hex(),
OrderNo: order.OrderNo,
TotalAmount: order.TotalAmount,
PayAmount: order.PayAmount,
2025-12-10 13:51:09 +08:00
Status: string(consts.OrderStatusPaid),
2025-12-10 09:02:41 +08:00
Subject: order.Subject,
CreatedAt: order.CreatedAt,
PaidAt: &order.PaidAt,
})
}
}
// 其他状态类似处理...
}
// 4. 返回结果
resp := &dto.ListOrdersResp{
Orders: orderSummaries,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}
return resp, nil
}
// ProcessExpiredOrders 处理过期订单
2025-12-10 13:51:09 +08:00
func (s *order) ProcessExpiredOrders(ctx context.Context, tenantID string) error {
2025-12-10 09:02:41 +08:00
// 获取过期的待支付订单
2025-12-10 13:51:09 +08:00
expiredOrders, err := dao.Order.GetExpiredPendingOrders(ctx, tenantID)
2025-12-10 09:02:41 +08:00
if err != nil {
return fmt.Errorf("获取过期订单失败: %w", err)
}
// 批量取消过期订单
for _, order := range expiredOrders {
updateData := bson.M{
"cancel_reason": "订单超时自动取消",
}
2025-12-10 13:51:09 +08:00
if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, tenantID, order.OrderNo, updateData); err != nil {
2025-12-10 09:02:41 +08:00
// 记录错误但继续处理其他订单
fmt.Printf("取消过期订单失败: %s, 错误: %v\n", order.OrderNo, err)
}
}
return nil
}
// UpdatePayInfo 更新支付信息
2025-12-10 13:51:09 +08:00
func (s *order) UpdatePayInfo(ctx context.Context, tenantID, orderNo string, payInfo entity.PayInfo) error {
return dao.Order.UpdatePayInfo(ctx, tenantID, orderNo, payInfo)
2025-12-10 09:02:41 +08:00
}